diff --git a/.idea/ballisticacore.iml b/.idea/ballisticacore.iml index f952ec4f..228f604b 100644 --- a/.idea/ballisticacore.iml +++ b/.idea/ballisticacore.iml @@ -5,6 +5,7 @@ + @@ -26,13 +27,13 @@ + - @@ -66,4 +67,4 @@ - + \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index ec6ad318..c28b9a2e 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -14,6 +14,7 @@ abeb abot abtn + accountname accountui accum accumkillcount @@ -94,6 +95,7 @@ audioop autodesk autogenerate + autonoassets autoremove autoretain autoselect @@ -245,6 +247,8 @@ clionbin clioncode clionroot + cloudtool + cloudtoolcmd clrblu clrend clrgrn @@ -326,6 +330,7 @@ datamodule dataname datetimemodule + datetimes daynum dayoffset dbapi @@ -339,6 +344,7 @@ depcls depdata depdatas + depentry deps depset depsets @@ -871,6 +877,7 @@ locationval locs logcat + logintoken logitech logput loofa @@ -1087,6 +1094,7 @@ ouya packagedir packagedirs + packagename painttxtattr palmos pandoc @@ -1365,6 +1373,7 @@ servercallthread servercallthreadtype servercfg + servercmd serverdialog serverget serverput @@ -1552,6 +1561,7 @@ testm testmagicmethods testmock + testobj testpatch testpt testsealable @@ -1678,6 +1688,7 @@ useragentstring userbase userfunctions + utcnow utimensat validpgpkeys valnew @@ -1737,6 +1748,7 @@ wmsbe woooo workdir + workflows wpath writeclasses writefuncs diff --git a/Makefile b/Makefile index 89ad9da1..20aff64c 100644 --- a/Makefile +++ b/Makefile @@ -54,32 +54,31 @@ prereqs-clean: assets: prereqs @cd assets && make -j${CPUS} -# Build only assets required for cmake builds (linux, mac) +# Build assets required for cmake builds (linux, mac) assets-cmake: prereqs @cd assets && $(MAKE) -j${CPUS} cmake -# Build only assets required for windows builds. -# (honoring the WINDOWS_PLATFORM value) +# Build assets required for WINDOWS_PLATFORM windows builds. assets-windows: prereqs @cd assets && $(MAKE) -j${CPUS} win-${WINDOWS_PLATFORM} -# Build only assets required for Win32 windows builds. +# Build assets required for Win32 windows builds. assets-windows-Win32: prereqs @cd assets && $(MAKE) -j${CPUS} win-Win32 -# Build only assets required for x64 windows builds. +# Build assets required for x64 windows builds. assets-windows-x64: prereqs @cd assets && $(MAKE) -j${CPUS} win-x64 -# Build only assets required for mac xcode builds +# Build assets required for mac xcode builds assets-mac: prereqs @cd assets && $(MAKE) -j${CPUS} mac -# Build only assets required for ios. +# Build assets required for ios. assets-ios: prereqs @cd assets && $(MAKE) -j${CPUS} ios -# Build only assets required for android. +# Build assets required for android. assets-android: prereqs @cd assets && $(MAKE) -j${CPUS} android diff --git a/assets/.asset_manifest_1.json b/assets/.asset_manifest_1.json index 32387d66..655304cf 100644 --- a/assets/.asset_manifest_1.json +++ b/assets/.asset_manifest_1.json @@ -14,7 +14,7 @@ "data/scripts/ba/__pycache__/_campaign.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_coopgame.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_coopsession.cpython-37.opt-1.pyc", - "data/scripts/ba/__pycache__/_dep.cpython-37.opt-1.pyc", + "data/scripts/ba/__pycache__/_dependency.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_enums.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_error.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_freeforallsession.cpython-37.opt-1.pyc", @@ -27,7 +27,7 @@ "data/scripts/ba/__pycache__/_lang.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_level.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_lobby.cpython-37.opt-1.pyc", - "data/scripts/ba/__pycache__/_maps.cpython-37.opt-1.pyc", + "data/scripts/ba/__pycache__/_map.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_math.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_messages.cpython-37.opt-1.pyc", "data/scripts/ba/__pycache__/_meta.cpython-37.opt-1.pyc", @@ -62,7 +62,7 @@ "data/scripts/ba/_campaign.py", "data/scripts/ba/_coopgame.py", "data/scripts/ba/_coopsession.py", - "data/scripts/ba/_dep.py", + "data/scripts/ba/_dependency.py", "data/scripts/ba/_enums.py", "data/scripts/ba/_error.py", "data/scripts/ba/_freeforallsession.py", @@ -75,7 +75,7 @@ "data/scripts/ba/_lang.py", "data/scripts/ba/_level.py", "data/scripts/ba/_lobby.py", - "data/scripts/ba/_maps.py", + "data/scripts/ba/_map.py", "data/scripts/ba/_math.py", "data/scripts/ba/_messages.py", "data/scripts/ba/_meta.py", diff --git a/assets/Makefile b/assets/Makefile index 11b571b8..30647a4b 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -170,6 +170,7 @@ SCRIPT_TARGETS_PY_1 = \ build/data/scripts/ba/_profile.py \ build/data/scripts/ba/_error.py \ build/data/scripts/ba/_achievement.py \ + build/data/scripts/ba/_map.py \ build/data/scripts/ba/_teambasesession.py \ build/data/scripts/ba/_gameutils.py \ build/data/scripts/ba/_activity.py \ @@ -197,14 +198,13 @@ SCRIPT_TARGETS_PY_1 = \ build/data/scripts/ba/_lobby.py \ build/data/scripts/ba/_stats.py \ build/data/scripts/ba/_input.py \ - build/data/scripts/ba/_dep.py \ build/data/scripts/ba/_level.py \ + build/data/scripts/ba/_dependency.py \ build/data/scripts/ba/_general.py \ build/data/scripts/ba/_server.py \ build/data/scripts/ba/_account.py \ build/data/scripts/ba/_music.py \ build/data/scripts/ba/_lang.py \ - build/data/scripts/ba/_maps.py \ build/data/scripts/ba/_teamgame.py \ build/data/scripts/ba/ui/__init__.py \ build/data/scripts/bastd/mainmenu.py \ @@ -405,6 +405,7 @@ SCRIPT_TARGETS_PYC_1 = \ build/data/scripts/ba/__pycache__/_profile.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_error.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_achievement.cpython-37.opt-1.pyc \ + build/data/scripts/ba/__pycache__/_map.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_teambasesession.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_gameutils.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_activity.cpython-37.opt-1.pyc \ @@ -432,14 +433,13 @@ SCRIPT_TARGETS_PYC_1 = \ build/data/scripts/ba/__pycache__/_lobby.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_stats.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_input.cpython-37.opt-1.pyc \ - build/data/scripts/ba/__pycache__/_dep.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_level.cpython-37.opt-1.pyc \ + build/data/scripts/ba/__pycache__/_dependency.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_general.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_server.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_account.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_music.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_lang.cpython-37.opt-1.pyc \ - build/data/scripts/ba/__pycache__/_maps.cpython-37.opt-1.pyc \ build/data/scripts/ba/__pycache__/_teamgame.cpython-37.opt-1.pyc \ build/data/scripts/ba/ui/__pycache__/__init__.cpython-37.opt-1.pyc \ build/data/scripts/bastd/__pycache__/mainmenu.cpython-37.opt-1.pyc \ @@ -764,6 +764,11 @@ build/data/scripts/ba/__pycache__/_achievement.cpython-37.opt-1.pyc: \ @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ +build/data/scripts/ba/__pycache__/_map.cpython-37.opt-1.pyc: \ + build/data/scripts/ba/_map.py + @echo Compiling script: $^ + @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ + build/data/scripts/ba/__pycache__/_teambasesession.cpython-37.opt-1.pyc: \ build/data/scripts/ba/_teambasesession.py @echo Compiling script: $^ @@ -899,13 +904,13 @@ build/data/scripts/ba/__pycache__/_input.cpython-37.opt-1.pyc: \ @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ -build/data/scripts/ba/__pycache__/_dep.cpython-37.opt-1.pyc: \ - build/data/scripts/ba/_dep.py +build/data/scripts/ba/__pycache__/_level.cpython-37.opt-1.pyc: \ + build/data/scripts/ba/_level.py @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ -build/data/scripts/ba/__pycache__/_level.cpython-37.opt-1.pyc: \ - build/data/scripts/ba/_level.py +build/data/scripts/ba/__pycache__/_dependency.cpython-37.opt-1.pyc: \ + build/data/scripts/ba/_dependency.py @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ @@ -934,11 +939,6 @@ build/data/scripts/ba/__pycache__/_lang.cpython-37.opt-1.pyc: \ @echo Compiling script: $^ @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ -build/data/scripts/ba/__pycache__/_maps.cpython-37.opt-1.pyc: \ - build/data/scripts/ba/_maps.py - @echo Compiling script: $^ - @rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@ - build/data/scripts/ba/__pycache__/_teamgame.cpython-37.opt-1.pyc: \ build/data/scripts/ba/_teamgame.py @echo Compiling script: $^ diff --git a/assets/src/data/scripts/ba/__init__.py b/assets/src/data/scripts/ba/__init__.py index 2598afb1..21b84b0a 100644 --- a/assets/src/data/scripts/ba/__init__.py +++ b/assets/src/data/scripts/ba/__init__.py @@ -43,7 +43,8 @@ from ba._actor import Actor from ba._app import App from ba._coopgame import CoopGameActivity from ba._coopsession import CoopSession -from ba._dep import Dep, Dependency, DepComponent, DepSet, AssetPackage +from ba._dependency import (Dependency, DependencyComponent, DependencySet, + AssetPackage) from ba._enums import TimeType, Permission, TimeFormat, SpecialChar from ba._error import (UNHANDLED, print_exception, print_error, NotFoundError, PlayerNotFoundError, NodeNotFoundError, @@ -55,7 +56,7 @@ from ba._freeforallsession import FreeForAllSession from ba._gameactivity import GameActivity from ba._gameresults import TeamGameResults from ba._lang import Lstr, setlanguage, get_valid_languages -from ba._maps import Map, getmaps +from ba._map import Map, getmaps from ba._session import Session from ba._stats import PlayerScoredMessage, PlayerRecord, Stats from ba._team import Team diff --git a/assets/src/data/scripts/ba/_activity.py b/assets/src/data/scripts/ba/_activity.py index 1ec4a5e4..7fcd0b3c 100644 --- a/assets/src/data/scripts/ba/_activity.py +++ b/assets/src/data/scripts/ba/_activity.py @@ -25,7 +25,7 @@ import weakref from typing import TYPE_CHECKING import _ba -from ba._dep import InstancedDepComponent +from ba._dependency import DependencyComponent if TYPE_CHECKING: from weakref import ReferenceType @@ -34,7 +34,7 @@ if TYPE_CHECKING: from bastd.actor.respawnicon import RespawnIcon -class Activity(InstancedDepComponent): +class Activity(DependencyComponent): """Units of execution wrangled by a ba.Session. Category: Gameplay Classes diff --git a/assets/src/data/scripts/ba/_app.py b/assets/src/data/scripts/ba/_app.py index 420462c6..29e09f4b 100644 --- a/assets/src/data/scripts/ba/_app.py +++ b/assets/src/data/scripts/ba/_app.py @@ -422,7 +422,7 @@ class App: from ba import _appconfig from ba import ui as bsui from ba import _achievement - from ba import _maps + from ba import _map from ba import _meta from ba import _music from ba import _campaign @@ -451,7 +451,7 @@ class App: stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts, stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage ]: - _maps.register_map(maptype) + _map.register_map(maptype) if self.debug_build: _apputils.suppress_debug_reports() @@ -590,7 +590,7 @@ class App: self.ran_on_launch = True - from ba._dep import test_depset + from ba._dependency import test_depset test_depset() # print('GAME TYPES ARE', meta.get_game_types()) # _bs.quit() diff --git a/assets/src/data/scripts/ba/_benchmark.py b/assets/src/data/scripts/ba/_benchmark.py index f1418589..1d9ba1e7 100644 --- a/assets/src/data/scripts/ba/_benchmark.py +++ b/assets/src/data/scripts/ba/_benchmark.py @@ -43,7 +43,7 @@ def run_cpu_benchmark() -> None: def __init__(self) -> None: print('FIXME: BENCHMARK SESSION WOULD CALC DEPS.') - depsets: Sequence[ba.DepSet] = [] + depsets: Sequence[ba.DependencySet] = [] super().__init__(depsets) diff --git a/assets/src/data/scripts/ba/_coopsession.py b/assets/src/data/scripts/ba/_coopsession.py index c68aa044..0bb87e8b 100644 --- a/assets/src/data/scripts/ba/_coopsession.py +++ b/assets/src/data/scripts/ba/_coopsession.py @@ -73,7 +73,7 @@ class CoopSession(Session): max_players = 4 print('FIXME: COOP SESSION WOULD CALC DEPS.') - depsets: Sequence[ba.DepSet] = [] + depsets: Sequence[ba.DependencySet] = [] super().__init__(depsets, team_names=TEAM_NAMES, diff --git a/assets/src/data/scripts/ba/_dep.py b/assets/src/data/scripts/ba/_dep.py deleted file mode 100644 index 07d96fd3..00000000 --- a/assets/src/data/scripts/ba/_dep.py +++ /dev/null @@ -1,523 +0,0 @@ -# Copyright (c) 2011-2019 Eric Froemling -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# ----------------------------------------------------------------------------- -"""Functionality related to object/asset dependencies.""" - -from __future__ import annotations - -import weakref -from typing import (Generic, TypeVar, TYPE_CHECKING, cast, Type, overload) - -import _ba -from ba import _general - -if TYPE_CHECKING: - from typing import Optional, Any, Dict, List, Set - import ba - -T = TypeVar('T', bound='DepComponent') -TI = TypeVar('TI', bound='InstancedDepComponent') -TS = TypeVar('TS', bound='StaticDepComponent') - - -class Dependency(Generic[T]): - """A dependency on a DepComponent (with an optional config). - - Category: Dependency Classes - - This class is used to request and access functionality provided - by other DepComponent classes from a DepComponent class. - The class functions as a descriptor, allowing dependencies to - be added at a class level much the same as properties or methods - and then used with class instances to access those dependencies. - For instance, if you do 'floofcls = ba.Dependency(FloofClass)' you - would then be able to instantiate a FloofClass in your class's - methods via self.floofcls(). - """ - - def __init__(self, cls: Type[T], config: Any = None): - """Instantiate a Dependency given a ba.DepComponent subtype. - - Optionally, an arbitrary object can be passed as 'config' to - influence dependency calculation for the target class. - """ - self.cls: Type[T] = cls - self.config = config - self._hash: Optional[int] = None - - def get_hash(self) -> int: - """Return the dependency's hash, calculating it if necessary.""" - if self._hash is None: - self._hash = _general.make_hash((self.cls, self.config)) - return self._hash - - # NOTE: it appears that mypy is currently not able to do overloads based - # on the type of 'self', otherwise we could just overload this to - # return different things based on self's type and avoid the need for - # the fake dep classes below. - # See https://github.com/python/mypy/issues/5320 - def __get__(self, obj: Any, cls: Any = None) -> Any: - if obj is None: - raise TypeError("Dependency must be accessed through an instance.") - - # We expect to be instantiated from an already living DepComponent - # with valid dep-data in place.. - assert type is not None - depdata = getattr(obj, '_depdata') - if depdata is None: - raise RuntimeError("Invalid dependency access.") - assert isinstance(depdata, DepData) - - # Now look up the data for this particular dep - depset = depdata.depset() - assert isinstance(depset, DepSet) - assert self._hash in depset.depdatas - depdata = depset.depdatas[self._hash] - assert isinstance(depdata, DepData) - if depdata.valid is False: - raise RuntimeError( - f'Accessing DepComponent {depdata.cls} in an invalid state.') - assert self.cls.dep_get_payload(depdata) is not None - return self.cls.dep_get_payload(depdata) - - -# We define a 'Dep' which at runtime simply aliases the Dependency class -# but in type-checking points to two overloaded functions based on the argument -# type. This lets the type system know what type of object the Dep represents. -# (object instances in the case of StaticDep classes or object types in the -# case of regular deps) At some point hopefully we can replace this with a -# simple overload in Dependency.__get__ based on the type of self -# (see note above). -if not TYPE_CHECKING: - Dep = Dependency -else: - - class _InstanceDep(Dependency[TI]): - """Fake stub we use to tell the type system we provide a type.""" - - def __get__(self, obj: Any, cls: Any = None) -> Type[TI]: - return cast(Type[TI], None) - - class _StaticDep(Dependency[TS]): - """Fake stub we use to tell the type system we provide an instance.""" - - def __get__(self, obj: Any, cls: Any = None) -> TS: - return cast(TS, None) - - # pylint: disable=invalid-name - # noinspection PyPep8Naming - @overload - def Dep(cls: Type[TI], config: Any = None) -> _InstanceDep[TI]: - """test""" - return _InstanceDep(cls, config) - - # noinspection PyPep8Naming - @overload - def Dep(cls: Type[TS], config: Any = None) -> _StaticDep[TS]: - """test""" - return _StaticDep(cls, config) - - # noinspection PyPep8Naming - def Dep(cls: Any, config: Any = None) -> Any: - """test""" - return Dependency(cls, config) - - # pylint: enable=invalid-name - - -class BoundDepComponent: - """A DepComponent class bound to its DepSet data. - - Can be called to instantiate the class with its data properly in place.""" - - def __init__(self, cls: Any, depdata: DepData): - self.cls = cls - # BoundDepComponents can be stored on depdatas so we use weakrefs - # to avoid dependency cycles. - self.depdata = weakref.ref(depdata) - - def __call__(self, *args: Any, **keywds: Any) -> Any: - # We don't simply call our target type to instantiate it; - # instead we manually call __new__ and then __init__. - # This allows us to inject its data properly before __init__(). - obj = self.cls.__new__(self.cls, *args, **keywds) - obj._depdata = self.depdata() - assert isinstance(obj._depdata, DepData) - obj.__init__(*args, **keywds) - return obj - - -class DepComponent: - """Base class for all classes that can act as or use dependencies. - - category: Dependency Classes - """ - - _depdata: DepData - - def __init__(self) -> None: - """Instantiate a DepComponent.""" - - # For now lets issue a warning if these are instantiated without - # data; we'll make this an error once we're no longer seeing warnings. - depdata = getattr(self, '_depdata', None) - if depdata is None: - print(f'FIXME: INSTANTIATING DEP CLASS {type(self)} DIRECTLY.') - - self.context = _ba.Context('current') - - @classmethod - def is_present(cls, config: Any = None) -> bool: - """Return whether this component/config is present on this device.""" - del config # Unused here. - return True - - @classmethod - def get_dynamic_deps(cls, config: Any = None) -> List[Dependency]: - """Return any dynamically-calculated deps for this component/config. - - Deps declared statically as part of the class do not need to be - included here; this is only for additional deps that may vary based - on the dep config value. (for instance a map required by a game type) - """ - del config # Unused here. - return [] - - @classmethod - def dep_get_payload(cls, depdata: DepData) -> Any: - """Return user-facing data for a loaded dep. - - If this dep does not yet have a 'payload' value, it should - be generated and cached. Otherwise the existing value - should be returned. - This is the value given for a DepComponent when accessed - through a Dependency instance on a live object, etc. - """ - del depdata # Unused here. - - -class DepData: - """Data associated with a dependency in a dependency set.""" - - def __init__(self, depset: DepSet, dep: Dependency[T]): - # Note: identical Dep/config pairs will share data, so the dep - # entry on a given Dep may not point to. - self.cls = dep.cls - self.config = dep.config - - # Arbitrary data for use by dependencies in the resolved set - # (the static instance for static-deps, etc). - self.payload: Any = None - self.valid: bool = False - - # Weakref to the depset that includes us (to avoid ref loop). - self.depset = weakref.ref(depset) - - -class DepSet(Generic[TI]): - """Set of resolved dependencies and their associated data.""" - - def __init__(self, root: Dependency[TI]): - self.root = root - self._resolved = False - - # Dependency data indexed by hash. - self.depdatas: Dict[int, DepData] = {} - - # Instantiated static-components. - self.static_instances: List[StaticDepComponent] = [] - - def __del__(self) -> None: - # When our dep-set goes down, clear out all dep-data payloads - # so we can throw errors if anyone tries to use them anymore. - for depdata in self.depdatas.values(): - depdata.payload = None - depdata.valid = False - - def resolve(self) -> None: - """Resolve the total set of required dependencies for the set. - - Raises a ba.DependencyError if dependencies are missing (or other - Exception types on other errors). - """ - - if self._resolved: - raise Exception("DepSet has already been resolved.") - - print('RESOLVING DEP SET') - - # First, recursively expand out all dependencies. - self._resolve(self.root, 0) - - # Now, if any dependencies are not present, raise an Exception - # telling exactly which ones (so hopefully they'll be able to be - # downloaded/etc. - missing = [ - Dependency(entry.cls, entry.config) - for entry in self.depdatas.values() - if not entry.cls.is_present(entry.config) - ] - if missing: - from ba._error import DependencyError - raise DependencyError(missing) - - self._resolved = True - print('RESOLVE SUCCESS!') - - def get_asset_package_ids(self) -> Set[str]: - """Return the set of asset-package-ids required by this dep-set. - - Must be called on a resolved dep-set. - """ - ids: Set[str] = set() - if not self._resolved: - raise Exception('Must be called on a resolved dep-set.') - for entry in self.depdatas.values(): - if issubclass(entry.cls, AssetPackage): - assert isinstance(entry.config, str) - ids.add(entry.config) - return ids - - def load(self) -> Type[TI]: - """Attach the resolved set to the current context. - - Returns a wrapper which can be used to instantiate the root dep. - """ - # NOTE: stuff below here should probably go in a separate 'instantiate' - # method or something. - if not self._resolved: - raise Exception("Can't instantiate an unresolved DepSet") - - # Go through all of our dep entries and give them a chance to - # preload whatever they want. - for entry in self.depdatas.values(): - # First mark everything as valid so recursive loads don't fail. - assert entry.valid is False - entry.valid = True - for entry in self.depdatas.values(): - # Do a get on everything which will init all payloads - # in the proper order recursively. - # NOTE: should we guard for recursion here?... - entry.cls.dep_get_payload(entry) - - # NOTE: like above, we're cheating here and telling the type - # system we're simply returning the root dependency class, when - # actually it's a bound-dependency wrapper containing its data/etc. - # ..Should fix if/when mypy is smart enough to preserve type safety - # on the wrapper's __call__() - rootdata = self.depdatas[self.root.get_hash()] - return cast(Type[TI], BoundDepComponent(self.root.cls, rootdata)) - - def _resolve(self, dep: Dependency[T], recursion: int) -> None: - - # Watch for wacky infinite dep loops. - if recursion > 10: - raise Exception('Max recursion reached') - - hashval = dep.get_hash() - - if hashval in self.depdatas: - # Found an already resolved one; we're done here. - return - - # Add our entry before we recurse so we don't repeat add it if - # there's a dependency loop. - self.depdatas[hashval] = DepData(self, dep) - - # Grab all Dependency instances we find in the class. - subdeps = [ - cls for cls in dep.cls.__dict__.values() - if isinstance(cls, Dependency) - ] - - # ..and add in any dynamic ones it provides. - subdeps += dep.cls.get_dynamic_deps(dep.config) - for subdep in subdeps: - self._resolve(subdep, recursion + 1) - - -class InstancedDepComponent(DepComponent): - """Base class for DepComponents intended to be instantiated as needed.""" - - @classmethod - def dep_get_payload(cls, depdata: DepData) -> Any: - """Data provider override; returns a BoundDepComponent.""" - if depdata.payload is None: - # The payload we want for ourself in the dep-set is simply - # the bound-def that users can use to instantiate our class - # with its data properly intact. We could also just store - # the class and instantiate one of these each time. - depdata.payload = BoundDepComponent(cls, depdata) - return depdata.payload - - -class StaticDepComponent(DepComponent): - """Base for DepComponents intended to be instantiated once and shared.""" - - @classmethod - def dep_get_payload(cls, depdata: DepData) -> Any: - """Data provider override; returns shared instance.""" - if depdata.payload is None: - # We want to share a single instance of our object with anything - # in the set that requested it, so create a temp bound-dep and - # create an instance from that. - depcls = BoundDepComponent(cls, depdata) - - # Instances have a strong ref to depdata so we can't give - # depdata a strong reference to it without creating a cycle. - # We also can't just weak-ref the instance or else it won't be - # kept alive. Our solution is to stick strong refs to all static - # components somewhere on the DepSet. - instance = depcls() - assert depdata.depset - depset2 = depdata.depset() - assert depset2 is not None - depset2.static_instances.append(instance) - depdata.payload = weakref.ref(instance) - assert isinstance(depdata.payload, weakref.ref) - payload = depdata.payload() - if payload is None: - raise RuntimeError( - f'Accessing DepComponent {cls} in an invalid state.') - return payload - - -class AssetPackage(StaticDepComponent): - """DepComponent representing a bundled package of game assets.""" - - def __init__(self) -> None: - super().__init__() - # pylint: disable=no-member - assert isinstance(self._depdata.config, str) - self.package_id = self._depdata.config - print(f'LOADING ASSET PACKAGE {self.package_id}') - - @classmethod - def is_present(cls, config: Any = None) -> bool: - assert isinstance(config, str) - - # Temp: hard-coding for a single asset-package at the moment. - if config == 'stdassets@1': - return True - return False - - def gettexture(self, name: str) -> ba.Texture: - """Load a named ba.Texture from the AssetPackage. - - Behavior is similar to ba.gettexture() - """ - return _ba.get_package_texture(self, name) - - def getmodel(self, name: str) -> ba.Model: - """Load a named ba.Model from the AssetPackage. - - Behavior is similar to ba.getmodel() - """ - return _ba.get_package_model(self, name) - - def getcollidemodel(self, name: str) -> ba.CollideModel: - """Load a named ba.CollideModel from the AssetPackage. - - Behavior is similar to ba.getcollideModel() - """ - return _ba.get_package_collide_model(self, name) - - def getsound(self, name: str) -> ba.Sound: - """Load a named ba.Sound from the AssetPackage. - - Behavior is similar to ba.getsound() - """ - return _ba.get_package_sound(self, name) - - def getdata(self, name: str) -> ba.Data: - """Load a named ba.Data from the AssetPackage. - - Behavior is similar to ba.getdata() - """ - return _ba.get_package_data(self, name) - - -class TestClassFactory(StaticDepComponent): - """Another test dep-obj.""" - - _assets = Dep(AssetPackage, 'stdassets@1') - - def __init__(self) -> None: - super().__init__() - print("Instantiating TestClassFactory") - self.tex = self._assets.gettexture('black') - self.model = self._assets.getmodel('landMine') - self.sound = self._assets.getsound('error') - self.data = self._assets.getdata('langdata') - - -class TestClassObj(InstancedDepComponent): - """Another test dep-obj.""" - - -class TestClass(InstancedDepComponent): - """A test dep-obj.""" - - _actorclass = Dep(TestClassObj) - _factoryclass = Dep(TestClassFactory, 123) - _factoryclass2 = Dep(TestClassFactory, 124) - - def __init__(self, arg: int) -> None: - super().__init__() - del arg - self._actor = self._actorclass() - print('got actor', self._actor) - print('have factory', self._factoryclass) - print('have factory2', self._factoryclass2) - - -def test_depset() -> None: - """Test call to try this stuff out...""" - # noinspection PyUnreachableCode - if False: # pylint: disable=using-constant-test - print('running test_depset()...') - - def doit() -> None: - from ba._error import DependencyError - depset = DepSet(Dep(TestClass)) - resolved = False - try: - depset.resolve() - resolved = True - except DependencyError as exc: - for dep in exc.deps: - if dep.cls is AssetPackage: - print('MISSING PACKAGE', dep.config) - else: - raise Exception('unknown dependency error for ' + - str(dep.cls)) - except Exception as exc: - print('DepSet resolve failed with exc type:', type(exc)) - if resolved: - testclass = depset.load() - instance = testclass(123) - print("INSTANTIATED ROOT:", instance) - - doit() - - # To test this, add prints on __del__ for stuff used above; - # everything should be dead at this point if we have no cycles. - print('everything should be cleaned up...') - _ba.quit() diff --git a/assets/src/data/scripts/ba/_dependency.py b/assets/src/data/scripts/ba/_dependency.py new file mode 100644 index 00000000..9e4a45f3 --- /dev/null +++ b/assets/src/data/scripts/ba/_dependency.py @@ -0,0 +1,437 @@ +# Copyright (c) 2011-2019 Eric Froemling +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- +"""Functionality related to object/asset dependencies.""" + +from __future__ import annotations + +import weakref +from typing import (Generic, TypeVar, TYPE_CHECKING) + +import _ba +from ba import _general + +if TYPE_CHECKING: + from typing import Optional, Any, Dict, List, Set, Type + from weakref import ReferenceType + import ba + +T = TypeVar('T', bound='DependencyComponent') + + +class Dependency(Generic[T]): + """A dependency on a DependencyComponent (with an optional config). + + Category: Dependency Classes + + This class is used to request and access functionality provided + by other DependencyComponent classes from a DependencyComponent class. + The class functions as a descriptor, allowing dependencies to + be added at a class level much the same as properties or methods + and then used with class instances to access those dependencies. + For instance, if you do 'floofcls = ba.Dependency(FloofClass)' you + would then be able to instantiate a FloofClass in your class's + methods via self.floofcls(). + """ + + def __init__(self, cls: Type[T], config: Any = None): + """Instantiate a Dependency given a ba.DependencyComponent type. + + Optionally, an arbitrary object can be passed as 'config' to + influence dependency calculation for the target class. + """ + self.cls: Type[T] = cls + self.config = config + self._hash: Optional[int] = None + + def get_hash(self) -> int: + """Return the dependency's hash, calculating it if necessary.""" + if self._hash is None: + self._hash = _general.make_hash((self.cls, self.config)) + return self._hash + + def __get__(self, obj: Any, cls: Any = None) -> T: + if not isinstance(obj, DependencyComponent): + if obj is None: + raise TypeError( + "Dependency must be accessed through an instance.") + raise TypeError( + f"Dependency cannot be added to class of type {type(obj)}" + " (class must inherit from ba.DependencyComponent).") + + # We expect to be instantiated from an already living + # DependencyComponent with valid dep-data in place.. + assert cls is not None + + # Get the DependencyEntry this instance is associated with and from + # there get back to the DependencySet + entry = getattr(obj, '_dep_entry') + if entry is None: + raise RuntimeError("Invalid dependency access.") + entry = entry() + assert isinstance(entry, DependencyEntry) + depset = entry.depset() + assert isinstance(depset, DependencySet) + + if not depset.resolved: + raise RuntimeError( + "Can't access data on an unresolved DependencySet.") + + # Look up the data in the set based on the hash for this Dependency. + assert self._hash in depset.entries + entry = depset.entries[self._hash] + assert isinstance(entry, DependencyEntry) + retval = entry.get_component() + assert isinstance(retval, self.cls) + return retval + + +class DependencyComponent: + """Base class for all classes that can act as or use dependencies. + + category: Dependency Classes + """ + + _dep_entry: ReferenceType[DependencyEntry] + + def __init__(self) -> None: + """Instantiate a DependencyComponent.""" + + # For now lets issue a warning if these are instantiated without + # a dep-entry; we'll make this an error once we're no longer + # seeing warnings. + entry = getattr(self, '_dep_entry', None) + if entry is None: + print(f'FIXME: INSTANTIATING DEP CLASS {type(self)} DIRECTLY.') + + @classmethod + def dep_is_present(cls, config: Any = None) -> bool: + """Return whether this component/config is present on this device.""" + del config # Unused here. + return True + + @classmethod + def get_dynamic_deps(cls, config: Any = None) -> List[Dependency]: + """Return any dynamically-calculated deps for this component/config. + + Deps declared statically as part of the class do not need to be + included here; this is only for additional deps that may vary based + on the dep config value. (for instance a map required by a game type) + """ + del config # Unused here. + return [] + + +class DependencyEntry: + """Data associated with a dependency/config pair in a ba.DependencySet.""" + + def __del__(self) -> None: + print('~DepEntry()', self.cls) + + def __init__(self, depset: DependencySet, dep: Dependency[T]): + print("DepEntry()", dep.cls) + self.cls = dep.cls + self.config = dep.config + + # Arbitrary data for use by dependencies in the resolved set + # (the static instance for static-deps, etc). + self.component: Optional[DependencyComponent] = None + + # Weakref to the depset that includes us (to avoid ref loop). + self.depset = weakref.ref(depset) + + def get_component(self) -> DependencyComponent: + """Return the component instance, creating it if necessary.""" + if self.component is None: + # We don't simply call our type to instantiate our instance; + # instead we manually call __new__ and then __init__. + # This allows us to inject its data properly before __init__(). + print('creating', self.cls) + instance = self.cls.__new__(self.cls) + # pylint: disable=protected-access + instance._dep_entry = weakref.ref(self) + instance.__init__() + + assert self.depset + depset = self.depset() + assert depset is not None + self.component = instance + component = self.component + assert isinstance(component, self.cls) + if component is None: + raise RuntimeError(f'Accessing DependencyComponent {self.cls} ' + 'in an invalid state.') + return component + + +class DependencySet(Generic[T]): + """Set of resolved dependencies and their associated data. + + To use DependencyComponents, a set must be created, resolved, and then + loaded. The DependencyComponents are only valid while the set remains + in existence. + """ + + def __init__(self, root_dependency: Dependency[T]): + print('DepSet()') + self._root_dependency = root_dependency + self._resolved = False + self._loaded = False + + # Dependency data indexed by hash. + self.entries: Dict[int, DependencyEntry] = {} + + def __del__(self) -> None: + print("~DepSet()") + + def resolve(self) -> None: + """Resolve the complete set of required dependencies for this set. + + Raises a ba.DependencyError if dependencies are missing (or other + Exception types on other errors). + """ + + if self._resolved: + raise Exception("DependencySet has already been resolved.") + + print('RESOLVING DEP SET') + + # First, recursively expand out all dependencies. + self._resolve(self._root_dependency, 0) + + # Now, if any dependencies are not present, raise an Exception + # telling exactly which ones (so hopefully they'll be able to be + # downloaded/etc. + missing = [ + Dependency(entry.cls, entry.config) + for entry in self.entries.values() + if not entry.cls.dep_is_present(entry.config) + ] + if missing: + from ba._error import DependencyError + raise DependencyError(missing) + + self._resolved = True + print('RESOLVE SUCCESS!') + + @property + def resolved(self) -> bool: + """Whether this set has been successfully resolved.""" + return self._resolved + + def get_asset_package_ids(self) -> Set[str]: + """Return the set of asset-package-ids required by this dep-set. + + Must be called on a resolved dep-set. + """ + ids: Set[str] = set() + if not self._resolved: + raise Exception('Must be called on a resolved dep-set.') + for entry in self.entries.values(): + if issubclass(entry.cls, AssetPackage): + assert isinstance(entry.config, str) + ids.add(entry.config) + return ids + + def load(self) -> None: + """Instantiate all DependencyComponents in the set. + + Returns a wrapper which can be used to instantiate the root dep. + """ + # NOTE: stuff below here should probably go in a separate 'instantiate' + # method or something. + if not self._resolved: + raise RuntimeError("Can't load an unresolved DependencySet") + + for entry in self.entries.values(): + # Do a get on everything which will init all payloads + # in the proper order recursively. + entry.get_component() + + self._loaded = True + + @property + def root(self) -> T: + """The instantiated root DependencyComponent instance for the set.""" + if not self._loaded: + raise RuntimeError("DependencySet is not loaded.") + + rootdata = self.entries[self._root_dependency.get_hash()].component + assert isinstance(rootdata, self._root_dependency.cls) + return rootdata + + def _resolve(self, dep: Dependency[T], recursion: int) -> None: + + # Watch for wacky infinite dep loops. + if recursion > 10: + raise Exception('Max recursion reached') + + hashval = dep.get_hash() + + if hashval in self.entries: + # Found an already resolved one; we're done here. + return + + # Add our entry before we recurse so we don't repeat add it if + # there's a dependency loop. + self.entries[hashval] = DependencyEntry(self, dep) + + # Grab all Dependency instances we find in the class. + subdeps = [ + cls for cls in dep.cls.__dict__.values() + if isinstance(cls, Dependency) + ] + + # ..and add in any dynamic ones it provides. + subdeps += dep.cls.get_dynamic_deps(dep.config) + for subdep in subdeps: + self._resolve(subdep, recursion + 1) + + +class AssetPackage(DependencyComponent): + """DependencyComponent representing a bundled package of game assets.""" + + def __init__(self) -> None: + super().__init__() + # pylint: disable=no-member + + # This is used internally by the get_package_xxx calls. + self.context = _ba.Context('current') + + entry = self._dep_entry() + assert entry is not None + assert isinstance(entry.config, str) + self.package_id = entry.config + print(f'LOADING ASSET PACKAGE {self.package_id}') + + @classmethod + def dep_is_present(cls, config: Any = None) -> bool: + assert isinstance(config, str) + + # Temp: hard-coding for a single asset-package at the moment. + if config == 'stdassets@1': + return True + return False + + def gettexture(self, name: str) -> ba.Texture: + """Load a named ba.Texture from the AssetPackage. + + Behavior is similar to ba.gettexture() + """ + return _ba.get_package_texture(self, name) + + def getmodel(self, name: str) -> ba.Model: + """Load a named ba.Model from the AssetPackage. + + Behavior is similar to ba.getmodel() + """ + return _ba.get_package_model(self, name) + + def getcollidemodel(self, name: str) -> ba.CollideModel: + """Load a named ba.CollideModel from the AssetPackage. + + Behavior is similar to ba.getcollideModel() + """ + return _ba.get_package_collide_model(self, name) + + def getsound(self, name: str) -> ba.Sound: + """Load a named ba.Sound from the AssetPackage. + + Behavior is similar to ba.getsound() + """ + return _ba.get_package_sound(self, name) + + def getdata(self, name: str) -> ba.Data: + """Load a named ba.Data from the AssetPackage. + + Behavior is similar to ba.getdata() + """ + return _ba.get_package_data(self, name) + + +class TestClassFactory(DependencyComponent): + """Another test dep-obj.""" + + _assets = Dependency(AssetPackage, 'stdassets@1') + + def __init__(self) -> None: + super().__init__() + print("Instantiating TestClassFactory") + self.tex = self._assets.gettexture('black') + self.model = self._assets.getmodel('landMine') + self.sound = self._assets.getsound('error') + self.data = self._assets.getdata('langdata') + + +class TestClassObj(DependencyComponent): + """Another test dep-obj.""" + + +class TestClass(DependencyComponent): + """A test dep-obj.""" + + _testclass = Dependency(TestClassObj) + _factoryclass = Dependency(TestClassFactory, 123) + _factoryclass2 = Dependency(TestClassFactory, 123) + + def __del__(self) -> None: + print("~TestClass()") + + def __init__(self) -> None: + super().__init__() + print('TestClass()') + self._actor = self._testclass + print('got actor', self._actor) + print('have factory', self._factoryclass) + print('have factory2', self._factoryclass2) + + +def test_depset() -> None: + """Test call to try this stuff out...""" + # noinspection PyUnreachableCode + if False: # pylint: disable=using-constant-test + print('running test_depset()...') + + def doit() -> None: + from ba._error import DependencyError + depset = DependencySet(Dependency(TestClass)) + try: + depset.resolve() + except DependencyError as exc: + for dep in exc.deps: + if dep.cls is AssetPackage: + print('MISSING ASSET PACKAGE', dep.config) + else: + raise Exception('unknown dependency error for ' + + str(dep.cls)) + except Exception as exc: + print('DependencySet resolve failed with exc type:', type(exc)) + if depset.resolved: + depset.load() + testobj = depset.root + # instance = testclass(123) + print("INSTANTIATED ROOT:", testobj) + + doit() + + # To test this, add prints on __del__ for stuff used above; + # everything should be dead at this point if we have no cycles. + print('everything should be cleaned up...') + _ba.quit() diff --git a/assets/src/data/scripts/ba/_gameactivity.py b/assets/src/data/scripts/ba/_gameactivity.py index b6b052e1..51e092f0 100644 --- a/assets/src/data/scripts/ba/_gameactivity.py +++ b/assets/src/data/scripts/ba/_gameactivity.py @@ -250,9 +250,9 @@ class GameActivity(Activity): implementation; should return a list of map names valid for this game-type for the given ba.Session type. """ - from ba import _maps + from ba import _map del sessiontype # unused arg - return _maps.getmaps("melee") + return _map.getmaps("melee") @classmethod def get_config_display_string(cls, config: Dict[str, Any]) -> ba.Lstr: @@ -261,7 +261,7 @@ class GameActivity(Activity): This is used when viewing game-lists or showing what game is up next in a series. """ - from ba import _maps + from ba import _map name = cls.get_display_string(config['settings']) # in newer configs, map is in settings; it used to be in the @@ -270,15 +270,15 @@ class GameActivity(Activity): sval = Lstr(value="${NAME} @ ${MAP}", subs=[('${NAME}', name), ('${MAP}', - _maps.get_map_display_string( - _maps.get_filtered_map_name( + _map.get_map_display_string( + _map.get_filtered_map_name( config['settings']['map'])))]) elif 'map' in config: sval = Lstr(value="${NAME} @ ${MAP}", subs=[('${NAME}', name), ('${MAP}', - _maps.get_map_display_string( - _maps.get_filtered_map_name(config['map']))) + _map.get_map_display_string( + _map.get_filtered_map_name(config['map']))) ]) else: print('invalid game config - expected map entry under settings') @@ -295,7 +295,7 @@ class GameActivity(Activity): def __init__(self, settings: Dict[str, Any]): """Instantiate the Activity.""" - from ba import _maps + from ba import _map super().__init__(settings) # Set some defaults. @@ -313,7 +313,7 @@ class GameActivity(Activity): else: # If settings doesn't specify a map, pick a random one from the # list of supported ones. - unowned_maps = _maps.get_unowned_maps() + unowned_maps = _map.get_unowned_maps() valid_maps: List[str] = [ m for m in self.get_supported_maps(type(self.session)) if m not in unowned_maps @@ -322,7 +322,7 @@ class GameActivity(Activity): _ba.screenmessage(Lstr(resource='noValidMapsErrorText')) raise Exception("No valid maps") map_name = valid_maps[random.randrange(len(valid_maps))] - self._map_type = _maps.get_map_class(map_name) + self._map_type = _map.get_map_class(map_name) self._map_type.preload() self._map: Optional[ba.Map] = None self._powerup_drop_timer: Optional[ba.Timer] = None diff --git a/assets/src/data/scripts/ba/_maps.py b/assets/src/data/scripts/ba/_map.py similarity index 100% rename from assets/src/data/scripts/ba/_maps.py rename to assets/src/data/scripts/ba/_map.py diff --git a/assets/src/data/scripts/ba/_playlist.py b/assets/src/data/scripts/ba/_playlist.py index f06d013b..33e84f32 100644 --- a/assets/src/data/scripts/ba/_playlist.py +++ b/assets/src/data/scripts/ba/_playlist.py @@ -47,13 +47,13 @@ def filter_playlist(playlist: PlaylistType, # pylint: disable=too-many-branches # pylint: disable=too-many-statements from ba import _meta - from ba import _maps + from ba import _map from ba import _general from ba import _gameactivity goodlist: List[Dict] = [] unowned_maps: Sequence[str] if remove_unowned or mark_unowned: - unowned_maps = _maps.get_unowned_maps() + unowned_maps = _map.get_unowned_maps() unowned_game_types = _meta.get_unowned_game_types() else: unowned_maps = [] @@ -69,7 +69,7 @@ def filter_playlist(playlist: PlaylistType, entry['settings']['map'] = entry['map'] del entry['map'] # update old map names to new ones - entry['settings']['map'] = _maps.get_filtered_map_name( + entry['settings']['map'] = _map.get_filtered_map_name( entry['settings']['map']) if remove_unowned and entry['settings']['map'] in unowned_maps: continue diff --git a/assets/src/data/scripts/ba/_session.py b/assets/src/data/scripts/ba/_session.py index 19ab3b1a..f48008bf 100644 --- a/assets/src/data/scripts/ba/_session.py +++ b/assets/src/data/scripts/ba/_session.py @@ -83,7 +83,7 @@ class Session: players: List[ba.Player] def __init__(self, - depsets: Sequence[ba.DepSet], + depsets: Sequence[ba.DependencySet], team_names: Sequence[str] = None, team_colors: Sequence[Sequence[float]] = None, use_team_colors: bool = True, @@ -94,7 +94,7 @@ class Session: # pylint: disable=too-many-branches """Instantiate a session. - depsets should be a sequence of successfully resolved ba.DepSet + depsets should be a sequence of successfully resolved ba.DependencySet instances; one for each ba.Activity the session may potentially run. """ # pylint: disable=too-many-locals @@ -105,7 +105,7 @@ class Session: from ba._gameactivity import GameActivity from ba._team import Team from ba._error import DependencyError - from ba._dep import Dep, AssetPackage + from ba._dependency import Dependency, AssetPackage print(' WOULD LOOK AT DEP SETS', depsets) @@ -131,7 +131,8 @@ class Session: # throw a combined exception if we found anything missing if missing_asset_packages: raise DependencyError([ - Dep(AssetPackage, set_id) for set_id in missing_asset_packages + Dependency(AssetPackage, set_id) + for set_id in missing_asset_packages ]) # ok; looks like our dependencies check out. diff --git a/assets/src/data/scripts/ba/_store.py b/assets/src/data/scripts/ba/_store.py index d7e570a4..c91d0a85 100644 --- a/assets/src/data/scripts/ba/_store.py +++ b/assets/src/data/scripts/ba/_store.py @@ -40,7 +40,7 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr: """Return a ba.Lstr for a store item name.""" # pylint: disable=cyclic-import from ba import _lang - from ba import _maps + from ba import _map item_info = get_store_item(item_name) if item_name.startswith('characters.'): return _lang.Lstr(translate=('characterNames', item_info['character'])) @@ -50,7 +50,7 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr: _lang.Lstr(resource='titleText'))]) if item_name.startswith('maps.'): map_type: Type[ba.Map] = item_info['map_type'] - return _maps.get_map_display_string(map_type.name) + return _map.get_map_display_string(map_type.name) if item_name.startswith('games.'): gametype: Type[ba.GameActivity] = item_info['gametype'] return gametype.get_display_string() diff --git a/assets/src/data/scripts/ba/_teambasesession.py b/assets/src/data/scripts/ba/_teambasesession.py index 559d5cb0..82e805d0 100644 --- a/assets/src/data/scripts/ba/_teambasesession.py +++ b/assets/src/data/scripts/ba/_teambasesession.py @@ -66,7 +66,7 @@ class TeamBaseSession(Session): team_colors = None print('FIXME: TEAM BASE SESSION WOULD CALC DEPS.') - depsets: Sequence[ba.DepSet] = [] + depsets: Sequence[ba.DependencySet] = [] super().__init__(depsets, team_names=team_names, team_colors=team_colors, diff --git a/assets/src/data/scripts/ba/internal.py b/assets/src/data/scripts/ba/internal.py index ec461d37..e4ac82ab 100644 --- a/assets/src/data/scripts/ba/internal.py +++ b/assets/src/data/scripts/ba/internal.py @@ -27,9 +27,9 @@ defensively) in mods. # pylint: disable=unused-import -from ba._maps import (get_unowned_maps, get_map_class, register_map, - preload_map_preview_media, get_map_display_string, - get_filtered_map_name) +from ba._map import (get_unowned_maps, get_map_class, register_map, + preload_map_preview_media, get_map_display_string, + get_filtered_map_name) from ba._appconfig import commit_app_config from ba._input import (get_device_value, get_input_map_hash, get_input_device_config) diff --git a/assets/src/data/scripts/bafoundation/__init__.py b/assets/src/data/scripts/bafoundation/__init__.py index 23088c51..1a006083 100644 --- a/assets/src/data/scripts/bafoundation/__init__.py +++ b/assets/src/data/scripts/bafoundation/__init__.py @@ -18,4 +18,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # ----------------------------------------------------------------------------- -"""Functionality shared between Ballistica client and server components.""" +"""Functionality shared between all Ballistica clients, servers, and tools.""" diff --git a/assets/src/data/scripts/bafoundation/util.py b/assets/src/data/scripts/bafoundation/util.py index 2c8650d5..8af8f0cd 100644 --- a/assets/src/data/scripts/bafoundation/util.py +++ b/assets/src/data/scripts/bafoundation/util.py @@ -36,7 +36,13 @@ TRET = TypeVar('TRET') def utc_now() -> datetime.datetime: - """Get offset-aware current utc time.""" + """Get offset-aware current utc time. + + This should be used for all datetimes getting sent over the network, + used with the entity system, etc. + (datetime.utcnow() gives a utc time value, but it is not timezone-aware + which makes it less safe to use) + """ return datetime.datetime.now(datetime.timezone.utc) diff --git a/assets/src/data/scripts/bastd/mainmenu.py b/assets/src/data/scripts/bastd/mainmenu.py index ad083671..441c1b46 100644 --- a/assets/src/data/scripts/bastd/mainmenu.py +++ b/assets/src/data/scripts/bastd/mainmenu.py @@ -46,7 +46,7 @@ if TYPE_CHECKING: class MainMenuActivity(ba.Activity): """Activity showing the rotating main menu bg stuff.""" - _stdassets = ba.Dep(ba.AssetPackage, 'stdassets@1') + _stdassets = ba.Dependency(ba.AssetPackage, 'stdassets@1') def on_transition_in(self) -> None: super().on_transition_in() @@ -903,7 +903,7 @@ class MainMenuSession(ba.Session): def __init__(self) -> None: # Gather dependencies we'll need (just our activity). - self._activity_deps = ba.DepSet(ba.Dep(MainMenuActivity)) + self._activity_deps = ba.DependencySet(ba.Dependency(MainMenuActivity)) super().__init__([self._activity_deps]) self._locked = False diff --git a/docs/ba_module.md b/docs/ba_module.md index f3af8e73..f65d20f4 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,6 +1,6 @@ - -

last updated on 2019-11-12 for Ballistica version 1.5.0 build 20001

+ +

last updated on 2019-11-21 for Ballistica version 1.5.0 build 20001

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 let me know. Happy modding!


@@ -150,8 +150,8 @@

Dependency Classes

Enums