simvx.core._drawable2d

The two dirty bits are deliberately SEPARATE (design §2.7, P0 gate 3b)

  • attr:

    _render_dirty – “this drawable’s geometry/appearance changed; its item(s) must be re-collected/re-uploaded.” Its lifetime is owned by the render layer: it persists until the upload step clears it (:meth:_clear_render_dirty), and is NEVER cleared by a world_position read.

  • attr:

    _transform_render_dirty – “this drawable moved; its transform row must be rewritten (geometry untouched – the scroll-by-translation fast path).” Also render-owned, also persists until the upload step clears it.

Neither is Node2D._transform_dirty: that flag is cleared lazily by any world_position/world_transform read (scene-adapter / collision / audio / culling all read it mid-frame) and _invalidate_transform short-circuits its descendant recursion if not self._transform_dirty. A render-dirty bit layered on it would inherit that early-return and leave descendants stale after a read-then-move sequence (the exact P0 gate 3b adversarial case). So the transform-render bit gets its OWN propagation with no such short-circuit.

Retention bits

These render-retention bits are read by the 2D item pipeline’s

class:

RenderItemCache (P2) to decide what to re-collect, re-capture, or patch in place each frame.

The shared 2D-drawable concept (design §7.6 / §2.7, P2 retention keystone).

P0 gate 3 proved the blanket Property.__set__ -> queue_redraw hook (descriptors.py) is silently a no-op for every Node2D today: only Control defined queue_redraw, so a Sprite2D colour / texture / visible change marked nothing dirty. The build-once retention design (§2.7) rides that same blanket hook to set a per-item dirty bit, so item-level invalidation for sprites is dead until the hook actually fires on a Node2D.

This module resolves the §7.6 “shared drawable base-class vs mixin vs hoist” question as a small mixin (:class:Drawable2D) carrying the render-retention state + queue_redraw. The mixin (not “hoist onto Node2D”) is chosen because the drawable concept must also cover CanvasLayer, which is a Node, not a Node2D (design §5.6 / §7.6: “a plain hoist onto Node2D would miss it”). A mixin lets Node2D and CanvasLayer share one implementation without forcing CanvasLayer through Node2D’s 2D transform cache. Control inherits it via Node2D and extends queue_redraw additively (its legacy _DrawRecorder path keeps working).

Module Contents

Classes

Drawable2D

Mixin: render-retention dirty state + queue_redraw for 2D drawables.

API

class simvx.core._drawable2d.Drawable2D[source]

Mixin: render-retention dirty state + queue_redraw for 2D drawables.

Mixed into :class:~simvx.core.nodes_2d.node2d.Node2D (so every sprite / shape / Text2D / Control inherits it) and

Class:

~simvx.core.nodes_2d.canvas.CanvasLayer (a Node, covered by the mixin rather than a Node2D hoist – design §7.6).

The mixin owns no transform: it never reads position/world_transform. It only flips render-retention flags. The item pipeline reads the flags + drains them after upload; the legacy path ignores them.

hdr

‘Property(…)’

queue_redraw() None[source]

Mark this drawable’s item(s) dirty (re-collect / re-upload next frame).

The blanket Property.__set__ hook (descriptors.py) calls this on any changed Property when the owner defines it – which is now every Node2D (colour, text, font_scale, visible, texture refs, size/anchor/ margin on Controls, …). It is also the manual escape hatch for an on_draw body that reads non-Property state (the dynamic cases).

Idempotent and cheap: a no-op once already dirty.

property render_dirty: bool[source]

Whether appearance/geometry changed since the last upload (introspection).

property transform_render_dirty: bool[source]

Whether the transform row needs a rewrite since the last upload.