simvx.core.descriptors¶
Descriptors, enums, and type aliases used throughout the engine.
Signal and Connection live in simvx.core.signals.
Module Contents¶
Classes¶
Cancellable handle returned by Node.start_coroutine(). |
|
Controls whether a node processes when the SceneTree is paused. |
|
Notifications dispatched to nodes during lifecycle and property changes. |
|
Descriptor for editor-visible, serializable node properties. |
|
List-like container with named child access. |
Functions¶
The value |
|
Put |
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 viadt = yield. Coroutines that yield without binding the value still work: the sent dt is simply discarded.Initialization
- __slots__¶
(‘_gen’, ‘_cancelled’, ‘_primed’)
- class simvx.core.descriptors.UpdateMode[source]¶
Bases:
enum.IntEnumControls 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, onlyALWAYSandPAUSED_ONLYnodes run theirprocess/physics_process.Because INHERIT walks ancestors, an
ALWAYSmode 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.IntEnumNotifications 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 (plusNonewhen its default isNone); anything else,boolincluded, raisesTypeErrorat assignment. A non-builtin scalar such asnp.float32or 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 anEnumdefault. hint: Tooltip / description shown in the inspector. link: WhenTrue, 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 inSaveManagersnapshots. 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 anEnumproperty, anintfor afloatone, a two-item sequence for aVec2: 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 aVec2, has none, so it raises at the assignment that wrote it rather than surfacing later as a wrong value. Numericrangeis the one place a wrong number is neither converted nor rejected: with the defaultclamp=Truean out-of-range number is folded to the nearest bound, andclamp=Falselets it through untouched (see therangeandclamparguments). 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_iowalksnode.get_properties()and emits any value that differs from default into the.pyscene file. Loading passes those stored values as kwargs to the node constructor, which feeds them through__set__for validation.Storage:
attris 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) setsFalseon its own class. Readingprop.attrdirectly 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 radiansSubclassing: 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: passdefault_factoryinstead. default_factory: Zero-arg callable invoked the first time each instance reads the property. The result is cached on the instance, matchingfunctools.cached_propertyanddataclasses.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). Withclamp=Falsethe 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. Useclamp=Falsefor physical magnitudes whose range is a suggestion, not a limit (e.g. particlespeedin 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, thenscalarrejection, thenrangeclamping, thenenummembership, then the change-detection andon_changemachinery. Everything downstream therefore sees the converted value. The declareddefaultis converted once at class-definition time so reads of an unwritten property return the canonical form too (default_factoryresults are used verbatim). Passing anEnumtype is the common case:coerce=BodyModelets callers writemode="static"while the node storesBodyMode.STATIC, so identity comparisons downstream hold. A value the callable rejects raisesValueErrornaming the owning class, the property and the accepted values. A property whosedefaultis anEnummember 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. Passcoerceexplicitly 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__(unlesscoalesce=True: see below). coalesce: WhenTrueandon_changeis 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 -= 1repeated five times in one frame rasterised the HUD text five times). The owning object must be attached to a SceneTree (obj._treeset); for tree-less owners the flag is ignored and hooks fire synchronously. persist: When True, the value is included inSaveManagersnapshots. save_version: Optional integer schema version recorded alongside the persisted value. clamp: When True (default) a numericrangehard-clamps every assignment into bounds (validation). When False therangeis an editor-field hint only and out-of-range values are accepted verbatim, both on assignment and in the inspector. Has no effect whenrangeis 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 clearTypeError. Plain Python numbers, numpy scalar types (np.float32,np.int64etc.), and 0-d numpy arrays are accepted. Use for properties that semantically represent a single number (e.g.Camera2D.zoom) so a strayVec2doesn’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
- 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=Trueparent 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 inProperty.__get__, which such a subclass replaces.
- set_raw(obj, value) None[source]¶
Put
valueback, bypassing validation andon_changewhere 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 andon_change. A caller that must not re-enteron_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
Trueand assignscurrent - amountwhen the result is non-negative; returnsFalseand 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 anyon_changehook andrange/enumvalidation still fire.amountmust be numeric and non-negative.
- simvx.core.descriptors.declared_default(prop: simvx.core.descriptors.Property) Any[source]¶
The value
propreads back on an instance that has never written it.Ask this rather than reading
prop.default. A Property declared withdefault_factoryleavesdefaultat 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_factorybranch 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 asprop.defaultwould have handed it over.
- simvx.core.descriptors.restore_property(obj: Any, name: str, value: Any) None[source]¶
Put
obj’s Propertynameback tovaluewithout running__set__.For the narrow case of undoing a write whose
on_changehook 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
nameis 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:
KeyErrorRaised when a node lookup by name/path finds no match.
Subclasses :class:
KeyErrorso existingexcept KeyErrorhandlers keep working, while giving a domain-specific type to catch (and a clearer name in tracebacks) fornode.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.