From dc6d29de22a19bfe02388e3c555ad12a9125b2b6 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 1 Feb 2024 12:35:00 -0800 Subject: [PATCH] dataclassio multitype fixes/tidying --- .efrocachemap | 40 +++++++++++++------------- CHANGELOG.md | 2 +- src/assets/ba_data/python/baenv.py | 2 +- src/assets/server_package/README.txt | 2 +- src/ballistica/shared/ballistica.cc | 2 +- tests/test_efro/test_dataclassio.py | 43 ++++++++++++++++++++++++++++ tools/efro/dataclassio/_base.py | 13 ++++++--- tools/efro/dataclassio/_inputter.py | 14 ++++++--- 8 files changed, 86 insertions(+), 32 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index bee3b976..f8c75782 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,26 +4060,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "4061f6114403c60ce1553d2b95c6fd7f", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "c73d4beea86082419fa0dee9cf8e93be", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "928c17c937e26d2fdc9edc8bc1e07659", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "07a90a9ee49ccfcf4cb0fcbccd0e1a04", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "aa4929306878a5e757be36bbd80cb3b3", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6579bfe457770767a35ce69513433273", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "0eb96f2dfd3798621dd35d3693e596f7", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "21ff8796c4e459d4087a68d02b998576", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "a69ade336e8d9574919debda154ca2ec", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "ce625368b6392bc3d9c6f033b1255c6f", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0b606c6c6f4774dc9cfebfab347d6271", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "56878ae9435c3b99a62319e016418dff", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "461c5e567f8bf9726a1b7f5ced7e7418", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c6a85daf172fee978c7deafb3b50fc21", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "42121063ca6dc6a446a331b2ea4dc707", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "94a0a0bec30f85b0397fb1925ef54e33", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "f9f983439f6b2a5cc92e2c8c575b66bb", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "ec5f0294584602e88e071eb336b671fd", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "6c49a9829024ff91e2d07dbaf2c75184", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f82d8ed05ca2fa9a3bef869e3ce6ac4b", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "7169141b846e3c15160f3405bd1ed2ce", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "04869210f06ff8c00fb6d68a69f6b335", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "e28eac17f30edddc35c483fbc4e69fc5", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "342aee2d1f2965caed811a72888cc7f1", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "c091240edeeeac378ffe86ce1421decc", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "c959d3661ea83a2f9920da6d6d355fd9", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9015fb2718d6fc5a9a9bd95b0086bde6", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "dfcd11db04c0071b7e5032673c2dad43", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "8a0ad295f62fc9cfa4a71d30038b0c20", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "21f2f9aecbd0c60b8e125f206ebd44c2", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "df5d59cf4b6da427b3e2bb4d1136cf32", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "72f5ab8cd24ce31b8bbfe08ce691bf7b", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "11280b6cbec7a21574f0e8e29c379bea", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "766b8ad86046a92fa5fa25a0a7ad88ae", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "10ac2e99587552edfc3eb089ea69ede4", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8cd63147b79d8866af817d01dcc8f5eb", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "061de20725a847ac6ed327d8c2e5b905", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "6556a6e96f2a360a4006f2c2b7a5a1f1", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "1324063eeaeda2c72f7e133984fa838b", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3c6b96c3a63fc53819425f8872264aa8", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "8709ad96140d71760c2f493ee8bd7c43", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "ee829cd5488e9750570dc6f602d65589", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "8709ad96140d71760c2f493ee8bd7c43", diff --git a/CHANGELOG.md b/CHANGELOG.md index 151e25aa..471b12c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.33 (build 21764, api 8, 2024-01-31) +### 1.7.33 (build 21766, api 8, 2024-02-01) - Stress test input-devices are now a bit smarter; they won't press any buttons while UIs are up (this could cause lots of chaos if it happened). - Added a 'Show Demos When Idle' option in advanced settings. If enabled, the diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index b61f151a..cdcf043b 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21764 +TARGET_BALLISTICA_BUILD = 21766 TARGET_BALLISTICA_VERSION = '1.7.33' diff --git a/src/assets/server_package/README.txt b/src/assets/server_package/README.txt index 407c003c..fe0c8d35 100644 --- a/src/assets/server_package/README.txt +++ b/src/assets/server_package/README.txt @@ -14,7 +14,7 @@ Mac: (brew install python3). Linux (x86_64): -- Server binaries are currently compiled against Ubuntu 20 LTS. +- Server binaries are currently compiled against Ubuntu 22 LTS. Raspberry Pi: - The server binary was compiled on a Raspberry Pi 4 running Raspbian Buster. diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index b1201f9b..4e3a9e8d 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21764; +const int kEngineBuildNumber = 21766; const char* kEngineVersion = "1.7.33"; const int kEngineApiVersion = 8; diff --git a/tests/test_efro/test_dataclassio.py b/tests/test_efro/test_dataclassio.py index 20e603cd..125613e3 100644 --- a/tests/test_efro/test_dataclassio.py +++ b/tests/test_efro/test_dataclassio.py @@ -5,6 +5,7 @@ from __future__ import annotations +import copy import datetime from enum import Enum from dataclasses import field, dataclass @@ -1159,6 +1160,7 @@ class MTTestClass2(MTTestBase): def test_multi_type() -> None: """Test IOMultiType stuff.""" # pylint: disable=too-many-locals + # pylint: disable=too-many-statements # Test converting single instances back and forth. val1: MTTestBase = MTTestClass1(ival=123) @@ -1179,6 +1181,17 @@ def test_multi_type() -> None: assert dataclass_from_dict(MTTestBase, outdict) == val1 assert dataclass_from_dict(MTTestBase, outdict2) == val2 + # Trying to load as a multi-type should fail if there is no type + # value present. + outdictmod = copy.deepcopy(outdict) + del outdictmod[tpname] + with pytest.raises(ValueError): + dataclass_from_dict(MTTestBase, outdictmod) + + # However it should work when loading an exact type. This can be + # necessary to gracefully upgrade old data to multi-type form. + dataclass_from_dict(MTTestClass1, outdictmod) + # Now test our multi-type embedded in other classes. We should be # able to throw a mix of things in there and have them deserialize # back the types we started with. @@ -1253,3 +1266,33 @@ def test_multi_type() -> None: outdict = dataclass_to_dict(container5) container5b = dataclass_from_dict(_TestContainerClass5, outdict) assert container5 == container5b + + # Optionals. + + @ioprepped + @dataclass + class _TestContainerClass6: + obj: MTTestBase | None + + container6 = _TestContainerClass6(obj=None) + outdict = dataclass_to_dict(container6) + container6b = dataclass_from_dict(_TestContainerClass6, outdict) + assert container6 == container6b + + container6 = _TestContainerClass6(obj=MTTestClass2('fwr')) + outdict = dataclass_to_dict(container6) + container6b = dataclass_from_dict(_TestContainerClass6, outdict) + assert container6 == container6b + + @ioprepped + @dataclass + class _TestContainerClass7: + obj: Annotated[ + MTTestBase | None, + IOAttrs('o', soft_default=None), + ] + + container7 = _TestContainerClass7(obj=None) + outdict = dataclass_to_dict(container7) + container7b = dataclass_from_dict(_TestContainerClass7, {}) + assert container7 == container7b diff --git a/tools/efro/dataclassio/_base.py b/tools/efro/dataclassio/_base.py index c1ba8841..eda3243e 100644 --- a/tools/efro/dataclassio/_base.py +++ b/tools/efro/dataclassio/_base.py @@ -69,16 +69,21 @@ class IOMultiType(Generic[EnumT]): This enables usage of high level base classes (for example a 'Message' type) in dataclasses, with dataclassio automatically - serializing/deserializing subclass instances using provided - per-class type-ids. + serializing & deserializing subclass instances based on their + type. - See tests/test_efro/test_dataclassio.py for an example of this. + Standard usage involves creating a class which inherits from this + one that acts as a 'registry', and then creating dataclass classes + inheriting from that registry class. Dataclassio will then do the + right thing when that registry class is used in type annotations. + + See tests/test_efro/test_dataclassio.py for examples. """ # Dataclasses inheriting from an IOMultiType will store a type-id # with this key in their serialized data. This value can be # overridden in IOMultiType subclasses in case of conflicts. - ID_STORAGE_NAME = '_iotype' + ID_STORAGE_NAME = '_dciotype' @classmethod def get_type(cls, type_id: EnumT) -> type[Self]: diff --git a/tools/efro/dataclassio/_inputter.py b/tools/efro/dataclassio/_inputter.py index d6627b25..0e19cba3 100644 --- a/tools/efro/dataclassio/_inputter.py +++ b/tools/efro/dataclassio/_inputter.py @@ -64,9 +64,15 @@ class _Inputter: outcls: type[Any] - # If we're dealing with a multi-type class, figure out the - # top level type we're going to. - if issubclass(self._cls, IOMultiType): + # If we're dealing with a multi-type subclass which is NOT a + # dataclass, we must rely on its stored type to figure out + # what type of dataclass we're going to. If we are a dataclass + # then we already know what type we're going to so we can + # survive without this, which is often necessary when reading + # old data that doesn't have a type id attr yet. + if issubclass(self._cls, IOMultiType) and not dataclasses.is_dataclass( + self._cls + ): type_id_val = values.get(self._cls.ID_STORAGE_NAME) if type_id_val is None: raise ValueError( @@ -265,7 +271,7 @@ class _Inputter: args: dict[str, Any] = {} for rawkey, value in values.items(): - # Ignore _iotype or whatnot. + # Ignore _dciotype or whatnot. if type_id_store_name is not None and rawkey == type_id_store_name: continue