"""Atomic executor registry and dispatch for the session runtime. Executors register themselves with :func:`atomic_executor` from their own modules; ``cdsl_engine.executors`` imports every executor module exactly once to build the registry. Adding a new atomic operation therefore means adding a decorated function in a family module -- the registry itself never changes. ``registry`` deliberately imports no executor module, so pattern executors can re-enter :func:`execute_node` for replay without an import cycle. """ from __future__ import annotations from typing import Any, Callable, Protocol from .session import ExecutionSession from .topology import CapabilityResult, FeaturePlanNode, FeatureResult class AtomicExecutor(Protocol): atomic_id: str def preflight(self, node: FeaturePlanNode, session: ExecutionSession) -> CapabilityResult: ... def execute(self, node: FeaturePlanNode, session: ExecutionSession) -> FeatureResult: ... #: The complete capability contract of this runtime. Every registered #: executor must name an id from this set, so a mistyped registration fails #: at import time instead of surfacing as an unknown-atomic blocker later. ALL_ATOMIC_IDS = frozenset({ "extrude_add_blind", "extrude_add_blind_with_hole", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "extrude_surface", "extrude_cut_through", "extrude_from_face", "loft_add", "loft_add_with_cap_face", "sweep_add", "revolve_add", "revolve_cut", "revolve_surface", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "box_add", "cylinder_add", "reference_plane", "reference_axis", "hole_wizard", "fillet", "chamfer", "shell", "pattern_linear", "pattern_mirror", "pattern_circular", "boolean_bodies", "transform_bodies", "delete_bodies", "thread_add", "thread_cut", "bend_add", "gear_add", "rack_add", }) #: ``atomic_id -> executor``. Populated by ``cdsl_engine.executors``. EXECUTORS: dict[str, "ExecutorFunction"] = {} ExecutorFunction = Callable[[FeaturePlanNode, ExecutionSession, dict[str, Any] | None], FeatureResult] def atomic_executor(*atomic_ids: str) -> Callable[["ExecutorFunction"], "ExecutorFunction"]: """Register one executor function for one or more atomic ids. Registration validates against ``ALL_ATOMIC_IDS`` and rejects duplicates, keeping the registry consistent with the declared capability contract. """ unknown = [atomic_id for atomic_id in atomic_ids if atomic_id not in ALL_ATOMIC_IDS] if unknown: raise ValueError(f"cannot register executors for unknown atomic ids: {unknown}") def decorator(func: "ExecutorFunction") -> "ExecutorFunction": for atomic_id in atomic_ids: if atomic_id in EXECUTORS: raise ValueError(f"duplicate executor registration for {atomic_id!r}") EXECUTORS[atomic_id] = func return func return decorator def execute_node( node: FeaturePlanNode, session: ExecutionSession, sketch_override: dict[str, Any] | None = None, ) -> FeatureResult: """Dispatch one plan node through its registered executor.""" executor = EXECUTORS.get(node.atomic_id) if executor is None: raise ValueError(f"No executor registered for {node.atomic_id!r}") previous_feature_id = session.active_feature_id session.active_feature_id = node.feature_id try: return executor(node, session, sketch_override) finally: session.active_feature_id = previous_feature_id