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

131 lines
4.8 KiB
Python

# Released under the MIT License. See LICENSE for details.
#
"""Misc utility functionality related to the entity system."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Union, Tuple, List
from efro.entity._value import CompoundValue
from efro.entity._support import BoundCompoundValue
def diff_compound_values(
obj1: Union[BoundCompoundValue, CompoundValue],
obj2: Union[BoundCompoundValue, CompoundValue]) -> str:
"""Generate a string showing differences between two compound values.
Both must be associated with data and have the same set of fields.
"""
# Ensure fields match and both are attached to data...
value1, data1 = get_compound_value_and_data(obj1)
if data1 is None:
raise ValueError(f'Invalid unbound compound value: {obj1}')
value2, data2 = get_compound_value_and_data(obj2)
if data2 is None:
raise ValueError(f'Invalid unbound compound value: {obj2}')
if not have_matching_fields(value1, value2):
raise ValueError(
f"Can't diff objs with non-matching fields: {value1} and {value2}")
# Ok; let 'er rip...
diff = _diff(obj1, obj2, 2)
return ' <no differences>' if diff == '' else diff
class CompoundValueDiff:
"""Wraps diff_compound_values() in an object for efficiency.
It is preferable to pass this to logging calls instead of the
final diff string since the diff will never be generated if
the associated logging level is not being emitted.
"""
def __init__(self, obj1: Union[BoundCompoundValue, CompoundValue],
obj2: Union[BoundCompoundValue, CompoundValue]):
self._obj1 = obj1
self._obj2 = obj2
def __repr__(self) -> str:
return diff_compound_values(self._obj1, self._obj2)
def _diff(obj1: Union[BoundCompoundValue, CompoundValue],
obj2: Union[BoundCompoundValue, CompoundValue], indent: int) -> str:
from efro.entity._support import BoundCompoundValue
bits: List[str] = []
indentstr = ' ' * indent
vobj1, _data1 = get_compound_value_and_data(obj1)
fields = sorted(vobj1.get_fields().keys())
for field in fields:
val1 = getattr(obj1, field)
val2 = getattr(obj2, field)
# for nested compounds, dive in and do nice piecewise compares
if isinstance(val1, BoundCompoundValue):
assert isinstance(val2, BoundCompoundValue)
diff = _diff(val1, val2, indent + 2)
if diff != '':
bits.append(f'{indentstr}{field}:')
bits.append(diff)
# for all else just do a single line
# (perhaps we could improve on this for other complex types)
else:
if val1 != val2:
bits.append(f'{indentstr}{field}: {val1} -> {val2}')
return '\n'.join(bits)
def have_matching_fields(val1: CompoundValue, val2: CompoundValue) -> bool:
"""Return whether two compound-values have matching sets of fields.
Note this just refers to the field configuration; not data.
"""
# Quick-out: matching types will always have identical fields.
if type(val1) is type(val2):
return True
# Otherwise do a full comparison.
return val1.get_fields() == val2.get_fields()
def get_compound_value_and_data(
obj: Union[BoundCompoundValue,
CompoundValue]) -> Tuple[CompoundValue, Any]:
"""Return value and data for bound or unbound compound values."""
# pylint: disable=cyclic-import
from efro.entity._support import BoundCompoundValue
from efro.entity._value import CompoundValue
if isinstance(obj, BoundCompoundValue):
value = obj.d_value
data = obj.d_data
elif isinstance(obj, CompoundValue):
value = obj
data = getattr(obj, 'd_data', None) # may not exist
else:
raise TypeError(
f'Expected a BoundCompoundValue or CompoundValue; got {type(obj)}')
return value, data
def compound_eq(obj1: Union[BoundCompoundValue, CompoundValue],
obj2: Union[BoundCompoundValue, CompoundValue]) -> Any:
"""Compare two compound value/bound-value objects for equality."""
# Criteria for comparison: both need to be a compound value
# and both must have data (which implies they are either a entity
# or bound to a subfield in an entity).
value1, data1 = get_compound_value_and_data(obj1)
if data1 is None:
return NotImplemented
value2, data2 = get_compound_value_and_data(obj2)
if data2 is None:
return NotImplemented
# Ok we can compare them. To consider them equal we look for
# matching sets of fields and matching data. Note that there
# could be unbound data causing inequality despite their field
# values all matching; not sure if that's what we want.
return have_matching_fields(value1, value2) and data1 == data2