mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-27 09:23:12 +08:00
latest entity work
This commit is contained in:
parent
75e289e234
commit
355d975803
4
.idea/dictionaries/ericf.xml
generated
4
.idea/dictionaries/ericf.xml
generated
@ -219,6 +219,7 @@
|
||||
<w>ccompiler</w>
|
||||
<w>cdrk</w>
|
||||
<w>cdull</w>
|
||||
<w>cdval</w>
|
||||
<w>centeuro</w>
|
||||
<w>centiseconds</w>
|
||||
<w>cfconfig</w>
|
||||
@ -287,6 +288,7 @@
|
||||
<w>compat</w>
|
||||
<w>compileall</w>
|
||||
<w>compilelocations</w>
|
||||
<w>compounddict</w>
|
||||
<w>compoundlist</w>
|
||||
<w>configerror</w>
|
||||
<w>confighash</w>
|
||||
@ -518,6 +520,7 @@
|
||||
<w>farthestpt</w>
|
||||
<w>fbase</w>
|
||||
<w>fclose</w>
|
||||
<w>fcmd</w>
|
||||
<w>fcntlmodule</w>
|
||||
<w>fcode</w>
|
||||
<w>fcontents</w>
|
||||
@ -591,6 +594,7 @@
|
||||
<w>fpathrel</w>
|
||||
<w>fpathshort</w>
|
||||
<w>fprint</w>
|
||||
<w>fproject</w>
|
||||
<w>fpsc</w>
|
||||
<w>framerate</w>
|
||||
<w>freefly</w>
|
||||
|
||||
@ -30,6 +30,7 @@ from bafoundation.entity._support import (BaseField, BoundCompoundValue,
|
||||
BoundListField, BoundDictField,
|
||||
BoundCompoundListField,
|
||||
BoundCompoundDictField)
|
||||
from bafoundation.entity.util import have_matching_fields
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Dict, Type, List, Any
|
||||
@ -203,6 +204,7 @@ class ListField(BaseField, Generic[T]):
|
||||
# When accessed on a FieldInspector we return a sub-field FieldInspector.
|
||||
# When accessed on an instance we return a BoundListField.
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
if TYPE_CHECKING:
|
||||
|
||||
# Access via type gives our field; via an instance gives a bound field.
|
||||
@ -277,6 +279,7 @@ class DictField(BaseField, Generic[TK, T]):
|
||||
# change the dict, but we can prune completely if empty (and allowed)
|
||||
return not data and not self._store_default
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
if TYPE_CHECKING:
|
||||
|
||||
# Return our field if accessed via type and bound-dict-field
|
||||
@ -354,6 +357,7 @@ class CompoundListField(BaseField, Generic[TC]):
|
||||
# We can also optionally prune the whole list if empty and allowed.
|
||||
return not data and not self._store_default
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@overload
|
||||
@ -370,10 +374,10 @@ class CompoundListField(BaseField, Generic[TC]):
|
||||
...
|
||||
|
||||
# Note:
|
||||
# When setting the list, we tell the type-checker that we accept
|
||||
# When setting the list, we tell the type-checker that we also accept
|
||||
# a raw list of CompoundValue objects, but at runtime we actually
|
||||
# always deal with BoundCompoundValue objects (see note in
|
||||
# BoundCompoundListField)
|
||||
# BoundCompoundListField for why we accept CompoundValue objs)
|
||||
@overload
|
||||
def __set__(self, obj: Any, value: List[TC]) -> None:
|
||||
...
|
||||
@ -392,7 +396,7 @@ class CompoundListField(BaseField, Generic[TC]):
|
||||
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
||||
|
||||
# If we were passed a BoundCompoundListField,
|
||||
# simply convert it to a list of BoundCompoundValue objects which
|
||||
# simply convert it to a flat list of BoundCompoundValue objects which
|
||||
# is what we work with natively here.
|
||||
if isinstance(value, BoundCompoundListField):
|
||||
value = list(value)
|
||||
@ -406,10 +410,28 @@ class CompoundListField(BaseField, Generic[TC]):
|
||||
# be sure the underlying data will line up; for example two
|
||||
# CompoundListFields with different child_field values should not
|
||||
# be inter-assignable.
|
||||
if (not all(isinstance(i, BoundCompoundValue) for i in value)
|
||||
or not all(i.d_value is self.d_value for i in value)):
|
||||
if not all(isinstance(i, BoundCompoundValue) for i in value):
|
||||
raise ValueError('CompoundListField assignment must be a '
|
||||
'list containing only its existing children.')
|
||||
'list containing only BoundCompoundValue objs.')
|
||||
|
||||
# Make sure the data all has the same CompoundValue type and
|
||||
# compare that type against ours once to make sure its fields match.
|
||||
# (this will not allow passing CompoundValues from multiple sources
|
||||
# but I don't know if that would ever come up..)
|
||||
for i, val in enumerate(value):
|
||||
if i == 0:
|
||||
# Do the full field comparison on the first value only..
|
||||
if not have_matching_fields(val.d_value, self.d_value):
|
||||
raise ValueError(
|
||||
'CompoundListField assignment must be a '
|
||||
'list containing matching CompoundValues.')
|
||||
else:
|
||||
# For all remaining values, just ensure they match the first.
|
||||
if val.d_value is not value[0].d_value:
|
||||
raise ValueError(
|
||||
'CompoundListField assignment cannot contain '
|
||||
'multiple CompoundValue types as sources.')
|
||||
|
||||
data[self.d_key] = self.filter_input([i.d_data for i in value],
|
||||
error=error)
|
||||
|
||||
@ -468,6 +490,7 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
||||
|
||||
# ONLY overriding these in type-checker land to clarify types.
|
||||
# (see note in BaseField)
|
||||
# noinspection DuplicatedCode
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@overload
|
||||
@ -485,14 +508,35 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
||||
def __get__(self, obj: Any, cls: Any = None) -> Any:
|
||||
...
|
||||
|
||||
# Note:
|
||||
# When setting the dict, we tell the type-checker that we also accept
|
||||
# a raw dict of CompoundValue objects, but at runtime we actually
|
||||
# always deal with BoundCompoundValue objects (see note in
|
||||
# BoundCompoundDictField for why we accept CompoundValue objs)
|
||||
@overload
|
||||
def __set__(self, obj: Any, value: Dict[TK, TC]) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __set__(self, obj: Any, value: BoundCompoundDictField[TK,
|
||||
TC]) -> None:
|
||||
...
|
||||
|
||||
def __set__(self, obj: Any, value: Any) -> None:
|
||||
...
|
||||
|
||||
def get_with_data(self, data: Any) -> Any:
|
||||
assert self.d_key in data
|
||||
return BoundCompoundDictField(self, data[self.d_key])
|
||||
|
||||
def set_with_data(self, data: Any, value: Any, error: bool) -> Any:
|
||||
|
||||
# If we were passed a BoundCompoundDictField,
|
||||
# simply convert it to a flat dict of BoundCompoundValue objects which
|
||||
# is what we work with natively here.
|
||||
if isinstance(value, BoundCompoundDictField):
|
||||
value = dict(value.items())
|
||||
|
||||
if not isinstance(value, dict):
|
||||
raise TypeError('CompoundDictField expected dict value on set.')
|
||||
|
||||
@ -501,10 +545,31 @@ class CompoundDictField(BaseField, Generic[TK, TC]):
|
||||
# be sure the underlying data will line up; for example two
|
||||
# CompoundListFields with different child_field values should not
|
||||
# be inter-assignable.
|
||||
if (not all(isinstance(i, self.d_keytype) for i in value.keys())
|
||||
or not all(
|
||||
isinstance(i, BoundCompoundValue) for i in value.values())
|
||||
or not all(i.d_value is self.d_value for i in value.values())):
|
||||
if (not all(isinstance(i, BoundCompoundValue)
|
||||
for i in value.values())):
|
||||
raise ValueError('CompoundDictField assignment must be a '
|
||||
'dict containing only its existing children.')
|
||||
data[self.d_key] = {key: val.d_data for key, val in value.items()}
|
||||
'dict containing only BoundCompoundValues.')
|
||||
|
||||
# Make sure the data all has the same CompoundValue type and
|
||||
# compare that type against ours once to make sure its fields match.
|
||||
# (this will not allow passing CompoundValues from multiple sources
|
||||
# but I don't know if that would ever come up..)
|
||||
first_value: Any = None
|
||||
for i, val in enumerate(value.values()):
|
||||
if i == 0:
|
||||
first_value = val.d_value
|
||||
# Do the full field comparison on the first value only..
|
||||
if not have_matching_fields(val.d_value, self.d_value):
|
||||
raise ValueError(
|
||||
'CompoundListField assignment must be a '
|
||||
'list containing matching CompoundValues.')
|
||||
else:
|
||||
# For all remaining values, just ensure they match the first.
|
||||
if val.d_value is not first_value:
|
||||
raise ValueError(
|
||||
'CompoundListField assignment cannot contain '
|
||||
'multiple CompoundValue types as sources.')
|
||||
|
||||
data[self.d_key] = self.filter_input(
|
||||
{key: val.d_data
|
||||
for key, val in value.items()}, error=error)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<!--DOCSHASH=096fe9ca51969f120799a3a00bd55cfb-->
|
||||
<h4><em>last updated on 2019-12-19 for Ballistica version 1.5.0 build 20001</em></h4>
|
||||
<!--DOCSHASH=2f4ba682ad3b48baa2f8a93eb9948cb0-->
|
||||
<h4><em>last updated on 2019-12-21 for Ballistica version 1.5.0 build 20001</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>
|
||||
|
||||
@ -72,7 +72,11 @@ class EntityTest(entity.Entity):
|
||||
tval2 = entity.Field('t2', entity.DateTimeValue())
|
||||
str_int_dict = entity.DictField('sd', str, entity.IntValue())
|
||||
compoundlist = entity.CompoundListField('l', CompoundTest())
|
||||
tdval = entity.CompoundDictField('td', str, CompoundTest())
|
||||
compoundlist2 = entity.CompoundListField('l2', CompoundTest())
|
||||
compoundlist3 = entity.CompoundListField('l3', CompoundTest2())
|
||||
compounddict = entity.CompoundDictField('td', str, CompoundTest())
|
||||
compounddict2 = entity.CompoundDictField('td2', str, CompoundTest())
|
||||
compounddict3 = entity.CompoundDictField('td3', str, CompoundTest2())
|
||||
fval2 = entity.Field('f2', entity.Float3Value())
|
||||
|
||||
|
||||
@ -111,8 +115,10 @@ def test_entity_values() -> None:
|
||||
|
||||
# Simple value dict field.
|
||||
assert 'foo' not in ent.str_int_dict
|
||||
# Set with incorrect key type should give TypeError.
|
||||
with pytest.raises(TypeError):
|
||||
ent.str_int_dict[0] = 123 # type: ignore
|
||||
# And set with incorrect value type should do same.
|
||||
with pytest.raises(TypeError):
|
||||
ent.str_int_dict['foo'] = 'bar' # type: ignore
|
||||
ent.str_int_dict['foo'] = 123
|
||||
@ -124,6 +130,9 @@ def test_entity_values() -> None:
|
||||
# (which seems to get a bit ugly, but may be worth revisiting)
|
||||
# assert dict(ent.str_int_dict) == {'foo': 123}
|
||||
|
||||
# Passing key/value pairs as a list works though..
|
||||
assert dict(ent.str_int_dict.items()) == {'foo': 123}
|
||||
|
||||
|
||||
# noinspection PyTypeHints
|
||||
def test_entity_values_2() -> None:
|
||||
@ -154,6 +163,17 @@ def test_entity_values_2() -> None:
|
||||
assert len(ent.compoundlist) == 1
|
||||
assert static_type_equals(ent.compoundlist[0], CompoundTest)
|
||||
|
||||
# Compound dict field.
|
||||
cdval = ent.compounddict.add('foo')
|
||||
assert static_type_equals(cdval, CompoundTest)
|
||||
# Set with incorrect key type should give TypeError.
|
||||
with pytest.raises(TypeError):
|
||||
_cdval2 = ent.compounddict.add(1) # type: ignore
|
||||
# Hmm; should this throw a TypeError and not a KeyError?..
|
||||
with pytest.raises(KeyError):
|
||||
_cdval3 = ent.compounddict[1] # type: ignore
|
||||
assert static_type_equals(ent.compounddict['foo'], CompoundTest)
|
||||
|
||||
# Enum value
|
||||
with pytest.raises(ValueError):
|
||||
ent.enumval = None # type: ignore
|
||||
@ -171,6 +191,7 @@ def test_entity_values_2() -> None:
|
||||
assert static_type_equals(ent.grp.compoundlist[0].subval, bool)
|
||||
|
||||
|
||||
# noinspection PyTypeHints
|
||||
def test_field_copies() -> None:
|
||||
"""Test copying various values between fields."""
|
||||
ent1 = EntityTest()
|
||||
@ -208,10 +229,43 @@ def test_field_copies() -> None:
|
||||
val.isubval = 356
|
||||
assert ent1.compoundlist[0].isubval == 356
|
||||
assert len(ent1.compoundlist) == 1
|
||||
ent1.compoundlist.append()
|
||||
assert len(ent1.compoundlist) == 2
|
||||
assert len(ent2.compoundlist) == 0
|
||||
# Copying to the same field on different obj should work.
|
||||
ent2.compoundlist = ent1.compoundlist
|
||||
assert ent2.compoundlist[0].isubval == 356
|
||||
assert len(ent2.compoundlist) == 1
|
||||
assert len(ent2.compoundlist) == 2
|
||||
# Cross-field assigns should work too if the field layouts match..
|
||||
ent1.compoundlist2 = ent1.compoundlist
|
||||
# And not if they don't...
|
||||
# (in this case mypy errors too but that may not always be the case)
|
||||
with pytest.raises(ValueError):
|
||||
ent1.compoundlist3 = ent1.compoundlist # type: ignore
|
||||
|
||||
# Copying a CompoundDict
|
||||
ent1.compounddict.add('foo')
|
||||
ent1.compounddict.add('bar')
|
||||
assert static_type_equals(ent1.compounddict['foo'].isubval, int)
|
||||
ent1.compounddict['foo'].isubval = 23
|
||||
# Copying to the same field on different obj should work.
|
||||
ent2.compounddict = ent1.compounddict
|
||||
assert ent2.compounddict.keys() == ['foo', 'bar']
|
||||
assert ent2.compounddict['foo'].isubval == 23
|
||||
# Cross field assigns should work too if the field layouts match..
|
||||
ent1.compounddict2 = ent1.compounddict
|
||||
# ..And should fail otherwise.
|
||||
# (mypy catches this too, but that may not always be the case if
|
||||
# two CompoundValues have the same type but different layouts based
|
||||
# on their __init__ args or whatnot)
|
||||
with pytest.raises(ValueError):
|
||||
ent1.compounddict3 = ent1.compounddict # type: ignore
|
||||
# Make sure invalid key types get caught when setting a full dict:
|
||||
with pytest.raises(TypeError):
|
||||
ent1.compounddict2 = {
|
||||
'foo': ent1.compounddict['foo'],
|
||||
2: ent1.compounddict['bar'], # type: ignore
|
||||
}
|
||||
|
||||
|
||||
def test_field_access_from_type() -> None:
|
||||
|
||||
@ -357,19 +357,26 @@ def sync_all() -> None:
|
||||
This assumes that there is a 'sync-full' and 'sync-list' Makefile target
|
||||
under each project.
|
||||
"""
|
||||
import concurrent.futures
|
||||
print(f'{CLRBLU}Updating formatting for all projects...{CLREND}')
|
||||
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
|
||||
if projects_str is None:
|
||||
raise CleanError('EFROTOOL_SYNC_PROJECTS is not defined.')
|
||||
projects = projects_str.split(':')
|
||||
|
||||
def _format_project(fproject: str) -> None:
|
||||
fcmd = f'cd "{fproject}" && make format'
|
||||
print(fcmd)
|
||||
subprocess.run(fcmd, shell=True, check=True)
|
||||
|
||||
# No matter what we're doing (even if just listing), run formatting
|
||||
# in all projects before beginning. Otherwise if we do a sync and then
|
||||
# a preflight we'll often wind up getting out-of-sync errors due to
|
||||
# formatting changing after the sync.
|
||||
for project in projects_str.split(':'):
|
||||
cmd = f'cd "{project}" && make format'
|
||||
print(cmd)
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
with concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=len(projects)) as executor:
|
||||
# Converting this to a list will propagate any errors.
|
||||
list(executor.map(_format_project, projects))
|
||||
|
||||
if len(sys.argv) > 2 and sys.argv[2] == 'list':
|
||||
# List mode
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user