Current State
- Each
Node has a mtxWorld matrix, and its ComponentTransform contains the mtxLocal matrix.
mtxWorld is updated once per frame during the render prepare loop, with a rudimentery dirty check on the involved matrices.
- This makes it hard for creators/developers to access a fresh
mtxWorld (after modifying mtxLocal or before the first render prepare cycle)
Proposed Idea
Introduce a lazy recomputation system for mtxWorld, inspired by Godot:
-
Dirty flag per node
- Each
Node has a mtxWorldDirty flag.
-
Selective invalidation
- Changing
ComponentTransform.mtxLocal marks the node dirty..
- Recursively marks descendants dirty only if they are not already dirty, avoiding redundant work.
-
Explicit update notification
- Modifications to
mtxLocal must be explicitly communicated (via setter or markDirty()), notifying the node and triggering invalidation.
-
On-demand computation
- Accessing a node’s
mtxWorld checks the mtxWorldDirty flag.
- If dirty, recompute using the parent’s
mtxWorld; otherwise, return cached value.
Proposed Invalidation Workflow
-
Assignment triggers invalidation
ComponentTransform.mtxLocal = matrix → calls ComponentTransform.markDirty().
-
Component propagates to node
ComponentTransform.markDirty() → calls ComponentTransform.node.markDirty().
-
Node invalidates subtree
Node.markDirty() → sets mtxWorldDirty and recursively marks descendants only if not already dirty.
Effects
- Efficient lazy updates: Only recompute
mtxWorld when needed.
- First change → expensive (walk branch and mark descendants not already dirty).
- Subsequent changes → cheap (skip already-invalid descendants).
- Reads of
mtxWorld → cheap (check dirty flag, recompute if needed).
- Transparent for creators: simple assignment triggers updates automatically.
- Predictable: explicit
markDirty() ensures correctness for procedural or bulk updates.
- Supports dynamic scenes: nodes added mid-frame compute transforms correctly on demand.
Considerations
- Child traversal cost: Marking a node dirty requires subtree traversal, which can be expensive for large hierarchies.
- Explicit dirty marking required: Developers must explicitly use the setter or
markDirty(); otherwise, mtxWorld may become stale.
- Potential developer mistakes: Forgetting to mark dirty can lead to subtle bugs.
- Lazy recomputation trade-off: First read after a change may be expensive due to recomputation.
- Integration with other systems: This mechanism would also have to be integrated with the mutation and animation sytem.
Current State
Nodehas amtxWorldmatrix, and itsComponentTransformcontains themtxLocalmatrix.mtxWorldis updated once per frame during the render prepare loop, with a rudimentery dirty check on the involved matrices.mtxWorld(after modifyingmtxLocalor before the first render prepare cycle)Proposed Idea
Introduce a lazy recomputation system for
mtxWorld, inspired by Godot:Dirty flag per node
Nodehas amtxWorldDirtyflag.Selective invalidation
ComponentTransform.mtxLocalmarks the node dirty..Explicit update notification
mtxLocalmust be explicitly communicated (via setter ormarkDirty()), notifying the node and triggering invalidation.On-demand computation
mtxWorldchecks themtxWorldDirtyflag.mtxWorld; otherwise, return cached value.Proposed Invalidation Workflow
Assignment triggers invalidation
ComponentTransform.mtxLocal = matrix→ callsComponentTransform.markDirty().Component propagates to node
ComponentTransform.markDirty()→ callsComponentTransform.node.markDirty().Node invalidates subtree
Node.markDirty()→ setsmtxWorldDirtyand recursively marks descendants only if not already dirty.Effects
mtxWorldwhen needed.mtxWorld→ cheap (check dirty flag, recompute if needed).markDirty()ensures correctness for procedural or bulk updates.Considerations
markDirty(); otherwise,mtxWorldmay become stale.