Source code for simvx.core.graphics._slots
"""The attribute names a ``__slots__`` class stores, for the copy protocols.
:class:`~simvx.core.graphics.texture.Texture` and
:class:`~simvx.core.graphics.material.Material` both hand-roll ``__copy__``,
``__deepcopy__`` and the pickle pair, and both need the same list. One helper,
so a correction lands in one place.
"""
from __future__ import annotations
__all__ = ["slot_names"]
#: Entries a class may put in ``__slots__`` that are machinery rather than data.
#: The list is the one ``copyreg._slotnames`` drops: ``__dict__`` names the dict
#: itself (carrying it through ``setattr`` would share one dict between an
#: original and its copy) and ``__weakref__`` names the weak-reference slot.
_NOT_DATA = frozenset({"__dict__", "__weakref__"})
[docs]
def slot_names(cls: type) -> tuple[str, ...]:
"""Every attribute name *cls* stores in a slot, base classes included.
The whole MRO is walked rather than one class's ``__slots__``, so a
subclass that adds slots of its own keeps them: backends subclass to add
GPU state. Names are returned most-derived first and each appears once.
Two spellings that a naive read of ``__slots__`` gets wrong are handled
here. ``__slots__ = "x"`` is a legal single-name form that iterates as
characters, and a private name is mangled by the class body that declared
it, so ``__slots__ = ("__x",)`` in ``Foo`` creates a slot called ``_Foo__x``.
"""
names: list[str] = []
seen: set[str] = set()
for klass in cls.__mro__:
declared = klass.__dict__.get("__slots__", ())
if isinstance(declared, str):
declared = (declared,)
for name in declared:
if name in _NOT_DATA:
continue
if name.startswith("__") and not name.endswith("__"):
owner = klass.__name__.lstrip("_")
if owner:
name = f"_{owner}{name}"
if name not in seen:
seen.add(name)
names.append(name)
return tuple(names)