vh-bombsquad-modded-server-.../dist/ba_data/python/efro/entity/_base.py
2024-02-26 00:17:10 +05:30

133 lines
4.9 KiB
Python

# Released under the MIT License. See LICENSE for details.
#
"""Base classes for the entity system."""
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
from efro.util import enum_by_value
if TYPE_CHECKING:
from typing import Any, Type
def dict_key_to_raw(key: Any, keytype: Type) -> Any:
"""Given a key value from the world, filter to stored key."""
if not isinstance(key, keytype):
raise TypeError(
f'Invalid key type; expected {keytype}, got {type(key)}.')
if issubclass(keytype, Enum):
val = key.value
# We convert int enums to string since that is what firestore supports.
if isinstance(val, int):
val = str(val)
return val
return key
def dict_key_from_raw(key: Any, keytype: Type) -> Any:
"""Given internal key, filter to world visible type."""
if issubclass(keytype, Enum):
# We store all enum keys as strings; if the enum uses
# int keys, convert back.
for enumval in keytype:
if isinstance(enumval.value, int):
return enum_by_value(keytype, int(key))
break
return enum_by_value(keytype, key)
return key
class DataHandler:
"""Base class for anything that can wrangle entity data.
This contains common functionality shared by Fields and Values.
"""
def get_default_data(self) -> Any:
"""Return the default internal data value for this object.
This will be inserted when initing nonexistent entity data.
"""
raise RuntimeError(f'get_default_data() unimplemented for {self}')
def filter_input(self, data: Any, error: bool) -> Any:
"""Given arbitrary input data, return valid internal data.
If error is True, exceptions should be thrown for any non-trivial
mismatch (more than just int vs float/etc.). Otherwise the invalid
data should be replaced with valid defaults and the problem noted
via the logging module.
The passed-in data can be modified in-place or returned as-is, or
completely new data can be returned. Compound types are responsible
for setting defaults and/or calling this recursively for their
children. Data that is not used by the field (such as orphaned values
in a dict field) can be left alone.
Supported types for internal data are:
- anything that works with json (lists, dicts, bools, floats, ints,
strings, None) - no tuples!
- datetime.datetime objects
"""
del error # unused
return data
def filter_output(self, data: Any) -> Any:
"""Given valid internal data, return user-facing data.
Note that entity data is expected to be filtered to correctness on
input, so if internal and extra entity data are the same type
Value types such as Vec3 may store data internally as simple float
tuples but return Vec3 objects to the user/etc. this is the mechanism
by which they do so.
"""
return data
def prune_data(self, data: Any) -> bool:
"""Prune internal data to strip out default values/etc.
Should return a bool indicating whether root data itself can be pruned.
The object is responsible for pruning any sub-fields before returning.
"""
class BaseField(DataHandler):
"""Base class for all field types."""
def __init__(self, d_key: str = None) -> None:
# Key for this field's data in parent dict/list (when applicable;
# some fields such as the child field under a list field represent
# more than a single field entry so this is unused)
self.d_key = d_key
# IMPORTANT: this method should only be overridden in the eyes of the
# type-checker (to specify exact return types). Subclasses should instead
# override get_with_data() for doing the actual work, since that method
# may sometimes be called explicitly instead of through __get__
def __get__(self, obj: Any, type_in: Any = None) -> Any:
if obj is None:
# when called on the type, we return the field
return self
return self.get_with_data(obj.d_data)
# IMPORTANT: same deal as __get__() (see note above)
def __set__(self, obj: Any, value: Any) -> None:
assert obj is not None
self.set_with_data(obj.d_data, value, error=True)
def get_with_data(self, data: Any) -> Any:
"""Get the field value given an explicit data source."""
assert self.d_key is not None
return self.filter_output(data[self.d_key])
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
"""Set the field value given an explicit data target.
If error is True, exceptions should be thrown for invalid data;
otherwise the problem should be logged but corrected.
"""
assert self.d_key is not None
data[self.d_key] = self.filter_input(value, error=error)