Dirty Flag

Intent: Avoid unnecessary work by deferring it until the result is needed.

Acron Mapping

Pattern role

Acron equivalent

Primary data

NodePath local transform (setPos, setHpr, setScale)

Derived/cached data

World-space transform, bounding box, path cost, AI visibility data

Dirty flag

Boolean _dirty on a Python wrapper

Invalidation

Set _dirty = True in the wrapper’s set_pos() / set_hpr() overrides

Recompute

_recalculate() called lazily inside get_world_pos()

Note

Acron’s scene graph already implements dirty flags internally for world-space transform propagation — you get this for free on NodePath. Apply the pattern yourself only for custom derived data that Acron doesn’t track (minimap positions, AI cost maps, UI summary strings).

How to Implement

  1. Identify the expensive derived value (e.g. nav-mesh region for an entity).

  2. Store both the cached value and a _dirty flag on the wrapper object.

  3. Set _dirty = True in every method that changes the primary data.

  4. In the getter, check _dirty; if True, recompute and clear the flag.

  5. Optionally, defer all dirty-object recomputes to a single late-frame task (batch recalculation).

Code Sketch (Python)

class SceneEntity:
    def __init__(self, node):
        self._node = node
        self._cached_region = None
        self._dirty = True       # start dirty to force first compute

    # ── Primary data setters — always mark dirty ──────────────────
    def set_pos(self, x, y, z):
        self._node.setPos(x, y, z)
        self._dirty = True

    def set_hpr(self, h, p, r):
        self._node.setHpr(h, p, r)
        self._dirty = True

    # ── Derived data getter — lazy recompute ──────────────────────
    @property
    def nav_region(self):
        if self._dirty:
            self._cached_region = self._compute_nav_region()
            self._dirty = False
        return self._cached_region

    def _compute_nav_region(self):
        pos = self._node.getPos(render)   # world-space position
        # expensive spatial lookup
        return nav_mesh.region_at(pos.x, pos.y)


# Batch version: collect all dirty entities and recompute once per frame
class DirtyFlagSystem:
    def __init__(self):
        self._dirty_set = set()
        taskMgr.add(self._flush, 'dirtyFlush', sort=45)  # late in frame

    def mark_dirty(self, entity):
        self._dirty_set.add(entity)

    def _flush(self, task):
        for entity in self._dirty_set:
            entity._cached_region = entity._compute_nav_region()
            entity._dirty = False
        self._dirty_set.clear()
        return task.cont

Pitfalls

  • Forgetting to mark dirty: any path that modifies primary data without calling the wrapper’s setter will silently serve stale cache. Encapsulate all setters or use __setattr__ interception.

  • Marking dirty too eagerly: if _dirty is set every frame regardless of actual change, the cache never helps. Only set it when the value actually changed.

  • Acron scene graph already handles transforms: do not re-implement transform dirty-flags for NodePath hierarchies — call node.getPos(render) (world space) which Acron recalculates efficiently already. Apply this pattern to custom derived data only.