simvx.core.descriptors

Descriptors, enums, and type aliases used throughout the engine.

Signal and Connection live in simvx.core.signals.

Module Contents

Classes

CoroutineHandle

Cancellable handle returned by Node.start_coroutine().

UpdateMode

Controls whether a node processes when the SceneTree is paused.

Notification

Notifications dispatched to nodes during lifecycle and property changes.

Property

Descriptor for editor-visible, serializable node properties.

Children

List-like container with named child access.

Functions

declared_default

The value prop reads back on an instance that has never written it.

restore_property

Put obj’s Property name back to value without running __set__.

Data

API

simvx.core.descriptors.log

‘getLogger(…)’

simvx.core.descriptors.Coroutine

None

class simvx.core.descriptors.CoroutineHandle(gen: simvx.core.descriptors.Coroutine)[source]

Cancellable handle returned by Node.start_coroutine().

The runtime drives coroutines with gen.send(dt) after the initial prime, so coroutines may receive the per-tick delta time via dt = yield. Coroutines that yield without binding the value still work: the sent dt is simply discarded.

Initialization

__slots__

(‘_gen’, ‘_cancelled’, ‘_primed’)

cancel()[source]

Cancel this coroutine. It will be removed on the next tick.

property is_cancelled: bool[source]
class simvx.core.descriptors.UpdateMode[source]

Bases: enum.IntEnum

Controls whether a node processes when the SceneTree is paused.

Set via node.update_mode = UpdateMode.ALWAYS (and similar). The effective mode is resolved by walking up the tree until a non-INHERIT ancestor is found; the resolved value is cached and invalidated on reparent.

Pause the tree with tree.paused = True. When paused, only ALWAYS and PAUSED_ONLY nodes run their process / physics_process.

Because INHERIT walks ancestors, an ALWAYS mode set high in the tree propagates to every INHERIT descendant: defeating the pause for the whole subtree. Set ALWAYS only on leaf nodes or CanvasLayers, never on a game root with gameplay children.

Initialization

Initialize self. See help(type(self)) for accurate signature.

INHERIT

0

PAUSABLE

1

PAUSED_ONLY

2

ALWAYS

3

DISABLED

4

__abs__()
__add__()
__and__()
__bool__()
__ceil__()
__delattr__()
__dir__()
__divmod__()
__eq__()
__float__()
__floor__()
__floordiv__()
__format__()
__ge__()
__getattribute__()
__getnewargs__()
__getstate__()
__gt__()
__hash__()
__index__()
__int__()
__invert__()
__le__()
__lshift__()
__lt__()
__mod__()
__mul__()
__ne__()
__neg__()
__new__()
__or__()
__pos__()
__pow__()
__radd__()
__rand__()
__rdivmod__()
__reduce__()
__reduce_ex__()
__repr__()
__rfloordiv__()
__rlshift__()
__rmod__()
__rmul__()
__ror__()
__round__()
__rpow__()
__rrshift__()
__rshift__()
__rsub__()
__rtruediv__()
__rxor__()
__setattr__()
__sizeof__()
__str__()
__sub__()
__subclasshook__()
__truediv__()
__trunc__()
__xor__()
as_integer_ratio()
bit_count()
bit_length()
conjugate()
class denominator
class imag
is_integer()
class numerator
class real
to_bytes()
__deepcopy__(memo)
__copy__()
name()
value()
class simvx.core.descriptors.Notification[source]

Bases: enum.IntEnum

Notifications dispatched to nodes during lifecycle and property changes.

Initialization

Initialize self. See help(type(self)) for accurate signature.

TRANSFORM_CHANGED

‘auto(…)’

VISIBILITY_CHANGED

‘auto(…)’

ENTER_TREE

‘auto(…)’

EXIT_TREE

‘auto(…)’

READY

‘auto(…)’

PARENTED

‘auto(…)’

UNPARENTED

‘auto(…)’

PROCESS

‘auto(…)’

PHYSICS_PROCESS

‘auto(…)’

__abs__()
__add__()
__and__()
__bool__()
__ceil__()
__delattr__()
__dir__()
__divmod__()
__eq__()
__float__()
__floor__()
__floordiv__()
__format__()
__ge__()
__getattribute__()
__getnewargs__()
__getstate__()
__gt__()
__hash__()
__index__()
__int__()
__invert__()
__le__()
__lshift__()
__lt__()
__mod__()
__mul__()
__ne__()
__neg__()
__new__()
__or__()
__pos__()
__pow__()
__radd__()
__rand__()
__rdivmod__()
__reduce__()
__reduce_ex__()
__repr__()
__rfloordiv__()
__rlshift__()
__rmod__()
__rmul__()
__ror__()
__round__()
__rpow__()
__rrshift__()
__rshift__()
__rsub__()
__rtruediv__()
__rxor__()
__setattr__()
__sizeof__()
__str__()
__sub__()
__subclasshook__()
__truediv__()
__trunc__()
__xor__()
as_integer_ratio()
bit_count()
bit_length()
conjugate()
class denominator
class imag
is_integer()
class numerator
class real
to_bytes()
__deepcopy__(memo)
__copy__()
name()
value()
class simvx.core.descriptors.Property(default: Any = _UNSET, *, default_factory: collections.abc.Callable[[], Any] | None = None, range=None, enum=None, coerce: collections.abc.Callable[[Any], Any] | None = None, hint='', link=False, propagate=False, group='', on_change: str | None = None, coalesce: bool = False, persist: bool = False, save_version: int | None = None, scalar: bool = False, clamp: bool = True)[source]

Descriptor for editor-visible, serializable node properties.

Declares a typed, validated property that the editor inspector can display and that the scene serializer persists automatically.

Args: default: Default value. Type is inferred from this (float, str, bool, Vec2, …). range: (lo, hi) clamp bounds for numeric values. A ranged property only accepts numbers (plus None when its default is None); anything else, bool included, raises TypeError at assignment. A non-builtin scalar such as np.float32 or a 0-d numeric array is converted to the builtin it stands for, so the stored type does not depend on where the value came from. enum: Allowed values list: the editor renders a dropdown. coerce: Callable applied to an incoming value before validation, so the property can accept a friendly spelling at the boundary and store the canonical one. Implied for an Enum default. hint: Tooltip / description shown in the inspector. link: When True, the resolved value is the sum of this node’s stored value and the parent’s value (numeric / vector types), or the parent’s value for other types. Useful for cumulative offsets that propagate down the tree. propagate: When True, bool/enum Properties inherit disabling values from parents. persist: When True, the value is included in SaveManager snapshots. save_version: Optional integer schema version recorded alongside the persisted value.

Value contract: A value with a unique correct interpretation is converted silently at the boundary; anything ambiguous or lossy is rejected there. This is the rule every typed subclass follows. "idle" for an Enum property, an int for a float one, a two-item sequence for a Vec2: each has exactly one right reading, so it is accepted and stored in canonical form. A string that names no member, or a three-item sequence for a Vec2, has none, so it raises at the assignment that wrote it rather than surfacing later as a wrong value. Numeric range is the one place a wrong number is neither converted nor rejected: with the default clamp=True an out-of-range number is folded to the nearest bound, and clamp=False lets it through untouched (see the range and clamp arguments). A non-number is still rejected: a range is a promise that the value is numeric. NaN is rejected under both settings, being no magnitude at all.

Serialization: simvx.core.scene_io walks node.get_properties() and emits any value that differs from default into the .py scene file. Loading passes those stored values as kwargs to the node constructor, which feeds them through __set__ for validation.

Storage: attr is the instance slot the value lives at, and it is subclass-facing: a Property subclass may read and write it, a consumer must not. A consumer that wants the value a snapshot should carry calls :meth:get_raw, and one that puts a value back calls :meth:set_raw. Those two honour :attr:storage_backed, which a derived Property (one that computes its value from other state and owns no slot) sets False on its own class. Reading prop.attr directly asks a question a derived Property has no answer to, and gets the declared default back instead of the live value.

Usage::

class Player(Node2D):
    speed = Property(5.0, range=(0, 20), hint="Movement speed")
    mode  = Property("walk", enum=["walk", "run", "fly"])
    state = Property(State.IDLE)                 # accepts "idle", stores State.IDLE
    angle = Property(0.0, coerce=math.radians)   # accepts degrees, stores radians

Subclassing: Two shapes, and they are not variations of one another.

A **validating** subclass keeps the storage and narrows what may go into
it, by overriding ``__set__``, checking, and delegating to
``super().__set__``. :class:`~simvx.core.properties.NodePath` and
:class:`~simvx.core.properties.Bitmask` are the shipped examples. Every
feature above still applies, because the base setter still runs.

A **derived** subclass owns no storage: its value is computed from other
state, and reading and writing it are the only way that state is
expressed. ``DirectionalLight3D.direction`` is the shipped example, over
the node's own rotation. The rules, which the base class cannot enforce:

1. **Override both accessors, and delegate to neither.**
   ``Property.__set__`` writes ``self.attr`` and ``Property.__get__``
   reads it, so a half-delegating derived Property writes to a slot
   nothing reads.
2. **``range``, ``enum``, ``coerce``, ``link`` and ``propagate`` do not
   apply**, because all five live inside the two accessors that were
   replaced. Validation goes in the overriding ``__set__``.
3. **Set ``storage_backed = False`` on the subclass.** That is what
   sends :meth:`get_raw` and :meth:`set_raw` through the accessors
   instead of at a slot that does not exist. Hot reload captures with
   ``get_raw``; without the declaration it reads the declared default
   off a missing slot and the restore writes that default over the live
   value.
4. **``default`` is still worth declaring**, because the scene emitter
   compares against it to decide whether the value is worth writing.

``__set_name__`` runs for a derived Property like any other and sets
both ``name`` and ``attr``. ``name`` is live: it appears in error
messages, in the inspector and in the emitted kwarg. ``attr`` is
vestigial, and ``storage_backed = False`` is the statement that nothing
should read it. Before that flag existed, the one derived Property in
the engine overrode ``__set_name__`` to point ``attr`` at the PUBLIC
name, so a raw read resolved through the descriptor by accident. That
worked, and it was a lie about what ``attr`` means; the flag replaced it.

Initialization

Create an editor-visible property descriptor.

Args: default: Default value for the property. Mutually exclusive with default_factory. Mutable containers (list, dict, set, bytearray) are rejected here because a single shared instance would alias across every owning object: pass default_factory instead. default_factory: Zero-arg callable invoked the first time each instance reads the property. The result is cached on the instance, matching functools.cached_property and dataclasses.field(default_factory=...) semantics. range: Optional (min, max) tuple for numeric values. By default (clamp=True) assignments are hard-clamped into [min, max], which doubles as input validation (audio safety, save-load sanitisation, [0,1] enforcement rely on this). With clamp=False the range is a soft hint: it sets the editor inspector field’s default bounds (which stretch to cover a value outside them), and assigned values pass through unclamped. Use clamp=False for physical magnitudes whose range is a suggestion, not a limit (e.g. particle speed in pixels/sec). enum: Optional list of allowed values. coerce: Optional single-argument callable that converts an incoming value into the form the property stores. It runs FIRST, before every other check: coerce, then scalar rejection, then range clamping, then enum membership, then the change-detection and on_change machinery. Everything downstream therefore sees the converted value. The declared default is converted once at class-definition time so reads of an unwritten property return the canonical form too (default_factory results are used verbatim). Passing an Enum type is the common case: coerce=BodyMode lets callers write mode="static" while the node stores BodyMode.STATIC, so identity comparisons downstream hold. A value the callable rejects raises ValueError naming the owning class, the property and the accepted values. A property whose default is an Enum member coerces to that member’s type without being asked, so every enum-valued property stores members rather than the bare strings or ints they wrap. Pass coerce explicitly to override that. hint: Description shown in the editor inspector. link: When True, child values are offset from the parent’s value. propagate: When True, bool/enum Settings inherit disabling values from parents. group: Inspector section name for grouping. Empty string = default “Properties” section. on_change: Name of a bound method to invoke on the owning instance after a successful value change (i.e. when the new value differs from the old). Hooks fired during __init__ are deferred until __init__ returns, then dispatched once each (deduplicated by (property, hook) pair) so the hook always sees a fully constructed object. After construction, hooks fire synchronously inside __set__ (unless coalesce=True: see below). coalesce: When True and on_change is set, post-init hook calls fire at most once per scene-tree frame. Multiple writes within one tick collapse to a single deferred call drained at the end of :meth:SceneTree.tick: useful for expensive handlers like HUD rasterisation that don’t care about intermediate values (Tower Defence: coins -= 1 repeated five times in one frame rasterised the HUD text five times). The owning object must be attached to a SceneTree (obj._tree set); for tree-less owners the flag is ignored and hooks fire synchronously. persist: When True, the value is included in SaveManager snapshots. save_version: Optional integer schema version recorded alongside the persisted value. clamp: When True (default) a numeric range hard-clamps every assignment into bounds (validation). When False the range is an editor-field hint only and out-of-range values are accepted verbatim, both on assignment and in the inspector. Has no effect when range is None. It does not relax the type: a ranged property converts its value to a builtin number and refuses NaN under either setting, because NaN is not a magnitude that a looser bound could accommodate. scalar: When True, reject array-like values (numpy ndarray with ndim >= 1, Vec2/Vec3, list, tuple, dict, set) at assignment with a clear TypeError. Plain Python numbers, numpy scalar types (np.float32, np.int64 etc.), and 0-d numpy arrays are accepted. Use for properties that semantically represent a single number (e.g. Camera2D.zoom) so a stray Vec2 doesn’t leak into downstream math and produce visual glitches.

__slots__

(‘default’, ‘default_factory’, ‘range’, ‘enum’, ‘hint’, ‘name’, ‘attr’, ‘link’, ‘_propagate’, ‘group…

coerce: collections.abc.Callable[[Any], Any]

None

storage_backed: bool

True

__new__(*args, coerce: collections.abc.Callable[[Any], Any] | None = None, **kwargs)[source]
__set_name__(owner, name)[source]
__get__(obj, objtype=None)[source]
__set__(obj, value)[source]
get_raw(obj)[source]

This Property’s stored value on obj, with no parent-link resolution.

The read a state snapshot wants: the node’s own value, not the value a link=True parent contributes to. Reading through __get__ instead would capture the resolved sum, and the matching restore would write it back as the child’s own, double-counting the parent on every reload.

A derived Property has no slot to read, so the only expression of its value is __get__, and that is what it gets. Its parent-link and propagate flags do not apply in the first place: both live in Property.__get__, which such a subclass replaces.

set_raw(obj, value) None[source]

Put value back, bypassing validation and on_change where that is possible.

A storage-backed Property is written straight at its slot: nothing is coerced, clamped or validated and no hook fires. A derived Property has no slot, so the write goes through __set__, which is the only way its value can be expressed at all, and that path DOES run validation and on_change. A caller that must not re-enter on_change (:func:restore_property) therefore refuses a derived Property rather than calling this and hoping.

try_decrement(obj, amount: float | int) bool[source]

Atomically decrement the property if it would not go negative.

Returns True and assigns current - amount when the result is non-negative; returns False and leaves the value untouched otherwise. Resolves the “currency / mana / HP gate” pattern that every game-with-currency reinvents:

Before::

if player.coins >= cost:
    player.coins -= cost
    purchase()

After::

if Player.coins.try_decrement(player, cost):
    purchase()

Reads-via-__get__ so parent-link offsets are respected; writes the raw stored value (no parent contribution) via __set__ so any on_change hook and range/enum validation still fire. amount must be numeric and non-negative.

__repr__()[source]
simvx.core.descriptors.declared_default(prop: simvx.core.descriptors.Property) Any[source]

The value prop reads back on an instance that has never written it.

Ask this rather than reading prop.default. A Property declared with default_factory leaves default at an unset sentinel, so a comparison against it calls every value of such a property non-default, and a snapshot that falls back to it stores the sentinel itself. Calling the factory answers the question the caller is actually asking, and matches what

Meth:

Property.__get__ materialises on first read.

On the default_factory branch the answer is a FRESH object per call, like the instance default it stands in for: callers there compare it and discard it, and must not hand it out as a value to keep. A plain default is the declared object itself, shared by every caller, exactly as prop.default would have handed it over.

simvx.core.descriptors.restore_property(obj: Any, name: str, value: Any) None[source]

Put obj’s Property name back to value without running __set__.

For the narrow case of undoing a write whose on_change hook could not carry it through – a physics knob the simulation refused, say – where the node must go back to reporting what it had. Assigning through the descriptor would fire the hook again, which would push the restored value straight back into whatever just rejected the new one, so the write goes to the Property’s own storage instead. It is not a general setter: nothing is coerced, clamped or validated, so pass a value that came out of the property in the first place.

Raises: TypeError: when name is a derived Property, one that owns no storage and can only be written through __set__. There is no way to put such a value back without re-entering the hook this function exists to avoid, so the caller is told rather than silently given the thing it asked not to happen.

exception simvx.core.descriptors.NodeNotFound[source]

Bases: KeyError

Raised when a node lookup by name/path finds no match.

Subclasses :class:KeyError so existing except KeyError handlers keep working, while giving a domain-specific type to catch (and a clearer name in tracebacks) for node.node_at(...) / node["name"] misses.

Initialization

Initialize self. See help(type(self)) for accurate signature.

class __cause__
class __context__
__delattr__()
__dir__()
__eq__()
__format__()
__ge__()
__getattribute__()
__getstate__()
__gt__()
__hash__()
__le__()
__lt__()
__ne__()
__new__()
__reduce__()
__reduce_ex__()
__repr__()
__setattr__()
__setstate__()
__sizeof__()
__str__()
__subclasshook__()
class __suppress_context__
class __traceback__
add_note()
class args
with_traceback()
class simvx.core.descriptors.Children[source]

List-like container with named child access.

node.children[0] # by index node.children[‘Camera’] # by name string for c in node.children: # iteration len(node.children) # count

Initialization

__slots__

(‘_list’, ‘_names’, ‘_snapshot’, ‘_dirty’)

safe_iter() list[source]

Return a snapshot safe for iteration during mutation. Avoids per-frame copy when children are unchanged.

move_first(node) None[source]

Move node to index 0 (drawn first, hit-tested last). No-op if absent.

move_last(node) None[source]

Move node to the end (drawn last, hit-tested first). No-op if absent.

__getitem__(key)[source]
__iter__()[source]
__len__()[source]
__contains__(item)[source]
__bool__()[source]
__repr__()[source]