sonolus.py 0.11.1__py3-none-any.whl → 0.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/visitor.py +2 -0
- sonolus/build/compile.py +4 -3
- sonolus/script/archetype.py +137 -25
- sonolus/script/debug.py +9 -1
- sonolus/script/internal/builtin_impls.py +78 -2
- sonolus/script/internal/context.py +46 -5
- sonolus/script/internal/introspection.py +8 -1
- {sonolus_py-0.11.1.dist-info → sonolus_py-0.12.1.dist-info}/METADATA +2 -2
- {sonolus_py-0.11.1.dist-info → sonolus_py-0.12.1.dist-info}/RECORD +12 -12
- {sonolus_py-0.11.1.dist-info → sonolus_py-0.12.1.dist-info}/WHEEL +0 -0
- {sonolus_py-0.11.1.dist-info → sonolus_py-0.12.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.11.1.dist-info → sonolus_py-0.12.1.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/visitor.py
CHANGED
|
@@ -1308,6 +1308,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1308
1308
|
raise NotImplementedError(f"Unsupported syntax: {type(node).__name__}")
|
|
1309
1309
|
|
|
1310
1310
|
def handle_getattr(self, node: ast.stmt | ast.expr, target: Value, key: str) -> Value:
|
|
1311
|
+
# If this is changed, remember to update the getattr impl too
|
|
1311
1312
|
with self.reporting_errors_at_node(node):
|
|
1312
1313
|
if isinstance(target, ConstantValue):
|
|
1313
1314
|
# Unwrap so we can access fields
|
|
@@ -1328,6 +1329,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1328
1329
|
raise TypeError(f"Unsupported field or descriptor {key}")
|
|
1329
1330
|
|
|
1330
1331
|
def handle_setattr(self, node: ast.stmt | ast.expr, target: Value, key: str, value: Value):
|
|
1332
|
+
# If this is changed, remember to update the setattr impl too
|
|
1331
1333
|
with self.reporting_errors_at_node(node):
|
|
1332
1334
|
if target._is_py_():
|
|
1333
1335
|
target = target._as_py_()
|
sonolus/build/compile.py
CHANGED
|
@@ -80,7 +80,7 @@ def compile_mode(
|
|
|
80
80
|
|
|
81
81
|
mode_state = ModeContextState(
|
|
82
82
|
mode,
|
|
83
|
-
|
|
83
|
+
archetypes,
|
|
84
84
|
)
|
|
85
85
|
nodes = OutputNodeGenerator()
|
|
86
86
|
results = {}
|
|
@@ -147,9 +147,10 @@ def compile_mode(
|
|
|
147
147
|
archetype_data["exports"] = [*archetype._exported_keys_]
|
|
148
148
|
|
|
149
149
|
callback_items = [
|
|
150
|
-
(cb_name, cb_info,
|
|
150
|
+
(cb_name, cb_info, archetype._callbacks_[cb_name])
|
|
151
151
|
for cb_name, cb_info in archetype._supported_callbacks_.items()
|
|
152
|
-
if
|
|
152
|
+
if cb_name in archetype._callbacks_
|
|
153
|
+
and archetype._callbacks_[cb_name] not in archetype._default_callbacks_
|
|
153
154
|
]
|
|
154
155
|
|
|
155
156
|
if thread_pool is not None:
|
sonolus/script/archetype.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
from abc import abstractmethod
|
|
5
|
-
from collections.abc import Callable
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from enum import Enum, StrEnum
|
|
8
8
|
from types import FunctionType
|
|
@@ -11,8 +11,9 @@ from typing import Annotated, Any, ClassVar, Self, TypedDict, get_origin
|
|
|
11
11
|
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr, IRStmt
|
|
12
12
|
from sonolus.backend.mode import Mode
|
|
13
13
|
from sonolus.backend.ops import Op
|
|
14
|
+
from sonolus.backend.visitor import compile_and_call
|
|
14
15
|
from sonolus.script.bucket import Bucket, Judgment
|
|
15
|
-
from sonolus.script.debug import static_error
|
|
16
|
+
from sonolus.script.debug import runtime_checks_enabled, static_error
|
|
16
17
|
from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
17
18
|
from sonolus.script.internal.context import ctx
|
|
18
19
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
@@ -208,7 +209,7 @@ class _IsScoredDescriptor(SonolusDescriptor):
|
|
|
208
209
|
if instance is None:
|
|
209
210
|
return self.value
|
|
210
211
|
elif ctx():
|
|
211
|
-
return ctx().mode_state.is_scored_by_archetype_id
|
|
212
|
+
return ctx().mode_state.is_scored_by_archetype_id.get_unchecked(instance.id)
|
|
212
213
|
else:
|
|
213
214
|
return self.value
|
|
214
215
|
|
|
@@ -222,7 +223,7 @@ class _IdDescriptor(SonolusDescriptor):
|
|
|
222
223
|
raise RuntimeError("Archetype id is only available during compilation")
|
|
223
224
|
if instance is None:
|
|
224
225
|
result = ctx().mode_state.archetypes.get(owner)
|
|
225
|
-
if result is None:
|
|
226
|
+
if result is None or owner in ctx().mode_state.compile_time_only_archetypes:
|
|
226
227
|
raise RuntimeError("Archetype is not registered")
|
|
227
228
|
return result
|
|
228
229
|
else:
|
|
@@ -238,7 +239,7 @@ class _KeyDescriptor(SonolusDescriptor):
|
|
|
238
239
|
|
|
239
240
|
def __get__(self, instance, owner):
|
|
240
241
|
if instance is not None and ctx():
|
|
241
|
-
return ctx().mode_state.keys_by_archetype_id
|
|
242
|
+
return ctx().mode_state.keys_by_archetype_id.get_unchecked(instance.id)
|
|
242
243
|
else:
|
|
243
244
|
return self.value
|
|
244
245
|
|
|
@@ -428,7 +429,7 @@ class _BaseArchetype:
|
|
|
428
429
|
|
|
429
430
|
_imported_keys_: ClassVar[dict[str, int]]
|
|
430
431
|
_exported_keys_: ClassVar[dict[str, int]]
|
|
431
|
-
_callbacks_: ClassVar[
|
|
432
|
+
_callbacks_: ClassVar[dict[str, Callable]]
|
|
432
433
|
_data_constructor_signature_: ClassVar[inspect.Signature]
|
|
433
434
|
_spawn_signature_: ClassVar[inspect.Signature]
|
|
434
435
|
|
|
@@ -492,17 +493,67 @@ class _BaseArchetype:
|
|
|
492
493
|
|
|
493
494
|
@classmethod
|
|
494
495
|
@meta_fn
|
|
495
|
-
def
|
|
496
|
+
def _compile_time_id(cls):
|
|
497
|
+
if not ctx():
|
|
498
|
+
raise RuntimeError("Archetype id is only available during compilation")
|
|
499
|
+
result = ctx().mode_state.archetypes.get(cls)
|
|
500
|
+
if result is None:
|
|
501
|
+
raise RuntimeError("Archetype is not registered")
|
|
502
|
+
return result
|
|
503
|
+
|
|
504
|
+
@classmethod
|
|
505
|
+
@meta_fn
|
|
506
|
+
def at(cls, index: int, check: bool = True) -> Self:
|
|
507
|
+
"""Access the entity of this archetype at the given index.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
index: The index of the entity to reference.
|
|
511
|
+
check: If true, raises an error if the entity at the index is not of this archetype or of a subclass of
|
|
512
|
+
this archetype. If false, no validation is performed.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
The entity at the given index.
|
|
516
|
+
"""
|
|
517
|
+
if not ctx():
|
|
518
|
+
raise RuntimeError("Archetype.at is only available during compilation")
|
|
519
|
+
compile_and_call(cls._check_is_at, index, check=check)
|
|
496
520
|
result = cls._new()
|
|
497
521
|
result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
|
|
498
522
|
return result
|
|
499
523
|
|
|
500
524
|
@classmethod
|
|
525
|
+
def is_at(cls, index: int, strict: bool = False) -> bool:
|
|
526
|
+
"""Return whether the entity at the given index is of this archetype.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
index: The index of the entity to check.
|
|
530
|
+
strict: If true, only returns true if the entity is exactly of this archetype. If false, also returns true
|
|
531
|
+
if the entity is of a subclass of this archetype.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Whether the entity at the given index is of this archetype.
|
|
535
|
+
"""
|
|
536
|
+
if strict:
|
|
537
|
+
return index >= 0 and cls._compile_time_id() == entity_info_at(index).archetype_id
|
|
538
|
+
else:
|
|
539
|
+
mro_ids = cls._get_mro_id_array(entity_info_at(index).archetype_id)
|
|
540
|
+
return index >= 0 and cls._compile_time_id() in mro_ids
|
|
541
|
+
|
|
542
|
+
@classmethod
|
|
543
|
+
def _check_is_at(cls, index: int, check: bool):
|
|
544
|
+
if not check or not runtime_checks_enabled():
|
|
545
|
+
return
|
|
546
|
+
assert index >= 0, "Entity index must be non-negative"
|
|
547
|
+
assert cls._compile_time_id() in cls._get_mro_id_array(entity_info_at(index).archetype_id), (
|
|
548
|
+
"Entity at index is not of the expected archetype"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
@staticmethod
|
|
501
552
|
@meta_fn
|
|
502
|
-
def
|
|
553
|
+
def _get_mro_id_array(archetype_id: int) -> Sequence[int]:
|
|
503
554
|
if not ctx():
|
|
504
|
-
raise RuntimeError("
|
|
505
|
-
return
|
|
555
|
+
raise RuntimeError("Archetype._get_mro_id_array is only available during compilation")
|
|
556
|
+
return ctx().get_archetype_mro_id_array(archetype_id)
|
|
506
557
|
|
|
507
558
|
@classmethod
|
|
508
559
|
@meta_fn
|
|
@@ -563,13 +614,16 @@ class _BaseArchetype:
|
|
|
563
614
|
return
|
|
564
615
|
if cls.name is None or cls.name in {getattr(mro_entry, "name", None) for mro_entry in cls.mro()[1:]}:
|
|
565
616
|
cls.name = cls.__name__.removeprefix(cls._removable_prefix)
|
|
566
|
-
cls._callbacks_ =
|
|
617
|
+
cls._callbacks_ = {}
|
|
567
618
|
for name in cls._supported_callbacks_:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
619
|
+
for mro_entry in cls.mro():
|
|
620
|
+
if name in mro_entry.__dict__:
|
|
621
|
+
cb = mro_entry.__dict__[name]
|
|
622
|
+
if cb not in cls._default_callbacks_:
|
|
623
|
+
cls._callbacks_[name] = cb
|
|
624
|
+
break
|
|
572
625
|
cls._field_init_done = False
|
|
626
|
+
cls._is_concrete_archetype_ = True
|
|
573
627
|
cls.id = _IdDescriptor()
|
|
574
628
|
cls._key_ = cls.key
|
|
575
629
|
cls.key = _KeyDescriptor(cls.key)
|
|
@@ -585,6 +639,14 @@ class _BaseArchetype:
|
|
|
585
639
|
for mro_entry in cls.mro()[1:]:
|
|
586
640
|
if hasattr(mro_entry, "_field_init_done"):
|
|
587
641
|
mro_entry._init_fields()
|
|
642
|
+
if sum(issubclass(base, _BaseArchetype) for base in cls.__bases__) > 1:
|
|
643
|
+
raise TypeError("Multiple inheritance of Archetypes is not supported")
|
|
644
|
+
mro_from_archetype_parents = set(
|
|
645
|
+
type("Dummy", tuple(base for base in cls.__bases__ if issubclass(base, _BaseArchetype)), {}).mro()
|
|
646
|
+
)
|
|
647
|
+
# Archetype parents would have already initialized relevant fields, so only consider the current class
|
|
648
|
+
# and mixins that were not already included via an archetype parent
|
|
649
|
+
mro_excluding_archetype_parents = [entry for entry in cls.mro() if entry not in mro_from_archetype_parents]
|
|
588
650
|
field_specifiers = get_field_specifiers(
|
|
589
651
|
cls,
|
|
590
652
|
skip={
|
|
@@ -600,7 +662,9 @@ class _BaseArchetype:
|
|
|
600
662
|
"_default_callbacks_",
|
|
601
663
|
"_callbacks_",
|
|
602
664
|
"_field_init_done",
|
|
665
|
+
"_is_concrete_archetype_",
|
|
603
666
|
},
|
|
667
|
+
included_classes=mro_excluding_archetype_parents,
|
|
604
668
|
).items()
|
|
605
669
|
if not hasattr(cls, "_imported_fields_"):
|
|
606
670
|
cls._imported_fields_ = {}
|
|
@@ -653,6 +717,13 @@ class _BaseArchetype:
|
|
|
653
717
|
f"Missing field annotation for '{name}', "
|
|
654
718
|
f"expected exactly one of imported, exported, entity_memory, or shared_memory"
|
|
655
719
|
)
|
|
720
|
+
if (
|
|
721
|
+
name in cls._imported_fields_
|
|
722
|
+
or name in cls._exported_fields_
|
|
723
|
+
or name in cls._memory_fields_
|
|
724
|
+
or name in cls._shared_memory_fields_
|
|
725
|
+
):
|
|
726
|
+
raise ValueError(f"Field '{name}' is already defined in a superclass")
|
|
656
727
|
field_type = validate_concrete_type(value.__args__[0])
|
|
657
728
|
match field_info.storage:
|
|
658
729
|
case _StorageType.IMPORTED:
|
|
@@ -771,6 +842,18 @@ class _BaseArchetype:
|
|
|
771
842
|
new_cls = type(name, (cls,), cls_dict)
|
|
772
843
|
return new_cls
|
|
773
844
|
|
|
845
|
+
@meta_fn
|
|
846
|
+
def _delegate(self, name: str, default: int | float | None = None):
|
|
847
|
+
name = validate_value(name)._as_py_()
|
|
848
|
+
if hasattr(super(), name):
|
|
849
|
+
fn = getattr(super(), name)
|
|
850
|
+
if ctx():
|
|
851
|
+
return compile_and_call(fn)
|
|
852
|
+
else:
|
|
853
|
+
return fn()
|
|
854
|
+
else:
|
|
855
|
+
return default
|
|
856
|
+
|
|
774
857
|
|
|
775
858
|
class PlayArchetype(_BaseArchetype):
|
|
776
859
|
"""Base class for play mode archetypes.
|
|
@@ -808,26 +891,28 @@ class PlayArchetype(_BaseArchetype):
|
|
|
808
891
|
|
|
809
892
|
Runs first when the level is loaded.
|
|
810
893
|
"""
|
|
894
|
+
self._delegate("preprocess")
|
|
811
895
|
|
|
812
896
|
def spawn_order(self) -> float:
|
|
813
897
|
"""Return the spawn order of the entity.
|
|
814
898
|
|
|
815
899
|
Runs when the level is loaded after [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess].
|
|
816
900
|
"""
|
|
817
|
-
return 0.0
|
|
901
|
+
return self._delegate("spawn_order", default=0.0)
|
|
818
902
|
|
|
819
903
|
def should_spawn(self) -> bool:
|
|
820
904
|
"""Return whether the entity should be spawned.
|
|
821
905
|
|
|
822
906
|
Runs each frame while the entity is the first entity in the spawn queue.
|
|
823
907
|
"""
|
|
824
|
-
return True
|
|
908
|
+
return self._delegate("should_spawn", default=True)
|
|
825
909
|
|
|
826
910
|
def initialize(self):
|
|
827
911
|
"""Initialize this entity.
|
|
828
912
|
|
|
829
913
|
Runs when this entity is spawned.
|
|
830
914
|
"""
|
|
915
|
+
self._delegate("initialize")
|
|
831
916
|
|
|
832
917
|
def update_sequential(self):
|
|
833
918
|
"""Perform non-parallel actions for this frame.
|
|
@@ -839,6 +924,7 @@ class PlayArchetype(_BaseArchetype):
|
|
|
839
924
|
[`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel]
|
|
840
925
|
for better performance.
|
|
841
926
|
"""
|
|
927
|
+
self._delegate("update_sequential")
|
|
842
928
|
|
|
843
929
|
def update_parallel(self):
|
|
844
930
|
"""Perform parallel actions for this frame.
|
|
@@ -847,18 +933,21 @@ class PlayArchetype(_BaseArchetype):
|
|
|
847
933
|
|
|
848
934
|
This is where most gameplay logic should be placed.
|
|
849
935
|
"""
|
|
936
|
+
self._delegate("update_parallel")
|
|
850
937
|
|
|
851
938
|
def touch(self):
|
|
852
939
|
"""Handle user input.
|
|
853
940
|
|
|
854
941
|
Runs after [`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential] each frame.
|
|
855
942
|
"""
|
|
943
|
+
self._delegate("touch")
|
|
856
944
|
|
|
857
945
|
def terminate(self):
|
|
858
946
|
"""Finalize before despawning.
|
|
859
947
|
|
|
860
948
|
Runs when the entity is despawned.
|
|
861
949
|
"""
|
|
950
|
+
self._delegate("terminate")
|
|
862
951
|
|
|
863
952
|
@property
|
|
864
953
|
@meta_fn
|
|
@@ -966,20 +1055,22 @@ class WatchArchetype(_BaseArchetype):
|
|
|
966
1055
|
|
|
967
1056
|
Runs first when the level is loaded.
|
|
968
1057
|
"""
|
|
1058
|
+
self._delegate("preprocess")
|
|
969
1059
|
|
|
970
1060
|
def spawn_time(self) -> float:
|
|
971
1061
|
"""Return the spawn time of the entity."""
|
|
972
|
-
return 0.0
|
|
1062
|
+
return self._delegate("spawn_time", default=0.0)
|
|
973
1063
|
|
|
974
1064
|
def despawn_time(self) -> float:
|
|
975
1065
|
"""Return the despawn time of the entity."""
|
|
976
|
-
return 0.0
|
|
1066
|
+
return self._delegate("despawn_time", default=0.0)
|
|
977
1067
|
|
|
978
1068
|
def initialize(self):
|
|
979
1069
|
"""Initialize this entity.
|
|
980
1070
|
|
|
981
1071
|
Runs when this entity is spawned.
|
|
982
1072
|
"""
|
|
1073
|
+
self._delegate("initialize")
|
|
983
1074
|
|
|
984
1075
|
def update_sequential(self):
|
|
985
1076
|
"""Perform non-parallel actions for this frame.
|
|
@@ -990,6 +1081,7 @@ class WatchArchetype(_BaseArchetype):
|
|
|
990
1081
|
Other logic should typically be placed in
|
|
991
1082
|
[`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel] for better performance.
|
|
992
1083
|
"""
|
|
1084
|
+
self._delegate("update_sequential")
|
|
993
1085
|
|
|
994
1086
|
def update_parallel(self):
|
|
995
1087
|
"""Parallel update callback.
|
|
@@ -998,12 +1090,14 @@ class WatchArchetype(_BaseArchetype):
|
|
|
998
1090
|
|
|
999
1091
|
This is where most gameplay logic should be placed.
|
|
1000
1092
|
"""
|
|
1093
|
+
self._delegate("update_parallel")
|
|
1001
1094
|
|
|
1002
1095
|
def terminate(self):
|
|
1003
1096
|
"""Finalize before despawning.
|
|
1004
1097
|
|
|
1005
1098
|
Runs when the entity is despawned.
|
|
1006
1099
|
"""
|
|
1100
|
+
self._delegate("terminate")
|
|
1007
1101
|
|
|
1008
1102
|
@property
|
|
1009
1103
|
@meta_fn
|
|
@@ -1074,12 +1168,14 @@ class PreviewArchetype(_BaseArchetype):
|
|
|
1074
1168
|
|
|
1075
1169
|
Runs first when the level is loaded.
|
|
1076
1170
|
"""
|
|
1171
|
+
self._delegate("preprocess")
|
|
1077
1172
|
|
|
1078
1173
|
def render(self):
|
|
1079
1174
|
"""Render the entity.
|
|
1080
1175
|
|
|
1081
1176
|
Runs after `preprocess`.
|
|
1082
1177
|
"""
|
|
1178
|
+
self._delegate("render")
|
|
1083
1179
|
|
|
1084
1180
|
@property
|
|
1085
1181
|
@meta_fn
|
|
@@ -1248,11 +1344,19 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1248
1344
|
return True
|
|
1249
1345
|
|
|
1250
1346
|
@meta_fn
|
|
1251
|
-
def get(self) -> A:
|
|
1252
|
-
"""Get the entity.
|
|
1347
|
+
def get(self, *, check: bool = True) -> A:
|
|
1348
|
+
"""Get the entity this reference points to.
|
|
1349
|
+
|
|
1350
|
+
Args:
|
|
1351
|
+
check: If true, raises an error if the referenced entity is not of this archetype or of a subclass of
|
|
1352
|
+
this archetype. If false, no validation is performed.
|
|
1353
|
+
|
|
1354
|
+
Returns:
|
|
1355
|
+
The entity this reference points to.
|
|
1356
|
+
"""
|
|
1253
1357
|
if ref := getattr(self, "_ref_", None):
|
|
1254
1358
|
return ref
|
|
1255
|
-
return self.archetype().at(self.index)
|
|
1359
|
+
return self.archetype().at(self.index, check=check)
|
|
1256
1360
|
|
|
1257
1361
|
@meta_fn
|
|
1258
1362
|
def get_as(self, archetype: type[_BaseArchetype]) -> _BaseArchetype:
|
|
@@ -1261,9 +1365,17 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1261
1365
|
raise TypeError("Using get_as in level data is not supported.")
|
|
1262
1366
|
return self.with_archetype(archetype).get()
|
|
1263
1367
|
|
|
1264
|
-
def archetype_matches(self) -> bool:
|
|
1265
|
-
"""Check if entity at the index is
|
|
1266
|
-
|
|
1368
|
+
def archetype_matches(self, strict: bool = False) -> bool:
|
|
1369
|
+
"""Check if entity at the index is of this archetype.
|
|
1370
|
+
|
|
1371
|
+
Args:
|
|
1372
|
+
strict: If true, only returns true if the entity is exactly of this archetype. If false, also returns true
|
|
1373
|
+
if the entity is of a subclass of this archetype.
|
|
1374
|
+
|
|
1375
|
+
Returns:
|
|
1376
|
+
Whether the entity at the given index is of this archetype.
|
|
1377
|
+
"""
|
|
1378
|
+
return self.archetype().is_at(self.index, strict=strict)
|
|
1267
1379
|
|
|
1268
1380
|
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
1269
1381
|
ref = getattr(self, "_ref_", None)
|
sonolus/script/debug.py
CHANGED
|
@@ -102,6 +102,14 @@ def notify(message: str):
|
|
|
102
102
|
print(f"[NOTIFY] {message}")
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
@meta_fn
|
|
106
|
+
def runtime_checks_enabled() -> bool:
|
|
107
|
+
if ctx():
|
|
108
|
+
return ctx().project_state.runtime_checks != RuntimeChecks.NONE
|
|
109
|
+
else:
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
|
|
105
113
|
@meta_fn
|
|
106
114
|
def require(value: int | float | bool, message: str | None = None):
|
|
107
115
|
"""Require a condition to be true, or raise an error.
|
|
@@ -212,7 +220,7 @@ def visualize_cfg(
|
|
|
212
220
|
project_state = ProjectContextState()
|
|
213
221
|
mode_state = ModeContextState(
|
|
214
222
|
mode,
|
|
215
|
-
|
|
223
|
+
archetypes,
|
|
216
224
|
)
|
|
217
225
|
|
|
218
226
|
cfg = callback_to_cfg(project_state, mode_state, fn, callback, archetype=archetype) # type: ignore
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from types import FunctionType
|
|
2
|
+
from typing import Any, Never, assert_never
|
|
2
3
|
|
|
3
4
|
from sonolus.backend.ops import Op
|
|
4
5
|
from sonolus.script.array import Array
|
|
@@ -389,6 +390,78 @@ def _super(*args):
|
|
|
389
390
|
return super(*(arg._as_py_() if arg._is_py_() else arg for arg in args))
|
|
390
391
|
|
|
391
392
|
|
|
393
|
+
@meta_fn
|
|
394
|
+
def _hasattr(obj: Any, name: str) -> bool:
|
|
395
|
+
from sonolus.script.internal.constant import ConstantValue
|
|
396
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
397
|
+
|
|
398
|
+
name = validate_value(name)._as_py_()
|
|
399
|
+
if isinstance(obj, ConstantValue):
|
|
400
|
+
# Unwrap so we can access fields
|
|
401
|
+
obj = obj._as_py_()
|
|
402
|
+
descriptor = None
|
|
403
|
+
for cls in type.mro(type(obj)):
|
|
404
|
+
descriptor = cls.__dict__.get(name, None)
|
|
405
|
+
if descriptor is not None:
|
|
406
|
+
break
|
|
407
|
+
# We want to mirror what getattr supports and fail fast if a future getattr would fail.
|
|
408
|
+
match descriptor:
|
|
409
|
+
case None:
|
|
410
|
+
return hasattr(obj, name)
|
|
411
|
+
case property() | SonolusDescriptor() | FunctionType() | classmethod() | staticmethod():
|
|
412
|
+
return True
|
|
413
|
+
case non_descriptor if not hasattr(non_descriptor, "__get__"):
|
|
414
|
+
return True
|
|
415
|
+
case _:
|
|
416
|
+
raise TypeError(f"Unsupported field or descriptor {name}")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@meta_fn
|
|
420
|
+
def _getattr(obj: Any, name: str, default=_empty) -> Any:
|
|
421
|
+
from sonolus.backend.visitor import compile_and_call
|
|
422
|
+
from sonolus.script.internal.constant import ConstantValue
|
|
423
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
424
|
+
|
|
425
|
+
name = validate_value(name)._as_py_()
|
|
426
|
+
if isinstance(obj, ConstantValue):
|
|
427
|
+
# Unwrap so we can access fields
|
|
428
|
+
obj = obj._as_py_()
|
|
429
|
+
descriptor = None
|
|
430
|
+
for cls in type.mro(type(obj)):
|
|
431
|
+
descriptor = cls.__dict__.get(name, None)
|
|
432
|
+
if descriptor is not None:
|
|
433
|
+
break
|
|
434
|
+
match descriptor:
|
|
435
|
+
case property(fget=getter):
|
|
436
|
+
return compile_and_call(getter, obj)
|
|
437
|
+
case SonolusDescriptor() | FunctionType() | classmethod() | staticmethod() | None:
|
|
438
|
+
return validate_value(getattr(obj, name) if default is _empty else getattr(obj, name, default))
|
|
439
|
+
case non_descriptor if not hasattr(non_descriptor, "__get__"):
|
|
440
|
+
return validate_value(getattr(obj, name) if default is _empty else getattr(obj, name, default))
|
|
441
|
+
case _:
|
|
442
|
+
raise TypeError(f"Unsupported field or descriptor {name}")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@meta_fn
|
|
446
|
+
def _setattr(obj: Any, name: str, value: Any):
|
|
447
|
+
from sonolus.backend.visitor import compile_and_call
|
|
448
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
449
|
+
|
|
450
|
+
name = validate_value(name)._as_py_()
|
|
451
|
+
if obj._is_py_():
|
|
452
|
+
obj = obj._as_py_()
|
|
453
|
+
descriptor = getattr(type(obj), name, None)
|
|
454
|
+
match descriptor:
|
|
455
|
+
case property(fset=setter):
|
|
456
|
+
if setter is None:
|
|
457
|
+
raise AttributeError(f"Cannot set attribute {name} because property has no setter")
|
|
458
|
+
compile_and_call(setter, obj, value)
|
|
459
|
+
case SonolusDescriptor():
|
|
460
|
+
setattr(obj, name, value)
|
|
461
|
+
case _:
|
|
462
|
+
raise TypeError(f"Unsupported field or descriptor {name}")
|
|
463
|
+
|
|
464
|
+
|
|
392
465
|
class _Type(Record):
|
|
393
466
|
@meta_fn
|
|
394
467
|
def __call__(self, value, /):
|
|
@@ -412,12 +485,13 @@ BUILTIN_IMPLS = {
|
|
|
412
485
|
id(abs): _abs,
|
|
413
486
|
id(all): _all,
|
|
414
487
|
id(any): _any,
|
|
415
|
-
id(sum): _sum,
|
|
416
488
|
id(bool): _bool,
|
|
417
489
|
id(callable): _callable,
|
|
418
490
|
id(enumerate): _enumerate,
|
|
419
491
|
id(filter): _filter,
|
|
420
492
|
id(float): _float,
|
|
493
|
+
id(getattr): _getattr,
|
|
494
|
+
id(hasattr): _hasattr,
|
|
421
495
|
id(int): _int,
|
|
422
496
|
id(isinstance): _isinstance,
|
|
423
497
|
id(iter): _iter,
|
|
@@ -428,6 +502,8 @@ BUILTIN_IMPLS = {
|
|
|
428
502
|
id(next): _next,
|
|
429
503
|
id(range): Range,
|
|
430
504
|
id(reversed): _reversed,
|
|
505
|
+
id(setattr): _setattr,
|
|
506
|
+
id(sum): _sum,
|
|
431
507
|
id(super): _super,
|
|
432
508
|
id(type): _type,
|
|
433
509
|
id(zip): _zip,
|
|
@@ -94,25 +94,36 @@ class ProjectContextState:
|
|
|
94
94
|
|
|
95
95
|
class ModeContextState:
|
|
96
96
|
archetypes: dict[type, int]
|
|
97
|
+
compile_time_only_archetypes: set[type]
|
|
97
98
|
archetypes_by_name: dict[str, type]
|
|
98
99
|
keys_by_archetype_id: Sequence[int]
|
|
99
100
|
is_scored_by_archetype_id: Sequence[bool]
|
|
101
|
+
archetype_mro_id_array_rom_indexes: Sequence[int] | None = None
|
|
100
102
|
environment_mappings: dict[_GlobalInfo, int]
|
|
101
103
|
environment_offsets: dict[Block, int]
|
|
102
104
|
mode: Mode
|
|
103
105
|
lock: Lock
|
|
104
106
|
|
|
105
|
-
def __init__(self, mode: Mode, archetypes:
|
|
107
|
+
def __init__(self, mode: Mode, archetypes: list[type] | None = None):
|
|
106
108
|
from sonolus.script.array import Array
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
archetypes = [*archetypes] if archetypes is not None else []
|
|
111
|
+
seen_archetypes = {*archetypes}
|
|
112
|
+
compile_time_only_archetypes = set()
|
|
113
|
+
for type_ in [*archetypes]:
|
|
114
|
+
for entry in type_.mro():
|
|
115
|
+
if getattr(entry, "_is_concrete_archetype_", False) and entry not in seen_archetypes:
|
|
116
|
+
archetypes.append(entry)
|
|
117
|
+
seen_archetypes.add(entry)
|
|
118
|
+
compile_time_only_archetypes.add(entry)
|
|
119
|
+
self.archetypes = {type_: idx for idx, type_ in enumerate(archetypes)}
|
|
120
|
+
self.compile_time_only_archetypes = compile_time_only_archetypes
|
|
109
121
|
self.archetypes_by_name = {type_.name: type_ for type_, _ in self.archetypes.items()} # type: ignore
|
|
110
|
-
ordered_archetypes = sorted(self.archetypes, key=lambda a: self.archetypes[a])
|
|
111
122
|
self.keys_by_archetype_id = (
|
|
112
|
-
Array(*((getattr(a, "_key_", -1)) for a in
|
|
123
|
+
Array(*((getattr(a, "_key_", -1)) for a in archetypes)) if archetypes else Array[int, Literal[0]]()
|
|
113
124
|
)
|
|
114
125
|
self.is_scored_by_archetype_id = (
|
|
115
|
-
Array(*((getattr(a, "_is_scored_", False)) for a in
|
|
126
|
+
Array(*((getattr(a, "_is_scored_", False)) for a in archetypes))
|
|
116
127
|
if archetypes
|
|
117
128
|
else Array[bool, Literal[0]]()
|
|
118
129
|
)
|
|
@@ -121,6 +132,27 @@ class ModeContextState:
|
|
|
121
132
|
self.mode = mode
|
|
122
133
|
self.lock = Lock()
|
|
123
134
|
|
|
135
|
+
def _init_archetype_mro_info(self, rom: ReadOnlyMemory):
|
|
136
|
+
from sonolus.script.array import Array
|
|
137
|
+
from sonolus.script.num import Num
|
|
138
|
+
|
|
139
|
+
with self.lock:
|
|
140
|
+
if self.archetype_mro_id_array_rom_indexes is not None:
|
|
141
|
+
return
|
|
142
|
+
archetype_mro_id_values = []
|
|
143
|
+
archetype_mro_id_offsets = []
|
|
144
|
+
for type_ in self.archetypes:
|
|
145
|
+
mro_ids = [self.archetypes[entry] for entry in type_.mro() if entry in self.archetypes]
|
|
146
|
+
archetype_mro_id_offsets.append(len(archetype_mro_id_values))
|
|
147
|
+
archetype_mro_id_values.append(len(mro_ids))
|
|
148
|
+
archetype_mro_id_values.extend(mro_ids)
|
|
149
|
+
archetype_mro_id_array_place = rom[tuple(archetype_mro_id_values)]
|
|
150
|
+
|
|
151
|
+
archetype_mro_id_rom_indexes = Array[int, len(archetype_mro_id_offsets)]._with_value(
|
|
152
|
+
[Num._accept_(offset + archetype_mro_id_array_place.index) for offset in archetype_mro_id_offsets]
|
|
153
|
+
)
|
|
154
|
+
self.archetype_mro_id_array_rom_indexes = archetype_mro_id_rom_indexes
|
|
155
|
+
|
|
124
156
|
|
|
125
157
|
class CallbackContextState:
|
|
126
158
|
callback: str
|
|
@@ -371,6 +403,15 @@ class Context:
|
|
|
371
403
|
self.mode_state.archetypes[type_] = len(self.mode_state.archetypes)
|
|
372
404
|
return self.mode_state.archetypes[type_]
|
|
373
405
|
|
|
406
|
+
def get_archetype_mro_id_array(self, archetype_id: int) -> Sequence[int]:
|
|
407
|
+
from sonolus.script.containers import ArrayPointer
|
|
408
|
+
from sonolus.script.num import Num
|
|
409
|
+
from sonolus.script.pointer import _deref
|
|
410
|
+
|
|
411
|
+
self.mode_state._init_archetype_mro_info(self.rom)
|
|
412
|
+
rom_index = self.mode_state.archetype_mro_id_array_rom_indexes[archetype_id]
|
|
413
|
+
return ArrayPointer[int](_deref(self.blocks.EngineRom, rom_index, Num), self.blocks.EngineRom, rom_index + 1)
|
|
414
|
+
|
|
374
415
|
|
|
375
416
|
def ctx() -> Context | Any: # Using Any to silence type checker warnings if it's None
|
|
376
417
|
return context_var.get()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
from collections.abc import Sequence
|
|
2
3
|
from typing import Annotated
|
|
3
4
|
|
|
4
5
|
_missing = object()
|
|
@@ -11,9 +12,15 @@ def get_field_specifiers(
|
|
|
11
12
|
globals=None, # noqa: A002
|
|
12
13
|
locals=None, # noqa: A002
|
|
13
14
|
eval_str=True,
|
|
15
|
+
included_classes: Sequence[type] | None = None,
|
|
14
16
|
):
|
|
15
17
|
"""Like inspect.get_annotations, but also turns class attributes into Annotated."""
|
|
16
|
-
|
|
18
|
+
if included_classes is not None:
|
|
19
|
+
results = {}
|
|
20
|
+
for entry in reversed(included_classes):
|
|
21
|
+
results.update(inspect.get_annotations(entry, eval_str=eval_str))
|
|
22
|
+
else:
|
|
23
|
+
results = inspect.get_annotations(cls, globals=globals, locals=locals, eval_str=eval_str)
|
|
17
24
|
for key, value in results.items():
|
|
18
25
|
class_value = getattr(cls, key, _missing)
|
|
19
26
|
if class_value is not _missing and key not in skip:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sonolus.py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.1
|
|
4
4
|
Summary: Sonolus engine development in Python
|
|
5
5
|
Project-URL: Documentation, https://sonolus.py.qwewqa.xyz/
|
|
6
6
|
Project-URL: Repository, https://github.com/qwewqa/sonolus.py
|
|
7
7
|
Project-URL: Issues, https://github.com/qwewqa/sonolus.py/issues
|
|
8
8
|
Project-URL: Changelog, https://sonolus.py.qwewqa.xyz/changelog/
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Python: >=3.12
|
|
10
|
+
Requires-Python: >=3.12.5
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
13
13
|
# Sonolus.py
|
|
@@ -11,7 +11,7 @@ sonolus/backend/node.py,sha256=w5y2GwSc2E9rQvGJNaMzvPW_FYjjfHw4QDUmKs1dAnc,714
|
|
|
11
11
|
sonolus/backend/ops.py,sha256=5weB_vIxbkwCSJuzYZyKUk7vVXsSIEDJYRlvE-2ke8A,10572
|
|
12
12
|
sonolus/backend/place.py,sha256=7qwV732hZ4WP-9GNN8FQSEKssPJZELip1wLXTWfop7Y,4717
|
|
13
13
|
sonolus/backend/utils.py,sha256=OwD1EPh8j-hsfkLzeKNzPQojT_3kklpJou0WTJNoCbc,2337
|
|
14
|
-
sonolus/backend/visitor.py,sha256=
|
|
14
|
+
sonolus/backend/visitor.py,sha256=0EO5EgACDHpbITvKMbdP4EmoIbBDmZtZjnup9jksf_E,64590
|
|
15
15
|
sonolus/backend/optimize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
sonolus/backend/optimize/allocate.py,sha256=CuumoMphkpQlGRNeKLHT4FBGE0XVj5pwhfNdrqiLFSs,7535
|
|
17
17
|
sonolus/backend/optimize/constant_evaluation.py,sha256=_u_VfLmd4Rlq9aKyRSeKb47352CXuf8uNgNhNTK1qe0,21510
|
|
@@ -28,19 +28,19 @@ sonolus/backend/optimize/ssa.py,sha256=raQO0furQQRPYb8iIBKfNrJlj-_5wqtI4EWNfLZ8Q
|
|
|
28
28
|
sonolus/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
sonolus/build/cli.py,sha256=uLY7JG3PTAb2a1bbjulFlgUGDzsHXyRAfkGV0lbuMgQ,10538
|
|
30
30
|
sonolus/build/collection.py,sha256=6hniAzriPWBKUeGDkXabNXpbdHiHnqiK9shs6U1OExM,12748
|
|
31
|
-
sonolus/build/compile.py,sha256=
|
|
31
|
+
sonolus/build/compile.py,sha256=m4v4wYWnI6ebot4-fJi9a7eoF0WIo3uPOPy0_NpLgK0,8855
|
|
32
32
|
sonolus/build/dev_server.py,sha256=yHD3KGqzebdqcEBOC5JDlNptzlH8Sq4i-DNnh_zXWCA,10705
|
|
33
33
|
sonolus/build/engine.py,sha256=jMymxbBXu-ekv71uU8TF2KbFaHs3yGjyJAztd1SoRDs,14808
|
|
34
34
|
sonolus/build/level.py,sha256=KLqUAtxIuIqrzeFURJA97rdqjA5pcvYSmwNZQhElaMQ,702
|
|
35
35
|
sonolus/build/node.py,sha256=Dhuz_-UlRd-EJC7-AP1NuyvrjHWNo7jGssniRh4dZhI,1239
|
|
36
36
|
sonolus/build/project.py,sha256=Uuz82QtTNFdklrVJ_i7EPp8hSjyOxLU1xAeOloa6G00,8579
|
|
37
37
|
sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
sonolus/script/archetype.py,sha256=
|
|
38
|
+
sonolus/script/archetype.py,sha256=bDTjhbNA-F0wXOiYh1_cRewIT2S2D9zcL2XerBhobPI,55113
|
|
39
39
|
sonolus/script/array.py,sha256=EbrNwl_WuJ0JjjkX0s_VJNXWqvYdm_ljTbyrDEMLGUY,13348
|
|
40
40
|
sonolus/script/array_like.py,sha256=E6S4TW2muXgcyVkhUASQVt7JSYUkpvdJPgHz6YiSHNo,14708
|
|
41
41
|
sonolus/script/bucket.py,sha256=LNePLmCwgXfKLmH4Z7ZcTFKWR32eq4AnnagI7jacrsU,7782
|
|
42
42
|
sonolus/script/containers.py,sha256=KKySXTh_ON9hut1ScMS31ewRNnVd4t7OwpIZg2bPhno,28676
|
|
43
|
-
sonolus/script/debug.py,sha256=
|
|
43
|
+
sonolus/script/debug.py,sha256=sWMZ-c68GggD8eUZJUPRXxxDjtCt9j8X4-_FUgTvUNE,7856
|
|
44
44
|
sonolus/script/easing.py,sha256=2FUJI_nfp990P_armCcRqHm2329O985glJAhSC6tnxs,11379
|
|
45
45
|
sonolus/script/effect.py,sha256=SfJxSNF3RlPCRXnkt62ZlWhCXw3mmmRCsoMsvTErUP0,7960
|
|
46
46
|
sonolus/script/engine.py,sha256=etI9dJsQ7V9YZICVNZg54WqpLijPxG8eTPHiV-_EiG8,10687
|
|
@@ -69,16 +69,16 @@ sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
|
|
|
69
69
|
sonolus/script/values.py,sha256=6iJG6h4IDlbcK8FH4GENSHOQc7C_7fCGa34wM80qToA,1629
|
|
70
70
|
sonolus/script/vec.py,sha256=f7keqogIQiRpuQ0dULOFCxFlmvkykoFZMFwYw9P39Kk,9319
|
|
71
71
|
sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
|
|
72
|
-
sonolus/script/internal/builtin_impls.py,sha256=
|
|
72
|
+
sonolus/script/internal/builtin_impls.py,sha256=6MST5yII1Y6Ozo3P6WKOfWLTPllPOsxVNPSNmWAl7jU,16556
|
|
73
73
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
74
74
|
sonolus/script/internal/constant.py,sha256=3ycbGkDJVUwcrCZ96vLjAoAARgsvaqDM8rJ_YCrLrvo,4289
|
|
75
|
-
sonolus/script/internal/context.py,sha256=
|
|
75
|
+
sonolus/script/internal/context.py,sha256=dhWQS7fFPMhb0FJyw3y--0HlBtwuYANdIYiv7u_96jk,22023
|
|
76
76
|
sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
|
|
77
77
|
sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
|
|
78
78
|
sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
|
|
79
79
|
sonolus/script/internal/generic.py,sha256=_3d5Rn_tn214-77fPE67vdbdqt1PQF8-2WB_XDu5YRg,7551
|
|
80
80
|
sonolus/script/internal/impl.py,sha256=R88cl4nLcfF0UhA9qdYRBOsl4nMx8ucgz8l7_oRY-l8,3503
|
|
81
|
-
sonolus/script/internal/introspection.py,sha256=
|
|
81
|
+
sonolus/script/internal/introspection.py,sha256=q6c83G_jmuLSoYSJj8ln0XtuwNEdndE0PkLbNWOhGmk,1421
|
|
82
82
|
sonolus/script/internal/math_impls.py,sha256=ox2pBJ6ELRO0LdLn_RZxgHHs_PCgQOHIhmDkwmLxJaU,2975
|
|
83
83
|
sonolus/script/internal/native.py,sha256=zOuRtgI3XJ_ExyR_ZkvbDABVc_JIWaKl62lFEL_bMaw,2007
|
|
84
84
|
sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
|
|
@@ -87,8 +87,8 @@ sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDw
|
|
|
87
87
|
sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
|
|
88
88
|
sonolus/script/internal/tuple_impl.py,sha256=WaI5HSF5h03ddXiSHEwzY9ttfsPUItaf86Y5VbZypek,3754
|
|
89
89
|
sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
|
|
90
|
-
sonolus_py-0.
|
|
91
|
-
sonolus_py-0.
|
|
92
|
-
sonolus_py-0.
|
|
93
|
-
sonolus_py-0.
|
|
94
|
-
sonolus_py-0.
|
|
90
|
+
sonolus_py-0.12.1.dist-info/METADATA,sha256=fh6sYiAQtG6zvJO4a66E2bQz_KOVNxzrAxhMMVl36_A,556
|
|
91
|
+
sonolus_py-0.12.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
92
|
+
sonolus_py-0.12.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
|
|
93
|
+
sonolus_py-0.12.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
|
|
94
|
+
sonolus_py-0.12.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|