mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-10-16 12:02:51 +00:00
90 lines
3.2 KiB
Python
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
|