mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-20 22:12:24 +08:00
added efro.util.valuedispatchmethod
This commit is contained in:
parent
18c501ee22
commit
09b8526f0d
3
.idea/dictionaries/ericf.xml
generated
3
.idea/dictionaries/ericf.xml
generated
@ -44,6 +44,7 @@
|
||||
<w>actorclass</w>
|
||||
<w>adbcfaca</w>
|
||||
<w>adbpath</w>
|
||||
<w>addcall</w>
|
||||
<w>addgame</w>
|
||||
<w>addlevel</w>
|
||||
<w>addr</w>
|
||||
@ -2191,6 +2192,7 @@
|
||||
<w>trsock</w>
|
||||
<w>tscale</w>
|
||||
<w>tscl</w>
|
||||
<w>tself</w>
|
||||
<w>tspc</w>
|
||||
<w>tstr</w>
|
||||
<w>turtledemo</w>
|
||||
@ -2275,6 +2277,7 @@
|
||||
<w>valnew</w>
|
||||
<w>vals</w>
|
||||
<w>valuedispatch</w>
|
||||
<w>valuedispatchmethod</w>
|
||||
<w>valueerror</w>
|
||||
<w>valuetext</w>
|
||||
<w>valuetype</w>
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
<w>aclass</w>
|
||||
<w>aclass's</w>
|
||||
<w>activityplayer</w>
|
||||
<w>addcall</w>
|
||||
<w>addrs</w>
|
||||
<w>adjoint</w>
|
||||
<w>adminset</w>
|
||||
@ -905,6 +906,7 @@
|
||||
<w>trilinear</w>
|
||||
<w>trimesh</w>
|
||||
<w>trimeshes</w>
|
||||
<w>tself</w>
|
||||
<w>tval</w>
|
||||
<w>tvos</w>
|
||||
<w>tweakage</w>
|
||||
@ -940,6 +942,7 @@
|
||||
<w>valobj</w>
|
||||
<w>vals</w>
|
||||
<w>valtab</w>
|
||||
<w>valuedispatchmethod</w>
|
||||
<w>valuedouble</w>
|
||||
<w>valueint</w>
|
||||
<w>valuestring</w>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2021-01-14 for Ballistica version 1.5.30 build 20267</em></h4>
|
||||
<h4><em>last updated on 2021-01-15 for Ballistica version 1.5.30 build 20267</em></h4>
|
||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||
<hr>
|
||||
|
||||
@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
||||
T = TypeVar('T')
|
||||
TVAL = TypeVar('TVAL')
|
||||
TARG = TypeVar('TARG')
|
||||
TSELF = TypeVar('TSELF')
|
||||
TRET = TypeVar('TRET')
|
||||
TENUM = TypeVar('TENUM', bound=Enum)
|
||||
|
||||
@ -95,56 +96,6 @@ def data_size_str(bytecount: int) -> str:
|
||||
return f'{gbytecount:.0f} GB'
|
||||
|
||||
|
||||
class DispatchMethodWrapper(Generic[TARG, TRET]):
|
||||
"""Type-aware standin for the dispatch func returned by dispatchmethod."""
|
||||
|
||||
def __call__(self, arg: TARG) -> TRET:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def register(func: Callable[[Any, Any], TRET]) -> Callable:
|
||||
"""Register a new dispatch handler for this dispatch-method."""
|
||||
|
||||
registry: Dict[Any, Callable]
|
||||
|
||||
|
||||
# noinspection PyProtectedMember,PyTypeHints
|
||||
def dispatchmethod(
|
||||
func: Callable[[Any, TARG],
|
||||
TRET]) -> DispatchMethodWrapper[TARG, TRET]:
|
||||
"""A variation of functools.singledispatch for methods."""
|
||||
from functools import singledispatch, update_wrapper
|
||||
origwrapper: Any = singledispatch(func)
|
||||
|
||||
# Pull this out so hopefully origwrapper can die,
|
||||
# otherwise we reference origwrapper in our wrapper.
|
||||
dispatch = origwrapper.dispatch
|
||||
|
||||
# All we do here is recreate the end of functools.singledispatch
|
||||
# where it returns a wrapper except instead of the wrapper using the
|
||||
# first arg to the function ours uses the second (to skip 'self').
|
||||
# This was made against Python 3.7; we should probably check up on
|
||||
# this in later versions in case anything has changed.
|
||||
# (or hopefully they'll add this functionality to their version)
|
||||
# NOTE: sounds like we can use functools singledispatchmethod in 3.8
|
||||
def wrapper(*args: Any, **kw: Any) -> Any:
|
||||
if not args or len(args) < 2:
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'2 positional arguments')
|
||||
|
||||
return dispatch(args[1].__class__)(*args, **kw)
|
||||
|
||||
funcname = getattr(func, '__name__', 'dispatchmethod method')
|
||||
wrapper.register = origwrapper.register # type: ignore
|
||||
wrapper.dispatch = dispatch # type: ignore
|
||||
wrapper.registry = origwrapper.registry # type: ignore
|
||||
# pylint: disable=protected-access
|
||||
wrapper._clear_cache = origwrapper._clear_cache # type: ignore
|
||||
update_wrapper(wrapper, func)
|
||||
# pylint: enable=protected-access
|
||||
return cast(DispatchMethodWrapper, wrapper)
|
||||
|
||||
|
||||
class DirtyBit:
|
||||
"""Manages whether a thing is dirty and regulates attempts to clean it.
|
||||
|
||||
@ -242,9 +193,66 @@ class DirtyBit:
|
||||
return False
|
||||
|
||||
|
||||
class DispatchMethodWrapper(Generic[TARG, TRET]):
|
||||
"""Type-aware standin for the dispatch func returned by dispatchmethod."""
|
||||
|
||||
def __call__(self, arg: TARG) -> TRET:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def register(func: Callable[[Any, Any], TRET]) -> Callable:
|
||||
"""Register a new dispatch handler for this dispatch-method."""
|
||||
|
||||
registry: Dict[Any, Callable]
|
||||
|
||||
|
||||
# noinspection PyProtectedMember,PyTypeHints
|
||||
def dispatchmethod(
|
||||
func: Callable[[Any, TARG],
|
||||
TRET]) -> DispatchMethodWrapper[TARG, TRET]:
|
||||
"""A variation of functools.singledispatch for methods.
|
||||
|
||||
Note: as of Python 3.9 there is now functools.singledispatchmethod,
|
||||
but it currently (as of Jan 2021) is not type-aware (at least in mypy),
|
||||
which gives us a reason to keep this one around for now.
|
||||
"""
|
||||
from functools import singledispatch, update_wrapper
|
||||
origwrapper: Any = singledispatch(func)
|
||||
|
||||
# Pull this out so hopefully origwrapper can die,
|
||||
# otherwise we reference origwrapper in our wrapper.
|
||||
dispatch = origwrapper.dispatch
|
||||
|
||||
# All we do here is recreate the end of functools.singledispatch
|
||||
# where it returns a wrapper except instead of the wrapper using the
|
||||
# first arg to the function ours uses the second (to skip 'self').
|
||||
# This was made against Python 3.7; we should probably check up on
|
||||
# this in later versions in case anything has changed.
|
||||
# (or hopefully they'll add this functionality to their version)
|
||||
# NOTE: sounds like we can use functools singledispatchmethod in 3.8
|
||||
def wrapper(*args: Any, **kw: Any) -> Any:
|
||||
if not args or len(args) < 2:
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'2 positional arguments')
|
||||
|
||||
return dispatch(args[1].__class__)(*args, **kw)
|
||||
|
||||
funcname = getattr(func, '__name__', 'dispatchmethod method')
|
||||
wrapper.register = origwrapper.register # type: ignore
|
||||
wrapper.dispatch = dispatch # type: ignore
|
||||
wrapper.registry = origwrapper.registry # type: ignore
|
||||
# pylint: disable=protected-access
|
||||
wrapper._clear_cache = origwrapper._clear_cache # type: ignore
|
||||
update_wrapper(wrapper, func)
|
||||
# pylint: enable=protected-access
|
||||
return cast(DispatchMethodWrapper, wrapper)
|
||||
|
||||
|
||||
def valuedispatch(call: Callable[[TVAL], TRET]) -> ValueDispatcher[TVAL, TRET]:
|
||||
"""Decorator for functions to allow dispatching based on a value.
|
||||
|
||||
This differs from functools.singledispatch in that it dispatches based
|
||||
on the value of an argument, not based on its type.
|
||||
The 'register' method of a value-dispatch function can be used
|
||||
to assign new functions to handle particular values.
|
||||
Unhandled values wind up in the original dispatch function."""
|
||||
@ -307,6 +315,61 @@ class ValueDispatcher1Arg(Generic[TVAL, TARG, TRET]):
|
||||
return partial(self._add_handler, value)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class ValueDispatcherMethod(Generic[TVAL, TRET]):
|
||||
"""Used by the valuedispatchmethod decorator."""
|
||||
|
||||
def __call__(self, value: TVAL) -> TRET:
|
||||
...
|
||||
|
||||
def register(self,
|
||||
value: TVAL) -> Callable[[Callable[[TSELF], TRET]], None]:
|
||||
"""Add a handler to the dispatcher."""
|
||||
...
|
||||
|
||||
|
||||
def valuedispatchmethod(
|
||||
call: Callable[[TSELF, TVAL],
|
||||
TRET]) -> ValueDispatcherMethod[TVAL, TRET]:
|
||||
"""Like valuedispatch but works with methods instead of functions."""
|
||||
|
||||
# NOTE: It seems that to wrap a method with a decorator and have self
|
||||
# dispatching do the right thing, we must return a function and not
|
||||
# an executable object. So for this version we store our data here
|
||||
# in the function call dict and simply return a call.
|
||||
|
||||
_base_call = call
|
||||
_handlers: Dict[TVAL, Callable[[TSELF], TRET]] = {}
|
||||
|
||||
def _add_handler(value: TVAL, addcall: Callable[[TSELF], TRET]) -> None:
|
||||
if value in _handlers:
|
||||
raise RuntimeError(f'Duplicate handlers added for {value}')
|
||||
_handlers[value] = addcall
|
||||
|
||||
def _register(value: TVAL) -> Callable[[Callable[[TSELF], TRET]], None]:
|
||||
from functools import partial
|
||||
return partial(_add_handler, value)
|
||||
|
||||
def _call_wrapper(self: TSELF, value: TVAL) -> TRET:
|
||||
handler = _handlers.get(value)
|
||||
if handler is not None:
|
||||
return handler(self)
|
||||
return _base_call(self, value)
|
||||
|
||||
# We still want to use our returned object to register handlers, but we're
|
||||
# actually just returning a function. So manually stuff the call onto it.
|
||||
setattr(_call_wrapper, 'register', _register)
|
||||
|
||||
# To the type checker's eyes we return a ValueDispatchMethod instance;
|
||||
# this lets it know about our register func and type-check its usage.
|
||||
# In reality we just return a raw function call (for reasons listed above).
|
||||
if TYPE_CHECKING: # pylint: disable=no-else-return
|
||||
return ValueDispatcherMethod[TVAL, TRET]()
|
||||
else:
|
||||
return _call_wrapper
|
||||
|
||||
|
||||
def make_hash(obj: Any) -> int:
|
||||
"""Makes a hash from a dictionary, list, tuple or set to any level,
|
||||
that contains only other hashable types (including any lists, tuples,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user