Source code for mini_arcade_core.scenes.registry
"""
SimScene registry for mini arcade core.
Allows registering and creating scenes by string IDs.
"""
from __future__ import annotations
import importlib
import pkgutil
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Protocol
from mini_arcade_core.runtime.context import RuntimeContext
from mini_arcade_core.utils import logger
from .autoreg import snapshot
if TYPE_CHECKING:
from mini_arcade_core.scenes.sim_scene import SimScene
[docs]
class SceneFactory(Protocol):
"""
Protocol for scene factory callables.
"""
[docs]
def __call__(self, context: RuntimeContext) -> "SimScene": ...
[docs]
@dataclass
class SceneRegistry:
"""
Registry for scene factories, allowing registration and creation of scenes by string IDs.
"""
_factories: Dict[str, SceneFactory]
@property
def listed_scene_ids(self) -> list[str]:
"""
Get a list of all registered scene IDs.
:return: A list of registered scene IDs.
:rtype: list[str]
"""
return list(self._factories.keys())
[docs]
def register(self, scene_id: str, factory: SceneFactory):
"""
Register a scene factory under a given scene ID.
:param scene_id: The string ID for the scene.
:type scene_id: str
:param factory: A callable that creates a SimScene instance.
:type factory: SceneFactory
"""
self._factories[scene_id] = factory
[docs]
def register_cls(self, scene_id: str, scene_cls: type["SimScene"]):
"""
Register a SimScene class under a given scene ID.
:param scene_id: The string ID for the scene.
:type scene_id: str
:param scene_cls: The SimScene class to register.
:type scene_cls: type["SimScene"]
"""
def return_factory(context: RuntimeContext) -> "SimScene":
return scene_cls(context)
self.register(scene_id, return_factory)
[docs]
def create(
self, scene_id: str, context: RuntimeContext
) -> "SimScene" | None:
"""
Create a scene instance using the registered factory for the given scene ID.
:param scene_id: The string ID of the scene to create.
:type scene_id: str
:param game: The Game instance to pass to the scene factory.
:type game: Game
:return: A new SimScene instance.
:rtype: SimScene
:raises KeyError: If no factory is registered for the given scene ID.
"""
try:
scene_factory = self._factories.get(scene_id, None)
if scene_factory is None:
logger.warning(f"No factory found for scene_id={scene_id!r}")
return None
return self._factories[scene_id](context)
except KeyError as e:
raise KeyError(f"Unknown scene_id={scene_id!r}") from e
[docs]
def load_catalog(self, catalog: Dict[str, type["SimScene"]]):
"""
Load a catalog of SimScene classes into the registry.
:param catalog: A dictionary mapping scene IDs to SimScene classes.
:type catalog: Dict[str, type["SimScene"]]
"""
for scene_id, cls in catalog.items():
self.register_cls(scene_id, cls)
[docs]
def discover(self, *packages: str) -> "SceneRegistry":
"""
Import all modules in a package so @scene decorators run.
:param packages: The package names to scan for scene modules.
:type packages: str
:return: The SceneRegistry instance (for chaining).
:rtype: SceneRegistry
"""
for package in packages:
pkg = importlib.import_module(package)
if not hasattr(pkg, "__path__"):
continue
for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
importlib.import_module(mod.name)
self.load_catalog(snapshot())
return self