sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__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/finalize.py +18 -10
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +24 -0
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
- sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
- sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
- sonolus/backend/{flow.py → optimize/flow.py} +6 -5
- sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
- sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/{passes.py → optimize/passes.py} +1 -1
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +10 -0
- sonolus/backend/visitor.py +360 -101
- sonolus/build/cli.py +14 -3
- sonolus/build/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/build/project.py +30 -1
- sonolus/script/archetype.py +429 -138
- sonolus/script/array.py +25 -8
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +73 -11
- sonolus/script/containers.py +234 -51
- sonolus/script/debug.py +8 -8
- sonolus/script/easing.py +147 -105
- sonolus/script/effect.py +60 -0
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +66 -32
- sonolus/script/instruction.py +79 -25
- sonolus/script/internal/builtin_impls.py +138 -27
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +14 -5
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +5 -2
- sonolus/script/{math.py → internal/math_impls.py} +28 -28
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/interval.py +234 -16
- sonolus/script/iterator.py +120 -167
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +79 -47
- sonolus/script/options.py +78 -12
- sonolus/script/particle.py +37 -4
- sonolus/script/pointer.py +4 -4
- sonolus/script/print.py +22 -1
- sonolus/script/project.py +59 -0
- sonolus/script/{graphics.py → quad.py} +75 -12
- sonolus/script/record.py +44 -13
- sonolus/script/runtime.py +50 -1
- sonolus/script/sprite.py +198 -115
- sonolus/script/text.py +2 -0
- sonolus/script/timing.py +72 -0
- sonolus/script/transform.py +296 -66
- sonolus/script/ui.py +134 -78
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +118 -3
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
- sonolus_py-0.1.6.dist-info/RECORD +89 -0
- sonolus/backend/dead_code.py +0 -80
- sonolus/backend/optimize.py +0 -37
- sonolus/backend/simplify.py +0 -47
- sonolus/script/comptime.py +0 -160
- sonolus/script/random.py +0 -14
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.4.dist-info/RECORD +0 -84
- /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
sonolus/script/archetype.py
CHANGED
|
@@ -5,14 +5,14 @@ from collections.abc import Callable
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from enum import Enum, StrEnum
|
|
7
7
|
from types import FunctionType
|
|
8
|
-
from typing import Annotated, Any, ClassVar, Self, get_origin
|
|
8
|
+
from typing import Annotated, Any, ClassVar, Self, TypedDict, get_origin
|
|
9
9
|
|
|
10
10
|
from sonolus.backend.ir import IRConst, IRInstr
|
|
11
11
|
from sonolus.backend.mode import Mode
|
|
12
12
|
from sonolus.backend.ops import Op
|
|
13
13
|
from sonolus.backend.place import BlockPlace
|
|
14
14
|
from sonolus.script.bucket import Bucket, Judgment
|
|
15
|
-
from sonolus.script.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
15
|
+
from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
16
16
|
from sonolus.script.internal.context import ctx
|
|
17
17
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
18
18
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
@@ -21,16 +21,16 @@ from sonolus.script.internal.introspection import get_field_specifiers
|
|
|
21
21
|
from sonolus.script.internal.native import native_call
|
|
22
22
|
from sonolus.script.internal.value import Value
|
|
23
23
|
from sonolus.script.num import Num
|
|
24
|
-
from sonolus.script.pointer import
|
|
24
|
+
from sonolus.script.pointer import _deref
|
|
25
25
|
from sonolus.script.record import Record
|
|
26
26
|
from sonolus.script.values import zeros
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
_ENTITY_MEMORY_SIZE = 64
|
|
29
|
+
_ENTITY_DATA_SIZE = 32
|
|
30
|
+
_ENTITY_SHARED_MEMORY_SIZE = 32
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class
|
|
33
|
+
class _StorageType(Enum):
|
|
34
34
|
IMPORTED = "imported"
|
|
35
35
|
EXPORTED = "exported"
|
|
36
36
|
MEMORY = "memory"
|
|
@@ -38,53 +38,55 @@ class StorageType(Enum):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
@dataclass
|
|
41
|
-
class
|
|
41
|
+
class _ArchetypeFieldInfo:
|
|
42
42
|
name: str | None
|
|
43
|
-
storage:
|
|
43
|
+
storage: _StorageType
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class
|
|
47
|
-
def __init__(self, name: str, data_name: str, storage:
|
|
46
|
+
class _ArchetypeField(SonolusDescriptor):
|
|
47
|
+
def __init__(self, name: str, data_name: str, storage: _StorageType, offset: int, type_: type[Value]):
|
|
48
48
|
self.name = name
|
|
49
49
|
self.data_name = data_name # name used in level data
|
|
50
50
|
self.storage = storage
|
|
51
51
|
self.offset = offset
|
|
52
52
|
self.type = type_
|
|
53
53
|
|
|
54
|
-
def __get__(self, instance:
|
|
54
|
+
def __get__(self, instance: _BaseArchetype, owner):
|
|
55
55
|
if instance is None:
|
|
56
56
|
return self
|
|
57
57
|
result = None
|
|
58
58
|
match self.storage:
|
|
59
|
-
case
|
|
59
|
+
case _StorageType.IMPORTED:
|
|
60
60
|
match instance._data_:
|
|
61
|
-
case
|
|
62
|
-
result =
|
|
63
|
-
case
|
|
64
|
-
result =
|
|
65
|
-
|
|
61
|
+
case _ArchetypeSelfData():
|
|
62
|
+
result = _deref(ctx().blocks.EntityData, self.offset, self.type)
|
|
63
|
+
case _ArchetypeReferenceData(index=index):
|
|
64
|
+
result = _deref(
|
|
65
|
+
ctx().blocks.EntityDataArray, self.offset + index * _ENTITY_DATA_SIZE, self.type
|
|
66
|
+
)
|
|
67
|
+
case _ArchetypeLevelData(values=values):
|
|
66
68
|
result = values[self.name]
|
|
67
|
-
case
|
|
69
|
+
case _StorageType.EXPORTED:
|
|
68
70
|
raise RuntimeError("Exported fields are write-only")
|
|
69
|
-
case
|
|
71
|
+
case _StorageType.MEMORY:
|
|
70
72
|
match instance._data_:
|
|
71
|
-
case
|
|
72
|
-
result =
|
|
73
|
-
case
|
|
73
|
+
case _ArchetypeSelfData():
|
|
74
|
+
result = _deref(ctx().blocks.EntityMemory, self.offset, self.type)
|
|
75
|
+
case _ArchetypeReferenceData():
|
|
74
76
|
raise RuntimeError("Entity memory of other entities is not accessible")
|
|
75
|
-
case
|
|
77
|
+
case _ArchetypeLevelData():
|
|
76
78
|
raise RuntimeError("Entity memory is not available in level data")
|
|
77
|
-
case
|
|
79
|
+
case _StorageType.SHARED:
|
|
78
80
|
match instance._data_:
|
|
79
|
-
case
|
|
80
|
-
result =
|
|
81
|
-
case
|
|
82
|
-
result =
|
|
81
|
+
case _ArchetypeSelfData():
|
|
82
|
+
result = _deref(ctx().blocks.EntitySharedMemory, self.offset, self.type)
|
|
83
|
+
case _ArchetypeReferenceData(index=index):
|
|
84
|
+
result = _deref(
|
|
83
85
|
ctx().blocks.EntitySharedMemoryArray,
|
|
84
|
-
Num._accept_(self.offset) + index *
|
|
86
|
+
Num._accept_(self.offset) + index * _ENTITY_SHARED_MEMORY_SIZE,
|
|
85
87
|
self.type,
|
|
86
88
|
)
|
|
87
|
-
case
|
|
89
|
+
case _ArchetypeLevelData():
|
|
88
90
|
raise RuntimeError("Entity shared memory is not available in level data")
|
|
89
91
|
if result is None:
|
|
90
92
|
raise RuntimeError("Invalid storage type")
|
|
@@ -93,53 +95,55 @@ class ArchetypeField(SonolusDescriptor):
|
|
|
93
95
|
else:
|
|
94
96
|
return result._as_py_()
|
|
95
97
|
|
|
96
|
-
def __set__(self, instance:
|
|
98
|
+
def __set__(self, instance: _BaseArchetype, value):
|
|
97
99
|
if instance is None:
|
|
98
100
|
raise RuntimeError("Cannot set field on class")
|
|
99
101
|
if not self.type._accepts_(value):
|
|
100
102
|
raise TypeError(f"Expected {self.type}, got {type(value)}")
|
|
101
103
|
target = None
|
|
102
104
|
match self.storage:
|
|
103
|
-
case
|
|
105
|
+
case _StorageType.IMPORTED:
|
|
104
106
|
match instance._data_:
|
|
105
|
-
case
|
|
106
|
-
target =
|
|
107
|
-
case
|
|
108
|
-
target =
|
|
109
|
-
|
|
107
|
+
case _ArchetypeSelfData():
|
|
108
|
+
target = _deref(ctx().blocks.EntityData, self.offset, self.type)
|
|
109
|
+
case _ArchetypeReferenceData(index=index):
|
|
110
|
+
target = _deref(
|
|
111
|
+
ctx().blocks.EntityDataArray, self.offset + index * _ENTITY_DATA_SIZE, self.type
|
|
112
|
+
)
|
|
113
|
+
case _ArchetypeLevelData(values=values):
|
|
110
114
|
target = values[self.name]
|
|
111
|
-
case
|
|
115
|
+
case _StorageType.EXPORTED:
|
|
112
116
|
match instance._data_:
|
|
113
|
-
case
|
|
117
|
+
case _ArchetypeSelfData():
|
|
114
118
|
if not isinstance(value, self.type):
|
|
115
119
|
raise TypeError(f"Expected {self.type}, got {type(value)}")
|
|
116
120
|
for k, v in value._to_flat_dict_(self.data_name).items():
|
|
117
121
|
index = instance._exported_keys_[k]
|
|
118
122
|
ctx().add_statements(IRInstr(Op.ExportValue, [IRConst(index), Num._accept_(v).ir()]))
|
|
119
123
|
return
|
|
120
|
-
case
|
|
124
|
+
case _ArchetypeReferenceData():
|
|
121
125
|
raise RuntimeError("Exported fields of other entities are not accessible")
|
|
122
|
-
case
|
|
126
|
+
case _ArchetypeLevelData():
|
|
123
127
|
raise RuntimeError("Exported fields are not available in level data")
|
|
124
|
-
case
|
|
128
|
+
case _StorageType.MEMORY:
|
|
125
129
|
match instance._data_:
|
|
126
|
-
case
|
|
127
|
-
target =
|
|
128
|
-
case
|
|
130
|
+
case _ArchetypeSelfData():
|
|
131
|
+
target = _deref(ctx().blocks.EntityMemory, self.offset, self.type)
|
|
132
|
+
case _ArchetypeReferenceData():
|
|
129
133
|
raise RuntimeError("Entity memory of other entities is not accessible")
|
|
130
|
-
case
|
|
134
|
+
case _ArchetypeLevelData():
|
|
131
135
|
raise RuntimeError("Entity memory is not available in level data")
|
|
132
|
-
case
|
|
136
|
+
case _StorageType.SHARED:
|
|
133
137
|
match instance._data_:
|
|
134
|
-
case
|
|
135
|
-
target =
|
|
136
|
-
case
|
|
137
|
-
target =
|
|
138
|
+
case _ArchetypeSelfData():
|
|
139
|
+
target = _deref(ctx().blocks.EntitySharedMemory, self.offset, self.type)
|
|
140
|
+
case _ArchetypeReferenceData(index=index):
|
|
141
|
+
target = _deref(
|
|
138
142
|
ctx().blocks.EntitySharedMemoryArray,
|
|
139
|
-
Num._accept_(self.offset) + index *
|
|
143
|
+
Num._accept_(self.offset) + index * _ENTITY_SHARED_MEMORY_SIZE,
|
|
140
144
|
self.type,
|
|
141
145
|
)
|
|
142
|
-
case
|
|
146
|
+
case _ArchetypeLevelData():
|
|
143
147
|
raise RuntimeError("Entity shared memory is not available in level data")
|
|
144
148
|
if target is None:
|
|
145
149
|
raise RuntimeError("Invalid storage type")
|
|
@@ -151,22 +155,75 @@ class ArchetypeField(SonolusDescriptor):
|
|
|
151
155
|
|
|
152
156
|
|
|
153
157
|
def imported(*, name: str | None = None) -> Any:
|
|
154
|
-
|
|
158
|
+
"""Declare a field as imported.
|
|
159
|
+
|
|
160
|
+
Imported fields may be loaded from the level data.
|
|
161
|
+
|
|
162
|
+
In watch mode, data may also be loaded from a corresponding exported field in play mode.
|
|
163
|
+
|
|
164
|
+
Imported fields may only be updated in the `preprocess` callback, and are read-only in other callbacks.
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
```
|
|
168
|
+
class MyArchetype(PlayArchetype):
|
|
169
|
+
field: int = imported()
|
|
170
|
+
field_with_explicit_name: int = imported(name="field_name")
|
|
171
|
+
```
|
|
172
|
+
"""
|
|
173
|
+
return _ArchetypeFieldInfo(name, _StorageType.IMPORTED)
|
|
155
174
|
|
|
156
175
|
|
|
157
176
|
def exported(*, name: str | None = None) -> Any:
|
|
158
|
-
|
|
177
|
+
"""Declare a field as exported.
|
|
178
|
+
|
|
179
|
+
This is only usable in play mode to export data to be loaded in watch mode.
|
|
180
|
+
|
|
181
|
+
Exported fields are write-only.
|
|
182
|
+
|
|
183
|
+
Usage:
|
|
184
|
+
```
|
|
185
|
+
class MyArchetype(PlayArchetype):
|
|
186
|
+
field: int = exported()
|
|
187
|
+
field_with_explicit_name: int = exported(name="#FIELD")
|
|
188
|
+
```
|
|
189
|
+
"""
|
|
190
|
+
return _ArchetypeFieldInfo(name, _StorageType.EXPORTED)
|
|
159
191
|
|
|
160
192
|
|
|
161
193
|
def entity_memory() -> Any:
|
|
162
|
-
|
|
194
|
+
"""Declare a field as entity memory.
|
|
195
|
+
|
|
196
|
+
Entity memory is private to the entity and is not accessible from other entities.
|
|
197
|
+
|
|
198
|
+
Entity memory fields may also be set when an entity is spawned using the `spawn()` method.
|
|
199
|
+
|
|
200
|
+
Usage:
|
|
201
|
+
```
|
|
202
|
+
class MyArchetype(PlayArchetype):
|
|
203
|
+
field: int = entity_memory()
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
"""
|
|
207
|
+
return _ArchetypeFieldInfo(None, _StorageType.MEMORY)
|
|
163
208
|
|
|
164
209
|
|
|
165
210
|
def shared_memory() -> Any:
|
|
166
|
-
|
|
211
|
+
"""Declare a field as shared memory.
|
|
212
|
+
|
|
213
|
+
Shared memory is accessible from other entities.
|
|
214
|
+
|
|
215
|
+
Shared memory may only be updated by sequential callbacks such as `preprocess`, `update_sequential`, and `touch`.
|
|
216
|
+
|
|
217
|
+
Usage:
|
|
218
|
+
```
|
|
219
|
+
class MyArchetype(PlayArchetype):
|
|
220
|
+
field: int = shared_memory()
|
|
221
|
+
```
|
|
222
|
+
"""
|
|
223
|
+
return _ArchetypeFieldInfo(None, _StorageType.SHARED)
|
|
167
224
|
|
|
168
225
|
|
|
169
|
-
_annotation_defaults: dict[Callable,
|
|
226
|
+
_annotation_defaults: dict[Callable, _ArchetypeFieldInfo] = {
|
|
170
227
|
imported: imported(),
|
|
171
228
|
exported: exported(),
|
|
172
229
|
entity_memory: entity_memory(),
|
|
@@ -175,14 +232,53 @@ _annotation_defaults: dict[Callable, ArchetypeFieldInfo] = {
|
|
|
175
232
|
|
|
176
233
|
|
|
177
234
|
class StandardImport:
|
|
235
|
+
"""Standard import annotations for Archetype fields.
|
|
236
|
+
|
|
237
|
+
Usage:
|
|
238
|
+
```
|
|
239
|
+
class MyArchetype(WatchArchetype):
|
|
240
|
+
judgment: StandardImport.JUDGMENT
|
|
241
|
+
```
|
|
242
|
+
"""
|
|
243
|
+
|
|
178
244
|
BEAT = Annotated[float, imported(name="#BEAT")]
|
|
245
|
+
"""The beat of the entity."""
|
|
246
|
+
|
|
179
247
|
BPM = Annotated[float, imported(name="#BPM")]
|
|
248
|
+
"""The bpm, for bpm change markers."""
|
|
249
|
+
|
|
180
250
|
TIMESCALE = Annotated[float, imported(name="#TIMESCALE")]
|
|
251
|
+
"""The timescale, for timescale change markers."""
|
|
252
|
+
|
|
181
253
|
JUDGMENT = Annotated[int, imported(name="#JUDGMENT")]
|
|
254
|
+
"""The judgment of the entity.
|
|
255
|
+
|
|
256
|
+
Automatically supported in watch mode for archetypes with a corresponding scored play mode archetype.
|
|
257
|
+
"""
|
|
182
258
|
ACCURACY = Annotated[float, imported(name="#ACCURACY")]
|
|
259
|
+
"""The accuracy of the entity.
|
|
260
|
+
|
|
261
|
+
Automatically supported in watch mode for archetypes with a corresponding scored play mode archetype.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def callback[T: Callable](*, order: int = 0) -> Callable[[T], T]:
|
|
266
|
+
"""Annotate a callback with its order.
|
|
267
|
+
|
|
268
|
+
Callbacks are execute from lowest to highest order. By default, callbacks have an order of 0.
|
|
269
|
+
|
|
270
|
+
Usage:
|
|
271
|
+
```
|
|
272
|
+
class MyArchetype(PlayArchetype):
|
|
273
|
+
@callback(order=1)
|
|
274
|
+
def update_sequential(self):
|
|
275
|
+
pass
|
|
276
|
+
```
|
|
183
277
|
|
|
278
|
+
Args:
|
|
279
|
+
order: The order of the callback. Lower values are executed first.
|
|
280
|
+
"""
|
|
184
281
|
|
|
185
|
-
def callback[T: Callable](order: int) -> Callable[[T], T]:
|
|
186
282
|
def decorator(func: T) -> T:
|
|
187
283
|
func._callback_order_ = order
|
|
188
284
|
return func
|
|
@@ -190,37 +286,42 @@ def callback[T: Callable](order: int) -> Callable[[T], T]:
|
|
|
190
286
|
return decorator
|
|
191
287
|
|
|
192
288
|
|
|
193
|
-
class
|
|
289
|
+
class _ArchetypeSelfData:
|
|
194
290
|
pass
|
|
195
291
|
|
|
196
292
|
|
|
197
|
-
class
|
|
293
|
+
class _ArchetypeReferenceData:
|
|
198
294
|
index: Num
|
|
199
295
|
|
|
200
296
|
def __init__(self, index: Num):
|
|
201
297
|
self.index = index
|
|
202
298
|
|
|
203
299
|
|
|
204
|
-
class
|
|
300
|
+
class _ArchetypeLevelData:
|
|
205
301
|
values: dict[str, Value]
|
|
206
302
|
|
|
207
303
|
def __init__(self, values: dict[str, Value]):
|
|
208
304
|
self.values = values
|
|
209
305
|
|
|
210
306
|
|
|
211
|
-
type
|
|
307
|
+
type _ArchetypeData = _ArchetypeSelfData | _ArchetypeReferenceData | _ArchetypeLevelData
|
|
212
308
|
|
|
213
309
|
|
|
214
|
-
class
|
|
310
|
+
class ArchetypeSchema(TypedDict):
|
|
311
|
+
name: str
|
|
312
|
+
fields: list[str]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class _BaseArchetype:
|
|
215
316
|
_is_comptime_value_ = True
|
|
216
317
|
|
|
217
318
|
_supported_callbacks_: ClassVar[dict[str, CallbackInfo]]
|
|
218
319
|
_default_callbacks_: ClassVar[set[Callable]]
|
|
219
320
|
|
|
220
|
-
_imported_fields_: ClassVar[dict[str,
|
|
221
|
-
_exported_fields_: ClassVar[dict[str,
|
|
222
|
-
_memory_fields_: ClassVar[dict[str,
|
|
223
|
-
_shared_memory_fields_: ClassVar[dict[str,
|
|
321
|
+
_imported_fields_: ClassVar[dict[str, _ArchetypeField]]
|
|
322
|
+
_exported_fields_: ClassVar[dict[str, _ArchetypeField]]
|
|
323
|
+
_memory_fields_: ClassVar[dict[str, _ArchetypeField]]
|
|
324
|
+
_shared_memory_fields_: ClassVar[dict[str, _ArchetypeField]]
|
|
224
325
|
|
|
225
326
|
_imported_keys_: ClassVar[dict[str, int]]
|
|
226
327
|
_exported_keys_: ClassVar[dict[str, int]]
|
|
@@ -228,9 +329,16 @@ class BaseArchetype:
|
|
|
228
329
|
_data_constructor_signature_: ClassVar[inspect.Signature]
|
|
229
330
|
_spawn_signature_: ClassVar[inspect.Signature]
|
|
230
331
|
|
|
231
|
-
_data_:
|
|
332
|
+
_data_: _ArchetypeData
|
|
232
333
|
|
|
233
334
|
name: ClassVar[str | None] = None
|
|
335
|
+
"""The name of the archetype.
|
|
336
|
+
|
|
337
|
+
If not set, the name will be the class name.
|
|
338
|
+
|
|
339
|
+
The name is used in level data.
|
|
340
|
+
"""
|
|
341
|
+
|
|
234
342
|
is_scored: ClassVar[bool] = False
|
|
235
343
|
|
|
236
344
|
def __init__(self, *args, **kwargs):
|
|
@@ -242,7 +350,7 @@ class BaseArchetype:
|
|
|
242
350
|
field.name: field.type._accept_(bound.arguments.get(field.name) or zeros(field.type))._get_()
|
|
243
351
|
for field in self._imported_fields_.values()
|
|
244
352
|
}
|
|
245
|
-
self._data_ =
|
|
353
|
+
self._data_ = _ArchetypeLevelData(values=values)
|
|
246
354
|
|
|
247
355
|
@classmethod
|
|
248
356
|
def _new(cls):
|
|
@@ -251,14 +359,14 @@ class BaseArchetype:
|
|
|
251
359
|
@classmethod
|
|
252
360
|
def _for_compilation(cls):
|
|
253
361
|
result = cls._new()
|
|
254
|
-
result._data_ =
|
|
362
|
+
result._data_ = _ArchetypeSelfData()
|
|
255
363
|
return result
|
|
256
364
|
|
|
257
365
|
@classmethod
|
|
258
366
|
@meta_fn
|
|
259
367
|
def at(cls, index: Num) -> Self:
|
|
260
368
|
result = cls._new()
|
|
261
|
-
result._data_ =
|
|
369
|
+
result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
|
|
262
370
|
return result
|
|
263
371
|
|
|
264
372
|
@classmethod
|
|
@@ -273,7 +381,21 @@ class BaseArchetype:
|
|
|
273
381
|
|
|
274
382
|
@classmethod
|
|
275
383
|
@meta_fn
|
|
276
|
-
def spawn(cls, **kwargs):
|
|
384
|
+
def spawn(cls, **kwargs: Any) -> None:
|
|
385
|
+
"""Spawn an entity of this archetype, injecting the given values into entity memory.
|
|
386
|
+
|
|
387
|
+
Usage:
|
|
388
|
+
```
|
|
389
|
+
class MyArchetype(PlayArchetype):
|
|
390
|
+
field: int = entity_memory()
|
|
391
|
+
|
|
392
|
+
def f():
|
|
393
|
+
MyArchetype.spawn(field=123)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
**kwargs: Entity memory values to inject by field name as defined in the Archetype.
|
|
398
|
+
"""
|
|
277
399
|
if not ctx():
|
|
278
400
|
raise RuntimeError("Spawn is only allowed within a callback")
|
|
279
401
|
archetype_id = cls.id()
|
|
@@ -284,8 +406,12 @@ class BaseArchetype:
|
|
|
284
406
|
data.extend(field.type._accept_(bound.arguments[field.name] or zeros(field.type))._to_list_())
|
|
285
407
|
native_call(Op.Spawn, archetype_id, *(Num(x) for x in data))
|
|
286
408
|
|
|
409
|
+
@classmethod
|
|
410
|
+
def schema(cls) -> ArchetypeSchema:
|
|
411
|
+
return {"name": cls.name or "unnamed", "fields": list(cls._imported_fields_)}
|
|
412
|
+
|
|
287
413
|
def _level_data_entries(self, level_refs: dict[Any, int] | None = None):
|
|
288
|
-
if not isinstance(self._data_,
|
|
414
|
+
if not isinstance(self._data_, _ArchetypeLevelData):
|
|
289
415
|
raise RuntimeError("Entity is not level data")
|
|
290
416
|
entries = []
|
|
291
417
|
for name, value in self._data_.values.items():
|
|
@@ -295,7 +421,7 @@ class BaseArchetype:
|
|
|
295
421
|
return entries
|
|
296
422
|
|
|
297
423
|
def __init_subclass__(cls, **kwargs):
|
|
298
|
-
if cls.__module__ ==
|
|
424
|
+
if cls.__module__ == _BaseArchetype.__module__:
|
|
299
425
|
if cls._supported_callbacks_ is None:
|
|
300
426
|
raise TypeError("Cannot directly subclass Archetype, use the Archetype subclass for your mode")
|
|
301
427
|
cls._default_callbacks_ = {getattr(cls, cb_info.py_name) for cb_info in cls._supported_callbacks_.values()}
|
|
@@ -304,6 +430,7 @@ class BaseArchetype:
|
|
|
304
430
|
raise TypeError("Cannot subclass Archetypes")
|
|
305
431
|
if cls.name is None:
|
|
306
432
|
cls.name = cls.__name__
|
|
433
|
+
field_specifiers = get_field_specifiers(cls, skip={"name", "is_scored"}).items()
|
|
307
434
|
cls._imported_fields_ = {}
|
|
308
435
|
cls._exported_fields_ = {}
|
|
309
436
|
cls._memory_fields_ = {}
|
|
@@ -312,7 +439,7 @@ class BaseArchetype:
|
|
|
312
439
|
exported_offset = 0
|
|
313
440
|
memory_offset = 0
|
|
314
441
|
shared_memory_offset = 0
|
|
315
|
-
for name, value in
|
|
442
|
+
for name, value in field_specifiers:
|
|
316
443
|
if value is ClassVar or get_origin(value) is ClassVar:
|
|
317
444
|
continue
|
|
318
445
|
if get_origin(value) is not Annotated:
|
|
@@ -323,7 +450,7 @@ class BaseArchetype:
|
|
|
323
450
|
for metadata in value.__metadata__:
|
|
324
451
|
if isinstance(metadata, FunctionType):
|
|
325
452
|
metadata = _annotation_defaults.get(metadata, metadata)
|
|
326
|
-
if isinstance(metadata,
|
|
453
|
+
if isinstance(metadata, _ArchetypeFieldInfo):
|
|
327
454
|
if field_info is not None:
|
|
328
455
|
raise TypeError(
|
|
329
456
|
f"Unexpected multiple field annotations for '{name}', "
|
|
@@ -337,26 +464,26 @@ class BaseArchetype:
|
|
|
337
464
|
)
|
|
338
465
|
field_type = validate_concrete_type(value.__args__[0])
|
|
339
466
|
match field_info.storage:
|
|
340
|
-
case
|
|
341
|
-
cls._imported_fields_[name] =
|
|
467
|
+
case _StorageType.IMPORTED:
|
|
468
|
+
cls._imported_fields_[name] = _ArchetypeField(
|
|
342
469
|
name, field_info.name or name, field_info.storage, imported_offset, field_type
|
|
343
470
|
)
|
|
344
471
|
imported_offset += field_type._size_()
|
|
345
472
|
setattr(cls, name, cls._imported_fields_[name])
|
|
346
|
-
case
|
|
347
|
-
cls._exported_fields_[name] =
|
|
473
|
+
case _StorageType.EXPORTED:
|
|
474
|
+
cls._exported_fields_[name] = _ArchetypeField(
|
|
348
475
|
name, field_info.name or name, field_info.storage, exported_offset, field_type
|
|
349
476
|
)
|
|
350
477
|
exported_offset += field_type._size_()
|
|
351
478
|
setattr(cls, name, cls._exported_fields_[name])
|
|
352
|
-
case
|
|
353
|
-
cls._memory_fields_[name] =
|
|
479
|
+
case _StorageType.MEMORY:
|
|
480
|
+
cls._memory_fields_[name] = _ArchetypeField(
|
|
354
481
|
name, field_info.name or name, field_info.storage, memory_offset, field_type
|
|
355
482
|
)
|
|
356
483
|
memory_offset += field_type._size_()
|
|
357
484
|
setattr(cls, name, cls._memory_fields_[name])
|
|
358
|
-
case
|
|
359
|
-
cls._shared_memory_fields_[name] =
|
|
485
|
+
case _StorageType.SHARED:
|
|
486
|
+
cls._shared_memory_fields_[name] = _ArchetypeField(
|
|
360
487
|
name, field_info.name or name, field_info.storage, shared_memory_offset, field_type
|
|
361
488
|
)
|
|
362
489
|
shared_memory_offset += field_type._size_()
|
|
@@ -387,41 +514,97 @@ class BaseArchetype:
|
|
|
387
514
|
cls._callbacks_.append(cb)
|
|
388
515
|
|
|
389
516
|
|
|
390
|
-
class PlayArchetype(
|
|
517
|
+
class PlayArchetype(_BaseArchetype):
|
|
518
|
+
"""Base class for play mode archetypes.
|
|
519
|
+
|
|
520
|
+
Usage:
|
|
521
|
+
```
|
|
522
|
+
class MyArchetype(PlayArchetype):
|
|
523
|
+
# Set to True if the entity is a note and contributes to combo and score
|
|
524
|
+
# Default is False
|
|
525
|
+
is_scored: bool = True
|
|
526
|
+
|
|
527
|
+
imported_field: int = imported()
|
|
528
|
+
exported_field: int = exported()
|
|
529
|
+
entity_memory_field: int = entity_memory()
|
|
530
|
+
shared_memory_field: int = shared_memory()
|
|
531
|
+
|
|
532
|
+
@callback(order=1)
|
|
533
|
+
def preprocess(self):
|
|
534
|
+
...
|
|
535
|
+
```
|
|
536
|
+
"""
|
|
537
|
+
|
|
391
538
|
_supported_callbacks_ = PLAY_CALLBACKS
|
|
392
539
|
|
|
540
|
+
is_scored: ClassVar[bool] = False
|
|
541
|
+
"""Whether the entity contributes to combo and score."""
|
|
542
|
+
|
|
393
543
|
def preprocess(self):
|
|
394
|
-
|
|
544
|
+
"""Perform upfront processing.
|
|
545
|
+
|
|
546
|
+
Runs first when the level is loaded.
|
|
547
|
+
"""
|
|
395
548
|
|
|
396
549
|
def spawn_order(self) -> float:
|
|
397
|
-
|
|
550
|
+
"""Return the spawn order of the entity.
|
|
551
|
+
|
|
552
|
+
Runs when the level is loaded after `preprocess`.
|
|
553
|
+
"""
|
|
398
554
|
|
|
399
555
|
def should_spawn(self) -> bool:
|
|
400
|
-
|
|
556
|
+
"""Return whether the entity should be spawned.
|
|
557
|
+
|
|
558
|
+
Runs when this entity is first in the spawn queue.
|
|
559
|
+
"""
|
|
401
560
|
|
|
402
561
|
def initialize(self):
|
|
403
|
-
|
|
562
|
+
"""Initialize this entity.
|
|
563
|
+
|
|
564
|
+
Runs when this entity is spawned.
|
|
565
|
+
"""
|
|
404
566
|
|
|
405
567
|
def update_sequential(self):
|
|
406
|
-
|
|
568
|
+
"""Perform non-parallel actions for this frame.
|
|
569
|
+
|
|
570
|
+
Runs first each frame.
|
|
571
|
+
|
|
572
|
+
This is where logic affecting shared memory should be placed.
|
|
573
|
+
Other logic should be placed in `update_parallel` for better performance.
|
|
574
|
+
"""
|
|
407
575
|
|
|
408
576
|
def update_parallel(self):
|
|
409
|
-
|
|
577
|
+
"""Perform parallel actions for this frame.
|
|
578
|
+
|
|
579
|
+
Runs after `touch` each frame.
|
|
580
|
+
|
|
581
|
+
This is where most gameplay logic should be placed.
|
|
582
|
+
"""
|
|
410
583
|
|
|
411
584
|
def touch(self):
|
|
412
|
-
|
|
585
|
+
"""Handle user input.
|
|
586
|
+
|
|
587
|
+
Runs after `update_sequential` each frame.
|
|
588
|
+
"""
|
|
413
589
|
|
|
414
590
|
def terminate(self):
|
|
415
|
-
|
|
591
|
+
"""Finalize before despawning.
|
|
592
|
+
|
|
593
|
+
Runs when the entity is despawned.
|
|
594
|
+
"""
|
|
416
595
|
|
|
417
596
|
@property
|
|
418
597
|
@meta_fn
|
|
419
598
|
def despawn(self):
|
|
599
|
+
"""Whether the entity should be despawned after this frame.
|
|
600
|
+
|
|
601
|
+
Setting this to True will despawn the entity.
|
|
602
|
+
"""
|
|
420
603
|
if not ctx():
|
|
421
604
|
raise RuntimeError("Calling despawn is only allowed within a callback")
|
|
422
605
|
match self._data_:
|
|
423
|
-
case
|
|
424
|
-
return
|
|
606
|
+
case _ArchetypeSelfData():
|
|
607
|
+
return _deref(ctx().blocks.EntityDespawn, 0, Num)
|
|
425
608
|
case _:
|
|
426
609
|
raise RuntimeError("Despawn is only accessible from the entity itself")
|
|
427
610
|
|
|
@@ -431,8 +614,8 @@ class PlayArchetype(BaseArchetype):
|
|
|
431
614
|
if not ctx():
|
|
432
615
|
raise RuntimeError("Calling despawn is only allowed within a callback")
|
|
433
616
|
match self._data_:
|
|
434
|
-
case
|
|
435
|
-
|
|
617
|
+
case _ArchetypeSelfData():
|
|
618
|
+
_deref(ctx().blocks.EntityDespawn, 0, Num)._set_(value)
|
|
436
619
|
case _:
|
|
437
620
|
raise RuntimeError("Despawn is only accessible from the entity itself")
|
|
438
621
|
|
|
@@ -442,80 +625,128 @@ class PlayArchetype(BaseArchetype):
|
|
|
442
625
|
if not ctx():
|
|
443
626
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
444
627
|
match self._data_:
|
|
445
|
-
case
|
|
446
|
-
return
|
|
447
|
-
case
|
|
448
|
-
return
|
|
628
|
+
case _ArchetypeSelfData():
|
|
629
|
+
return _deref(ctx().blocks.EntityInfo, 0, PlayEntityInfo)
|
|
630
|
+
case _ArchetypeReferenceData(index=index):
|
|
631
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
|
|
449
632
|
case _:
|
|
450
633
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
451
634
|
|
|
452
635
|
@property
|
|
453
636
|
def index(self) -> int:
|
|
637
|
+
"""The index of this entity."""
|
|
454
638
|
return self._info.index
|
|
455
639
|
|
|
456
640
|
@property
|
|
457
641
|
def is_waiting(self) -> bool:
|
|
642
|
+
"""Whether this entity is waiting to be spawned."""
|
|
458
643
|
return self._info.state == 0
|
|
459
644
|
|
|
460
645
|
@property
|
|
461
646
|
def is_active(self) -> bool:
|
|
647
|
+
"""Whether this entity is active."""
|
|
462
648
|
return self._info.state == 1
|
|
463
649
|
|
|
464
650
|
@property
|
|
465
651
|
def is_despawned(self) -> bool:
|
|
652
|
+
"""Whether this entity is despawned."""
|
|
466
653
|
return self._info.state == 2
|
|
467
654
|
|
|
468
655
|
@property
|
|
469
656
|
def life(self) -> ArchetypeLife:
|
|
657
|
+
"""How this entity contributes to life."""
|
|
470
658
|
if not ctx():
|
|
471
659
|
raise RuntimeError("Calling life is only allowed within a callback")
|
|
472
660
|
match self._data_:
|
|
473
|
-
case
|
|
474
|
-
return
|
|
661
|
+
case _ArchetypeSelfData() | _ArchetypeReferenceData():
|
|
662
|
+
return _deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
475
663
|
case _:
|
|
476
664
|
raise RuntimeError("Life is not available in level data")
|
|
477
665
|
|
|
478
666
|
@property
|
|
479
667
|
def result(self) -> PlayEntityInput:
|
|
668
|
+
"""The result of this entity.
|
|
669
|
+
|
|
670
|
+
Only meaningful for scored entities.
|
|
671
|
+
"""
|
|
480
672
|
if not ctx():
|
|
481
673
|
raise RuntimeError("Calling result is only allowed within a callback")
|
|
482
674
|
match self._data_:
|
|
483
|
-
case
|
|
484
|
-
return
|
|
675
|
+
case _ArchetypeSelfData():
|
|
676
|
+
return _deref(ctx().blocks.EntityInput, 0, PlayEntityInput)
|
|
485
677
|
case _:
|
|
486
678
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
487
679
|
|
|
488
680
|
def ref(self):
|
|
489
|
-
|
|
681
|
+
"""Get a reference to this entity for creating level data.
|
|
682
|
+
|
|
683
|
+
Not valid elsewhere.
|
|
684
|
+
"""
|
|
685
|
+
if not isinstance(self._data_, _ArchetypeLevelData):
|
|
490
686
|
raise RuntimeError("Entity is not level data")
|
|
491
687
|
result = EntityRef[type(self)](index=-1)
|
|
492
688
|
result._ref_ = self
|
|
493
689
|
return result
|
|
494
690
|
|
|
495
691
|
|
|
496
|
-
class WatchArchetype(
|
|
692
|
+
class WatchArchetype(_BaseArchetype):
|
|
693
|
+
"""Base class for watch mode archetypes.
|
|
694
|
+
|
|
695
|
+
Usage:
|
|
696
|
+
```
|
|
697
|
+
class MyArchetype(WatchArchetype):
|
|
698
|
+
imported_field: int = imported()
|
|
699
|
+
entity_memory_field: int = entity_memory()
|
|
700
|
+
shared_memory_field: int = shared_memory()
|
|
701
|
+
|
|
702
|
+
@callback(order=1)
|
|
703
|
+
def update_sequential(self):
|
|
704
|
+
...
|
|
705
|
+
```
|
|
706
|
+
"""
|
|
707
|
+
|
|
497
708
|
_supported_callbacks_ = WATCH_ARCHETYPE_CALLBACKS
|
|
498
709
|
|
|
499
710
|
def preprocess(self):
|
|
500
|
-
|
|
711
|
+
"""Perform upfront processing.
|
|
712
|
+
|
|
713
|
+
Runs first when the level is loaded.
|
|
714
|
+
"""
|
|
501
715
|
|
|
502
716
|
def spawn_time(self) -> float:
|
|
503
|
-
|
|
717
|
+
"""Return the spawn time of the entity."""
|
|
504
718
|
|
|
505
719
|
def despawn_time(self) -> float:
|
|
506
|
-
|
|
720
|
+
"""Return the despawn time of the entity."""
|
|
507
721
|
|
|
508
722
|
def initialize(self):
|
|
509
|
-
|
|
723
|
+
"""Initialize this entity.
|
|
724
|
+
|
|
725
|
+
Runs when this entity is spawned.
|
|
726
|
+
"""
|
|
510
727
|
|
|
511
728
|
def update_sequential(self):
|
|
512
|
-
|
|
729
|
+
"""Perform non-parallel actions for this frame.
|
|
730
|
+
|
|
731
|
+
Runs first each frame.
|
|
732
|
+
|
|
733
|
+
This is where logic affecting shared memory should be placed.
|
|
734
|
+
Other logic should be placed in `update_parallel` for better performance.
|
|
735
|
+
"""
|
|
513
736
|
|
|
514
737
|
def update_parallel(self):
|
|
515
|
-
|
|
738
|
+
"""Parallel update callback.
|
|
739
|
+
|
|
740
|
+
Runs after `touch` each frame.
|
|
741
|
+
|
|
742
|
+
This is where most gameplay logic should be placed.
|
|
743
|
+
"""
|
|
516
744
|
|
|
517
745
|
def terminate(self):
|
|
518
|
-
|
|
746
|
+
"""Finalize before despawning.
|
|
747
|
+
|
|
748
|
+
Runs when the entity is despawned.
|
|
749
|
+
"""
|
|
519
750
|
|
|
520
751
|
@property
|
|
521
752
|
@meta_fn
|
|
@@ -523,43 +754,56 @@ class WatchArchetype(BaseArchetype):
|
|
|
523
754
|
if not ctx():
|
|
524
755
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
525
756
|
match self._data_:
|
|
526
|
-
case
|
|
527
|
-
return
|
|
528
|
-
case
|
|
529
|
-
return
|
|
757
|
+
case _ArchetypeSelfData():
|
|
758
|
+
return _deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
|
|
759
|
+
case _ArchetypeReferenceData(index=index):
|
|
760
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), PlayEntityInfo)
|
|
530
761
|
case _:
|
|
531
762
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
532
763
|
|
|
533
764
|
@property
|
|
534
765
|
def index(self) -> int:
|
|
766
|
+
"""The index of this entity."""
|
|
535
767
|
return self._info.index
|
|
536
768
|
|
|
537
769
|
@property
|
|
538
770
|
def is_active(self) -> bool:
|
|
771
|
+
"""Whether this entity is active."""
|
|
539
772
|
return self._info.state == 1
|
|
540
773
|
|
|
541
774
|
@property
|
|
542
775
|
def life(self) -> ArchetypeLife:
|
|
776
|
+
"""How this entity contributes to life."""
|
|
543
777
|
if not ctx():
|
|
544
778
|
raise RuntimeError("Calling life is only allowed within a callback")
|
|
545
779
|
match self._data_:
|
|
546
|
-
case
|
|
547
|
-
return
|
|
780
|
+
case _ArchetypeSelfData() | _ArchetypeReferenceData():
|
|
781
|
+
return _deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
548
782
|
case _:
|
|
549
783
|
raise RuntimeError("Life is not available in level data")
|
|
550
784
|
|
|
551
785
|
@property
|
|
552
786
|
def result(self) -> WatchEntityInput:
|
|
787
|
+
"""The result of this entity.
|
|
788
|
+
|
|
789
|
+
Only meaningful for scored entities.
|
|
790
|
+
"""
|
|
553
791
|
if not ctx():
|
|
554
792
|
raise RuntimeError("Calling result is only allowed within a callback")
|
|
555
793
|
match self._data_:
|
|
556
|
-
case
|
|
557
|
-
return
|
|
794
|
+
case _ArchetypeSelfData():
|
|
795
|
+
return _deref(ctx().blocks.EntityInput, 0, WatchEntityInput)
|
|
558
796
|
case _:
|
|
559
797
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
560
798
|
|
|
561
799
|
@property
|
|
562
800
|
def target_time(self) -> float:
|
|
801
|
+
"""The target time of this entity.
|
|
802
|
+
|
|
803
|
+
Only meaningful for scored entities. Determines when combo and score are updated.
|
|
804
|
+
|
|
805
|
+
Alias of `result.target_time`.
|
|
806
|
+
"""
|
|
563
807
|
return self.result.target_time
|
|
564
808
|
|
|
565
809
|
@target_time.setter
|
|
@@ -567,56 +811,86 @@ class WatchArchetype(BaseArchetype):
|
|
|
567
811
|
self.result.target_time = value
|
|
568
812
|
|
|
569
813
|
|
|
570
|
-
class PreviewArchetype(
|
|
814
|
+
class PreviewArchetype(_BaseArchetype):
|
|
815
|
+
"""Base class for preview mode archetypes.
|
|
816
|
+
|
|
817
|
+
Usage:
|
|
818
|
+
```
|
|
819
|
+
class MyArchetype(PreviewArchetype):
|
|
820
|
+
imported_field: int = imported()
|
|
821
|
+
entity_memory_field: int = entity_memory()
|
|
822
|
+
shared_memory_field: int = shared_memory()
|
|
823
|
+
|
|
824
|
+
@callback(order=1)
|
|
825
|
+
def preprocess(self):
|
|
826
|
+
...
|
|
827
|
+
```
|
|
828
|
+
"""
|
|
829
|
+
|
|
571
830
|
_supported_callbacks_ = PREVIEW_CALLBACKS
|
|
572
831
|
|
|
573
832
|
def preprocess(self):
|
|
574
|
-
|
|
833
|
+
"""Perform upfront processing.
|
|
834
|
+
|
|
835
|
+
Runs first when the level is loaded.
|
|
836
|
+
"""
|
|
575
837
|
|
|
576
838
|
def render(self):
|
|
577
|
-
|
|
839
|
+
"""Render the entity.
|
|
840
|
+
|
|
841
|
+
Runs after `preprocess`.
|
|
842
|
+
"""
|
|
578
843
|
|
|
579
844
|
@property
|
|
580
845
|
def _info(self) -> PreviewEntityInfo:
|
|
581
846
|
if not ctx():
|
|
582
847
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
583
848
|
match self._data_:
|
|
584
|
-
case
|
|
585
|
-
return
|
|
586
|
-
case
|
|
587
|
-
return
|
|
849
|
+
case _ArchetypeSelfData():
|
|
850
|
+
return _deref(ctx().blocks.EntityInfo, 0, PreviewEntityInfo)
|
|
851
|
+
case _ArchetypeReferenceData(index=index):
|
|
852
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
|
|
588
853
|
case _:
|
|
589
854
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
590
855
|
|
|
591
856
|
@property
|
|
592
857
|
def index(self) -> int:
|
|
858
|
+
"""The index of this entity."""
|
|
593
859
|
return self._info.index
|
|
594
860
|
|
|
595
861
|
|
|
596
862
|
@meta_fn
|
|
597
863
|
def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
|
|
864
|
+
"""Retrieve entity info of the entity at the given index.
|
|
865
|
+
|
|
866
|
+
Available in play, watch, and preview mode.
|
|
867
|
+
"""
|
|
598
868
|
if not ctx():
|
|
599
869
|
raise RuntimeError("Calling entity_info_at is only allowed within a callback")
|
|
600
870
|
match ctx().global_state.mode:
|
|
601
871
|
case Mode.PLAY:
|
|
602
|
-
return
|
|
872
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
|
|
603
873
|
case Mode.WATCH:
|
|
604
|
-
return
|
|
874
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
605
875
|
case Mode.PREVIEW:
|
|
606
|
-
return
|
|
876
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
|
|
607
877
|
case _:
|
|
608
878
|
raise RuntimeError(f"Entity info is not available in mode '{ctx().global_state.mode}'")
|
|
609
879
|
|
|
610
880
|
|
|
611
881
|
@meta_fn
|
|
612
|
-
def archetype_life_of(archetype: type[
|
|
882
|
+
def archetype_life_of(archetype: type[_BaseArchetype] | _BaseArchetype) -> ArchetypeLife:
|
|
883
|
+
"""Retrieve the archetype life of the given archetype.
|
|
884
|
+
|
|
885
|
+
Available in play and watch mode.
|
|
886
|
+
"""
|
|
613
887
|
archetype = validate_value(archetype)
|
|
614
888
|
archetype = archetype._as_py_()
|
|
615
889
|
if not ctx():
|
|
616
890
|
raise RuntimeError("Calling archetype_life_of is only allowed within a callback")
|
|
617
891
|
match ctx().global_state.mode:
|
|
618
892
|
case Mode.PLAY | Mode.WATCH:
|
|
619
|
-
return
|
|
893
|
+
return _deref(ctx().blocks.ArchetypeLife, archetype.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
620
894
|
case _:
|
|
621
895
|
raise RuntimeError(f"Archetype life is not available in mode '{ctx().global_state.mode}'")
|
|
622
896
|
|
|
@@ -639,10 +913,19 @@ class PreviewEntityInfo(Record):
|
|
|
639
913
|
|
|
640
914
|
|
|
641
915
|
class ArchetypeLife(Record):
|
|
916
|
+
"""How an entity contributes to life."""
|
|
917
|
+
|
|
642
918
|
perfect_increment: Num
|
|
919
|
+
"""Life increment for a perfect judgment."""
|
|
920
|
+
|
|
643
921
|
great_increment: Num
|
|
922
|
+
"""Life increment for a great judgment."""
|
|
923
|
+
|
|
644
924
|
good_increment: Num
|
|
925
|
+
"""Life increment for a good judgment."""
|
|
926
|
+
|
|
645
927
|
miss_increment: Num
|
|
928
|
+
"""Life increment for a miss judgment."""
|
|
646
929
|
|
|
647
930
|
def update(
|
|
648
931
|
self,
|
|
@@ -651,6 +934,7 @@ class ArchetypeLife(Record):
|
|
|
651
934
|
good_increment: Num | None = None,
|
|
652
935
|
miss_increment: Num | None = None,
|
|
653
936
|
):
|
|
937
|
+
"""Update the life increments."""
|
|
654
938
|
if perfect_increment is not None:
|
|
655
939
|
self.perfect_increment = perfect_increment
|
|
656
940
|
if great_increment is not None:
|
|
@@ -674,12 +958,14 @@ class WatchEntityInput(Record):
|
|
|
674
958
|
bucket_value: float
|
|
675
959
|
|
|
676
960
|
|
|
677
|
-
class EntityRef[A:
|
|
961
|
+
class EntityRef[A: _BaseArchetype](Record):
|
|
962
|
+
"""Reference to another entity."""
|
|
963
|
+
|
|
678
964
|
index: int
|
|
679
965
|
|
|
680
966
|
@classmethod
|
|
681
967
|
def archetype(cls) -> type[A]:
|
|
682
|
-
return cls.
|
|
968
|
+
return cls.type_var_value(A)
|
|
683
969
|
|
|
684
970
|
def get(self) -> A:
|
|
685
971
|
return self.archetype().at(self.index)
|
|
@@ -695,5 +981,10 @@ class EntityRef[A: BaseArchetype](Record):
|
|
|
695
981
|
|
|
696
982
|
|
|
697
983
|
class StandardArchetypeName(StrEnum):
|
|
984
|
+
"""Standard archetype names."""
|
|
985
|
+
|
|
698
986
|
BPM_CHANGE = "#BPM_CHANGE"
|
|
987
|
+
"""Bpm change marker"""
|
|
988
|
+
|
|
699
989
|
TIMESCALE_CHANGE = "#TIMESCALE_CHANGE"
|
|
990
|
+
"""Timescale change marker"""
|