Class GlyphGrid

java.lang.Object
com.badlogic.gdx.scenes.scene2d.Actor
com.badlogic.gdx.scenes.scene2d.Group
com.github.yellowstonegames.glyph.GlyphGrid
All Implemented Interfaces:
com.badlogic.gdx.scenes.scene2d.utils.Cullable

public class GlyphGrid extends com.badlogic.gdx.scenes.scene2d.Group
Stores a sparse map of (unmoving) glyphs by their positions, as a dense grid of background colors, and potentially Actors that can move around and perform Actions. Glyphs are represented by long values, and are handled by a Font (which this also stores) to get a char, foreground color, and a variety of possible styles for each glyph. A GlyphGrid also stores a Viewport, which defaults to a StretchViewport but can be changed easily; this viewport affects what region of the GlyphGrid is drawn. This is a scene2d Group and Actor, and must be added to a Stage to function correctly. If you want to draw a limited portion of the grid for performance, you can use an overload of draw(Batch, Frustum) and manually draw the GlyphGrid, or keep using Stage.draw() and set the startX, startY, endX, and endY fields to restrict the drawn area. You should call resize(int, int) in your application's or screen's resize code, because it keeps the viewport accurate.
Action behavior is unfortunately a little complex, but issues can usually be resolved by moving around the order in which Stage.act() Stage.draw(), and map-changing methods are called. Typically, if you put(Coord, long) every visible glyph every frame, you would do that first, then call Stage.act(), and only then call Stage.draw(). This allows Actions (in act()) to affect the GlyphGrid without the map being overwritten before it can be drawn. There are some useful Actions specialized to working with GlyphGrid in GridAction, and some non-specialized support code for Actions in MoreActions.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    int[][]
     
    int
     
    int
     
    protected com.github.tommyettinger.textra.Font
     
    protected int
     
    protected int
     
    com.github.yellowstonegames.grid.CoordLongOrderedMap
     
    int
     
    int
     
    com.badlogic.gdx.utils.viewport.Viewport
     

    Fields inherited from class com.badlogic.gdx.scenes.scene2d.Actor

    POOLS
  • Constructor Summary

    Constructors
    Constructor
    Description
    Constructs a bare-bones GlyphGrid with size 64x64.
    GlyphGrid(com.github.tommyettinger.textra.Font font)
    Constructs a 64x64 GlyphGrid with the specified Font.
    GlyphGrid(com.github.tommyettinger.textra.Font font, int gridWidth, int gridHeight)
    Constructs a GlyphGrid with the specified size in cells wide and cells tall for its grid, using the specified Font (which will be copied).
    GlyphGrid(com.github.tommyettinger.textra.Font font, int gridWidth, int gridHeight, boolean squareCenteredCells)
    Constructs a GlyphGrid with the specified size in cells wide and cells tall for its grid, using the specified Font (which will be copied).
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    Returns true if any children of this GlyphGrid currently have Actions, or false if none do.
    void
    burst(float delay, float startX, float startY, float distance, int count, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, Runnable postRunnable)
    Adds count new GlyphActors at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor, and after delay seconds, starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActors.
    void
    burst(float startX, float startY, float distance, int count, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration)
    Adds count new GlyphActors at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor, and immediately starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before finally removing the GlyphActors.
    void
    draw(com.badlogic.gdx.graphics.g2d.Batch batch)
    Draws the entire GlyphGrid at its position in world units.
    void
    draw(com.badlogic.gdx.graphics.g2d.Batch batch, float parentAlpha)
     
    void
    draw(com.badlogic.gdx.graphics.g2d.Batch batch, int startCellX, int startCellY, int endCellX, int endCellY)
    Draws part of the GlyphGrid at its position in world units.
    void
    draw(com.badlogic.gdx.graphics.g2d.Batch batch, com.badlogic.gdx.math.Frustum limit)
    Draws part of the GlyphGrid at its position in world units.
    dyeFG(int x, int y, int newColor, float change, float duration, Runnable post)
     
    com.github.tommyettinger.textra.Font
     
    int
    Gets how high the grid is, measured in discrete cells.
    int
    Gets how wide the grid is, measured in discrete cells.
    boolean
    Returns true if this GlyphGrid or any of its children currently have Actions, or false if none do.
    void
    put(int x, int y, char simpleChar, int color)
    Places a character (optionally with style information) at the specified cell, using the given foreground color.
    void
    put(int x, int y, int codepoint)
    Places a character (optionally with style information) at the specified cell, using white foreground color.
    void
    put(int x, int y, int codepoint, int color)
    Places a character (optionally with style information) at the specified cell, using the given foreground color.
    void
    put(int x, int y, long glyph)
    Places a glyph (optionally with style information and/or color) at the specified cell.
    void
    put(com.github.yellowstonegames.grid.Coord fused, long glyph)
    Places a glyph (optionally with style information and/or color) at the specified cell (given as a Coord).
    void
    resize(int screenWidth, int screenHeight)
    This should generally be called in the ApplicationListener.resize(int, int) or Screen.resize(int, int) method when the screen size changes.
    void
    setFont(com.github.tommyettinger.textra.Font font)
    Sets the Font this uses, but also configures the viewport to use the appropriate size cells, then scales the font to size 1x1 (this makes some calculations much easier inside GlyphGrid).
    void
    setFont(com.github.tommyettinger.textra.Font font, boolean squareCenter)
    Sets the Font this uses, but also configures the viewport to use the appropriate size cells, then scales the font to size 1x1 (this makes some calculations much easier inside GlyphGrid).
    void
    setVisibilities(com.github.tommyettinger.function.IntIntPredicate predicate)
    Sets the visibility of each Actor child of this GlyphGrid based on the result of a predicate called on its x,y grid position (rounded from its float position).
    void
    summon(float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration)
    Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and immediately starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before removing the GlyphActor.
    void
    summon(float delay, float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, com.badlogic.gdx.math.Interpolation moveInterpolation, Runnable postRunnable)
    Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and after delay seconds, starts changing color to endColor, changing position using the given Interpolation so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActor.
    void
    summon(float delay, float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, Runnable postRunnable)
    Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and after delay seconds, starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActor.

    Methods inherited from class com.badlogic.gdx.scenes.scene2d.Group

    act, addActor, addActorAfter, addActorAt, addActorBefore, applyTransform, applyTransform, childrenChanged, clear, clear, clearChildren, clearChildren, computeTransform, debugAll, drawChildren, drawDebug, drawDebugChildren, findActor, getChild, getChildren, getCullingArea, hasChildren, hit, isTransform, localToDescendantCoordinates, removeActor, removeActor, removeActorAt, resetTransform, resetTransform, setCullingArea, setDebug, setStage, setTransform, swapActor, swapActor, toString

    Methods inherited from class com.badlogic.gdx.scenes.scene2d.Actor

    addAction, addCaptureListener, addListener, ancestorsVisible, ascendantsVisible, clearActions, clearListeners, clipBegin, clipBegin, clipEnd, debug, drawDebugBounds, fire, firstAscendant, getActions, getCaptureListeners, getColor, getDebug, getHeight, getListeners, getName, getOriginX, getOriginY, getParent, getRight, getRotation, getScaleX, getScaleY, getStage, getTop, getTouchable, getUserObject, getWidth, getX, getX, getY, getY, getZIndex, hasActions, hasKeyboardFocus, hasParent, hasScrollFocus, isAscendantOf, isDescendantOf, isTouchable, isTouchFocusListener, isTouchFocusTarget, isVisible, localToActorCoordinates, localToAscendantCoordinates, localToParentCoordinates, localToScreenCoordinates, localToStageCoordinates, moveBy, notify, parentToLocalCoordinates, positionChanged, remove, removeAction, removeCaptureListener, removeListener, rotateBy, rotationChanged, scaleBy, scaleBy, scaleChanged, screenToLocalCoordinates, setBounds, setColor, setColor, setDebug, setHeight, setName, setOrigin, setOrigin, setOriginX, setOriginY, setParent, setPosition, setPosition, setRotation, setScale, setScale, setScaleX, setScaleY, setSize, setTouchable, setUserObject, setVisible, setWidth, setX, setX, setY, setY, setZIndex, sizeBy, sizeBy, sizeChanged, stageToLocalCoordinates, toBack, toFront

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Field Details

    • gridWidth

      protected int gridWidth
    • gridHeight

      protected int gridHeight
    • map

      public com.github.yellowstonegames.grid.CoordLongOrderedMap map
    • backgrounds

      public int[][] backgrounds
    • font

      protected com.github.tommyettinger.textra.Font font
    • viewport

      public com.badlogic.gdx.utils.viewport.Viewport viewport
    • startX

      public int startX
    • startY

      public int startY
    • endX

      public int endX
    • endY

      public int endY
  • Constructor Details

    • GlyphGrid

      public GlyphGrid()
      Constructs a bare-bones GlyphGrid with size 64x64. Does not set font, you will have to set it later. This calls GlyphGrid(Font, int, int, boolean) with font=null and squareCenteredCells=false.
    • GlyphGrid

      public GlyphGrid(com.github.tommyettinger.textra.Font font)
      Constructs a 64x64 GlyphGrid with the specified Font. You probably want GlyphGrid(Font, int, int) unless your maps are always 64x64. This calls GlyphGrid(Font, int, int, boolean) with squareCenteredCells=true.
      Parameters:
      font - a Font that will be copied and used for the new GlyphGrid
    • GlyphGrid

      public GlyphGrid(com.github.tommyettinger.textra.Font font, int gridWidth, int gridHeight)
      Constructs a GlyphGrid with the specified size in cells wide and cells tall for its grid, using the specified Font (which will be copied). This calls GlyphGrid(Font, int, int, boolean) with squareCenteredCells=true.
      Parameters:
      font - a Font that will be copied and used for the new GlyphGrid
      gridWidth - how many cells wide the grid should be
      gridHeight - how many cells tall the grid should be
    • GlyphGrid

      public GlyphGrid(com.github.tommyettinger.textra.Font font, int gridWidth, int gridHeight, boolean squareCenteredCells)
      Constructs a GlyphGrid with the specified size in cells wide and cells tall for its grid, using the specified Font (which will be copied). If squareCenteredGlyphs is true, the Font copy this uses will be modified to have extra space around glyphs so that they fit in square cells. For fonts that use gridGlyphs (the default behavior), any box drawing characters will still take up the full cell, and will connect seamlessly.
      Parameters:
      font - a Font that will be copied and used for the new GlyphGrid
      gridWidth - how many cells wide the grid should be
      gridHeight - how many cells tall the grid should be
      squareCenteredCells - if true, space will be added to make glyphs fit in square cells
  • Method Details

    • getFont

      public com.github.tommyettinger.textra.Font getFont()
    • setFont

      public void setFont(com.github.tommyettinger.textra.Font font)
      Sets the Font this uses, but also configures the viewport to use the appropriate size cells, then scales the font to size 1x1 (this makes some calculations much easier inside GlyphGrid). This is the same as calling setFont(font, true).
      Parameters:
      font - a Font that will be used directly (not copied) and used to calculate the viewport dimensions
    • setFont

      public void setFont(com.github.tommyettinger.textra.Font font, boolean squareCenter)
      Sets the Font this uses, but also configures the viewport to use the appropriate size cells, then scales the font to size 1x1 (this makes some calculations much easier inside GlyphGrid). The line height of the given Font is set to 1.25x its current height, which helps a lot when fitting glyphs into cells. This also halves the crispness of distance field fonts used (standard fonts are not affected), and just for good measure sets Font.integerPosition to false.
      This can add spacing to cells so that they are always square, while keeping the aspect ratio of font as it was passed in. Use squareCenter=true to enable this; note that it modifies the Font more deeply than normally.
      Parameters:
      font - a Font that will be used directly (not copied) and used to calculate the viewport dimensions
      squareCenter - if true, spacing will be added to the sides of each glyph so that they fit in square cells
    • getGridWidth

      public int getGridWidth()
      Gets how wide the grid is, measured in discrete cells.
      Returns:
      how many cells wide the grid is
    • getGridHeight

      public int getGridHeight()
      Gets how high the grid is, measured in discrete cells.
      Returns:
      how many cells high the grid is
    • put

      public void put(int x, int y, int codepoint)
      Places a character (optionally with style information) at the specified cell, using white foreground color.
      Parameters:
      x - x position of the cell, measured in cells on the grid
      y - y position of the cell, measured in cells on the grid
      codepoint - the character, with or without style information, to place
    • put

      public void put(int x, int y, int codepoint, int color)
      Places a character (optionally with style information) at the specified cell, using the given foreground color.
      Parameters:
      x - x position of the cell, measured in cells on the grid
      y - y position of the cell, measured in cells on the grid
      codepoint - the character, with or without style information, to place
      color - the RGBA8888 color to use for the character
    • put

      public void put(int x, int y, char simpleChar, int color)
      Places a character (optionally with style information) at the specified cell, using the given foreground color.
      Parameters:
      x - x position of the cell, measured in cells on the grid
      y - y position of the cell, measured in cells on the grid
      simpleChar - the character, without style information, to place
      color - the RGBA8888 color to use for the character
    • put

      public void put(int x, int y, long glyph)
      Places a glyph (optionally with style information and/or color) at the specified cell.
      Parameters:
      x - x position of the cell, measured in cells on the grid
      y - y position of the cell, measured in cells on the grid
      glyph - the glyph to place, as produced by Font.markupGlyph(char, String, ColorLookup)
    • put

      public void put(com.github.yellowstonegames.grid.Coord fused, long glyph)
      Places a glyph (optionally with style information and/or color) at the specified cell (given as a Coord). This put() method has the least overhead if you already have a Coord key and long glyph.
      Parameters:
      fused - a Coord representing an x,y position
      glyph - the glyph to place, as produced by Font.markupGlyph(char, String, ColorLookup)
    • draw

      public void draw(com.badlogic.gdx.graphics.g2d.Batch batch)
      Draws the entire GlyphGrid at its position in world units. Does no clipping.
      Parameters:
      batch - a SpriteBatch, usually; must at least be compatible with SpriteBatch's attributes
    • draw

      public void draw(com.badlogic.gdx.graphics.g2d.Batch batch, com.badlogic.gdx.math.Frustum limit)
      Draws part of the GlyphGrid at its position in world units. Still iterates through all keys in order, but only draws those that are visible in the given Frustum limit.
      Parameters:
      batch - a SpriteBatch, usually; must at least be compatible with SpriteBatch's attributes
      limit - a Frustum, usually obtained from Camera.frustum, that delimits what will be rendered
    • draw

      public void draw(com.badlogic.gdx.graphics.g2d.Batch batch, int startCellX, int startCellY, int endCellX, int endCellY)
      Draws part of the GlyphGrid at its position in world units. Only draws cells between startCellX (inclusive) and endCellX (exclusive), and likewise for startCellY and endCellY, where those ints represent cell positions and not screen or world positions. This only even considers keys that are in the given start-to-end rectangle, and doesn't check keys or values outside it. This is probably the most efficient of the draw() methods here, but requires you to know what the start and end bounds are. All the start and end cell coordinates must be non-negative.
      Parameters:
      batch - a SpriteBatch, usually; must at least be compatible with SpriteBatch's attributes
      startCellX - the inclusive x of the lower-left corner, measured in cells, to start rendering at
      startCellY - the inclusive y of the lower-left corner, measured in cells, to start rendering at
      endCellX - the exclusive x of the upper-right corner, measured in cells, to stop rendering at
      endCellY - the exclusive y of the upper-right corner, measured in cells, to stop rendering at
    • draw

      public void draw(com.badlogic.gdx.graphics.g2d.Batch batch, float parentAlpha)
      Overrides:
      draw in class com.badlogic.gdx.scenes.scene2d.Group
    • resize

      public void resize(int screenWidth, int screenHeight)
      This should generally be called in the ApplicationListener.resize(int, int) or Screen.resize(int, int) method when the screen size changes. This affects the viewport only.
      Parameters:
      screenWidth - the new screen width in pixels
      screenHeight - the new screen height in pixels
    • setVisibilities

      public void setVisibilities(com.github.tommyettinger.function.IntIntPredicate predicate)
      Sets the visibility of each Actor child of this GlyphGrid based on the result of a predicate called on its x,y grid position (rounded from its float position). If predicate returns true, an Actor will be set to be visible, while if it returns false, the Actor will be set to be invisible.
      Parameters:
      predicate - will be given the rounded x,y positions of Actors and should return true if the Actor is visible
    • isAnythingActing

      public boolean isAnythingActing()
      Returns true if this GlyphGrid or any of its children currently have Actions, or false if none do.
      Returns:
      whether this GlyphGrid or any of its children currently have Actions
    • areChildrenActing

      public boolean areChildrenActing()
      Returns true if any children of this GlyphGrid currently have Actions, or false if none do.
      Returns:
      whether any children of this GlyphGrid currently have Actions
    • dyeFG

      public MoreActions.LenientSequenceAction dyeFG(int x, int y, int newColor, float change, float duration, Runnable post)
    • summon

      public void summon(float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration)
      Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and immediately starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before removing the GlyphActor. This doesn't return any kind of Action because it handles the creating of a GlyphActor and its Actions on its own.
      Parameters:
      startX - the starting x position in world coordinates
      startY - the starting y position in world coordinates
      endX - the ending x position in world coordinates
      endY - the ending y position in world coordinates
      shown - the char to show (the same char throughout the effect)
      startColor - the starting Color
      endColor - the Color to transition to
      startRotation - the starting rotation for the summoned glyph, in degrees
      endRotation - the ending rotation for the summoned glyph, in degrees
      duration - the duration in seconds for the effect
    • summon

      public void summon(float delay, float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, Runnable postRunnable)
      Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and after delay seconds, starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActor. This doesn't return any kind of Action because it handles the creating of a GlyphActor and its Actions on its own.
      Parameters:
      delay - how long to wait in seconds before starting the effect
      startX - the starting x position in world coordinates
      startY - the starting y position in world coordinates
      endX - the ending x position in world coordinates
      endY - the ending y position in world coordinates
      shown - the char to show (the same char throughout the effect)
      startColor - the starting Color
      endColor - the Color to transition to
      startRotation - the starting rotation for the summoned glyph, in degrees
      endRotation - the ending rotation for the summoned glyph, in degrees
      duration - the duration in seconds for the effect
      postRunnable - a Runnable to execute after the summoning completes; may be null to do nothing.
    • summon

      public void summon(float delay, float startX, float startY, float endX, float endY, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, com.badlogic.gdx.math.Interpolation moveInterpolation, Runnable postRunnable)
      Adds a new GlyphActor at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor and startRotation, and after delay seconds, starts changing color to endColor, changing position using the given Interpolation so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActor. This doesn't return any kind of Action because it handles the creating of a GlyphActor and its Actions on its own.
      Parameters:
      delay - how long to wait in seconds before starting the effect
      startX - the starting x position in world coordinates
      startY - the starting y position in world coordinates
      endX - the ending x position in world coordinates
      endY - the ending y position in world coordinates
      shown - the char to show (the same char throughout the effect)
      startColor - the starting Color
      endColor - the Color to transition to
      startRotation - the starting rotation for the summoned glyph, in degrees
      endRotation - the ending rotation for the summoned glyph, in degrees
      duration - the duration in seconds for the effect
      moveInterpolation - a non-null Interpolation that affects the rate at which the glyph moves
      postRunnable - a Runnable to execute after the summoning completes; may be null to do nothing.
    • burst

      public void burst(float startX, float startY, float distance, int count, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration)
      Adds count new GlyphActors at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor, and immediately starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before finally removing the GlyphActors. The rotation of each GlyphActor this spawns will be different; the given startRotation applies to the always-created GlyphActor directly above the start, and the endRotation will be that GlyphActor's rotation when it is removed, but any other created GlyphActors will have their own rotations based on the direction they move in. This doesn't return any kind of Action because it handles the creating of a GlyphActor and its Actions on its own.
      Parameters:
      startX - the starting x position in world coordinates
      startY - the starting y position in world coordinates
      distance - how far away each GlyphActor should move away from the start, in world coordinates
      count - how many chars to summon
      shown - the char to show (the same char throughout the effect)
      startColor - the starting Color
      endColor - the Color to transition to
      startRotation - the starting rotation for the top summoned glyph, in degrees
      endRotation - the ending rotation for the top summoned glyph, in degrees
      duration - the duration in seconds for the effect
    • burst

      public void burst(float delay, float startX, float startY, float distance, int count, char shown, int startColor, int endColor, float startRotation, float endRotation, float duration, Runnable postRunnable)
      Adds count new GlyphActors at (startX, startY) in world coordinates (which should have 1 world unit equal to 1 cell) using the char shown with the given startColor, and after delay seconds, starts changing color to endColor, changing position so that it ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if it is non-null) and finally removing the GlyphActors. The rotation of each GlyphActor this spawns will be different; the given startRotation applies to the always-created GlyphActor directly above the start, and the endRotation will be that GlyphActor's rotation when it is removed, but any other created GlyphActors will have their own rotations based on the direction they move in. This doesn't return any kind of Action because it handles the creating of a GlyphActor and its Actions on its own.
      Parameters:
      delay - how long to wait in seconds before starting the effect
      startX - the starting x position in world coordinates
      startY - the starting y position in world coordinates
      distance - how far away each GlyphActor should move away from the start, in world coordinates
      shown - the char to show (the same char throughout the effect)
      startColor - the starting Color
      endColor - the Color to transition to
      startRotation - the starting rotation for the top summoned glyph, in degrees
      endRotation - the ending rotation for the top summoned glyph, in degrees
      duration - the duration in seconds for the effect
      postRunnable - a Runnable to execute after the summoning completes; may be null to do nothing.