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