vh-bombsquad-modded-server-.../dist/ba_data/python/ba/_appcomponent.py
2024-06-06 19:50:58 +05:30

90 lines
3.2 KiB
Python

# Released under the MIT License. See LICENSE for details.
#
"""Provides the AppComponent class."""
from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, cast
import _ba
if TYPE_CHECKING:
from typing import Callable, Any
T = TypeVar('T', bound=type)
class AppComponentSubsystem:
"""Subsystem for wrangling AppComponents.
Category: **App Classes**
This subsystem acts as a registry for classes providing particular
functionality for the app, and allows plugins or other custom code to
easily override said functionality.
Use ba.app.components to get the single shared instance of this class.
The general idea with this setup is that a base-class is defined to
provide some functionality and then anyone wanting that functionality
uses the getclass() method with that base class to return the current
registered implementation. The user should not know or care whether
they are getting the base class itself or some other implementation.
Change-callbacks can also be requested for base classes which will
fire in a deferred manner when particular base-classes are overridden.
"""
def __init__(self) -> None:
self._implementations: dict[type, type] = {}
self._prev_implementations: dict[type, type] = {}
self._dirty_base_classes: set[type] = set()
self._change_callbacks: dict[type, list[Callable[[Any], None]]] = {}
def setclass(self, baseclass: type, implementation: type) -> None:
"""Set the class providing an implementation of some base-class.
The provided implementation class must be a subclass of baseclass.
"""
# Currently limiting this to logic-thread use; can revisit if needed
# (would need to guard access to our implementations dict).
assert _ba.in_logic_thread()
if not issubclass(implementation, baseclass):
raise TypeError(
f'Implementation {implementation}'
f' is not a subclass of baseclass {baseclass}.'
)
self._implementations[baseclass] = implementation
# If we're the first thing getting dirtied, set up a callback to
# clean everything. And add ourself to the dirty list regardless.
if not self._dirty_base_classes:
_ba.pushcall(self._run_change_callbacks)
self._dirty_base_classes.add(baseclass)
def getclass(self, baseclass: T) -> T:
"""Given a base-class, return the currently set implementation class.
If no custom implementation has been set, the provided base-class
is returned.
"""
assert _ba.in_logic_thread()
del baseclass # Unused.
return cast(T, None)
def register_change_callback(
self, baseclass: T, callback: Callable[[T], None]
) -> None:
"""Register a callback to fire when a class implementation changes.
The callback will be scheduled to run in the logic thread event
loop. Note that any further setclass calls before the callback
runs will not result in additional callbacks.
"""
assert _ba.in_logic_thread()
self._change_callbacks.setdefault(baseclass, []).append(callback)
def _run_change_callbacks(self) -> None:
pass