sonolus.py 0.1.4__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/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/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/script/archetype.py +419 -137
- 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 +8 -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 +197 -112
- 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.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.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.5.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
sonolus/script/archetype.py
CHANGED
|
@@ -12,7 +12,7 @@ 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`.
|
|
167
216
|
|
|
217
|
+
Usage:
|
|
218
|
+
```
|
|
219
|
+
class MyArchetype(PlayArchetype):
|
|
220
|
+
field: int = shared_memory()
|
|
221
|
+
```
|
|
222
|
+
"""
|
|
223
|
+
return _ArchetypeFieldInfo(None, _StorageType.SHARED)
|
|
168
224
|
|
|
169
|
-
|
|
225
|
+
|
|
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.
|
|
183
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
|
+
```
|
|
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,37 @@ 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 _BaseArchetype:
|
|
215
311
|
_is_comptime_value_ = True
|
|
216
312
|
|
|
217
313
|
_supported_callbacks_: ClassVar[dict[str, CallbackInfo]]
|
|
218
314
|
_default_callbacks_: ClassVar[set[Callable]]
|
|
219
315
|
|
|
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,
|
|
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]]
|
|
224
320
|
|
|
225
321
|
_imported_keys_: ClassVar[dict[str, int]]
|
|
226
322
|
_exported_keys_: ClassVar[dict[str, int]]
|
|
@@ -228,9 +324,16 @@ class BaseArchetype:
|
|
|
228
324
|
_data_constructor_signature_: ClassVar[inspect.Signature]
|
|
229
325
|
_spawn_signature_: ClassVar[inspect.Signature]
|
|
230
326
|
|
|
231
|
-
_data_:
|
|
327
|
+
_data_: _ArchetypeData
|
|
232
328
|
|
|
233
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
|
+
|
|
234
337
|
is_scored: ClassVar[bool] = False
|
|
235
338
|
|
|
236
339
|
def __init__(self, *args, **kwargs):
|
|
@@ -242,7 +345,7 @@ class BaseArchetype:
|
|
|
242
345
|
field.name: field.type._accept_(bound.arguments.get(field.name) or zeros(field.type))._get_()
|
|
243
346
|
for field in self._imported_fields_.values()
|
|
244
347
|
}
|
|
245
|
-
self._data_ =
|
|
348
|
+
self._data_ = _ArchetypeLevelData(values=values)
|
|
246
349
|
|
|
247
350
|
@classmethod
|
|
248
351
|
def _new(cls):
|
|
@@ -251,14 +354,14 @@ class BaseArchetype:
|
|
|
251
354
|
@classmethod
|
|
252
355
|
def _for_compilation(cls):
|
|
253
356
|
result = cls._new()
|
|
254
|
-
result._data_ =
|
|
357
|
+
result._data_ = _ArchetypeSelfData()
|
|
255
358
|
return result
|
|
256
359
|
|
|
257
360
|
@classmethod
|
|
258
361
|
@meta_fn
|
|
259
362
|
def at(cls, index: Num) -> Self:
|
|
260
363
|
result = cls._new()
|
|
261
|
-
result._data_ =
|
|
364
|
+
result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
|
|
262
365
|
return result
|
|
263
366
|
|
|
264
367
|
@classmethod
|
|
@@ -273,7 +376,21 @@ class BaseArchetype:
|
|
|
273
376
|
|
|
274
377
|
@classmethod
|
|
275
378
|
@meta_fn
|
|
276
|
-
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
|
+
"""
|
|
277
394
|
if not ctx():
|
|
278
395
|
raise RuntimeError("Spawn is only allowed within a callback")
|
|
279
396
|
archetype_id = cls.id()
|
|
@@ -285,7 +402,7 @@ class BaseArchetype:
|
|
|
285
402
|
native_call(Op.Spawn, archetype_id, *(Num(x) for x in data))
|
|
286
403
|
|
|
287
404
|
def _level_data_entries(self, level_refs: dict[Any, int] | None = None):
|
|
288
|
-
if not isinstance(self._data_,
|
|
405
|
+
if not isinstance(self._data_, _ArchetypeLevelData):
|
|
289
406
|
raise RuntimeError("Entity is not level data")
|
|
290
407
|
entries = []
|
|
291
408
|
for name, value in self._data_.values.items():
|
|
@@ -295,7 +412,7 @@ class BaseArchetype:
|
|
|
295
412
|
return entries
|
|
296
413
|
|
|
297
414
|
def __init_subclass__(cls, **kwargs):
|
|
298
|
-
if cls.__module__ ==
|
|
415
|
+
if cls.__module__ == _BaseArchetype.__module__:
|
|
299
416
|
if cls._supported_callbacks_ is None:
|
|
300
417
|
raise TypeError("Cannot directly subclass Archetype, use the Archetype subclass for your mode")
|
|
301
418
|
cls._default_callbacks_ = {getattr(cls, cb_info.py_name) for cb_info in cls._supported_callbacks_.values()}
|
|
@@ -304,6 +421,7 @@ class BaseArchetype:
|
|
|
304
421
|
raise TypeError("Cannot subclass Archetypes")
|
|
305
422
|
if cls.name is None:
|
|
306
423
|
cls.name = cls.__name__
|
|
424
|
+
field_specifiers = get_field_specifiers(cls, skip={"name", "is_scored"}).items()
|
|
307
425
|
cls._imported_fields_ = {}
|
|
308
426
|
cls._exported_fields_ = {}
|
|
309
427
|
cls._memory_fields_ = {}
|
|
@@ -312,7 +430,7 @@ class BaseArchetype:
|
|
|
312
430
|
exported_offset = 0
|
|
313
431
|
memory_offset = 0
|
|
314
432
|
shared_memory_offset = 0
|
|
315
|
-
for name, value in
|
|
433
|
+
for name, value in field_specifiers:
|
|
316
434
|
if value is ClassVar or get_origin(value) is ClassVar:
|
|
317
435
|
continue
|
|
318
436
|
if get_origin(value) is not Annotated:
|
|
@@ -323,7 +441,7 @@ class BaseArchetype:
|
|
|
323
441
|
for metadata in value.__metadata__:
|
|
324
442
|
if isinstance(metadata, FunctionType):
|
|
325
443
|
metadata = _annotation_defaults.get(metadata, metadata)
|
|
326
|
-
if isinstance(metadata,
|
|
444
|
+
if isinstance(metadata, _ArchetypeFieldInfo):
|
|
327
445
|
if field_info is not None:
|
|
328
446
|
raise TypeError(
|
|
329
447
|
f"Unexpected multiple field annotations for '{name}', "
|
|
@@ -337,26 +455,26 @@ class BaseArchetype:
|
|
|
337
455
|
)
|
|
338
456
|
field_type = validate_concrete_type(value.__args__[0])
|
|
339
457
|
match field_info.storage:
|
|
340
|
-
case
|
|
341
|
-
cls._imported_fields_[name] =
|
|
458
|
+
case _StorageType.IMPORTED:
|
|
459
|
+
cls._imported_fields_[name] = _ArchetypeField(
|
|
342
460
|
name, field_info.name or name, field_info.storage, imported_offset, field_type
|
|
343
461
|
)
|
|
344
462
|
imported_offset += field_type._size_()
|
|
345
463
|
setattr(cls, name, cls._imported_fields_[name])
|
|
346
|
-
case
|
|
347
|
-
cls._exported_fields_[name] =
|
|
464
|
+
case _StorageType.EXPORTED:
|
|
465
|
+
cls._exported_fields_[name] = _ArchetypeField(
|
|
348
466
|
name, field_info.name or name, field_info.storage, exported_offset, field_type
|
|
349
467
|
)
|
|
350
468
|
exported_offset += field_type._size_()
|
|
351
469
|
setattr(cls, name, cls._exported_fields_[name])
|
|
352
|
-
case
|
|
353
|
-
cls._memory_fields_[name] =
|
|
470
|
+
case _StorageType.MEMORY:
|
|
471
|
+
cls._memory_fields_[name] = _ArchetypeField(
|
|
354
472
|
name, field_info.name or name, field_info.storage, memory_offset, field_type
|
|
355
473
|
)
|
|
356
474
|
memory_offset += field_type._size_()
|
|
357
475
|
setattr(cls, name, cls._memory_fields_[name])
|
|
358
|
-
case
|
|
359
|
-
cls._shared_memory_fields_[name] =
|
|
476
|
+
case _StorageType.SHARED:
|
|
477
|
+
cls._shared_memory_fields_[name] = _ArchetypeField(
|
|
360
478
|
name, field_info.name or name, field_info.storage, shared_memory_offset, field_type
|
|
361
479
|
)
|
|
362
480
|
shared_memory_offset += field_type._size_()
|
|
@@ -387,41 +505,97 @@ class BaseArchetype:
|
|
|
387
505
|
cls._callbacks_.append(cb)
|
|
388
506
|
|
|
389
507
|
|
|
390
|
-
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
|
+
|
|
391
529
|
_supported_callbacks_ = PLAY_CALLBACKS
|
|
392
530
|
|
|
531
|
+
is_scored: ClassVar[bool] = False
|
|
532
|
+
"""Whether the entity contributes to combo and score."""
|
|
533
|
+
|
|
393
534
|
def preprocess(self):
|
|
394
|
-
|
|
535
|
+
"""Perform upfront processing.
|
|
536
|
+
|
|
537
|
+
Runs first when the level is loaded.
|
|
538
|
+
"""
|
|
395
539
|
|
|
396
540
|
def spawn_order(self) -> float:
|
|
397
|
-
|
|
541
|
+
"""Return the spawn order of the entity.
|
|
542
|
+
|
|
543
|
+
Runs when the level is loaded after `preprocess`.
|
|
544
|
+
"""
|
|
398
545
|
|
|
399
546
|
def should_spawn(self) -> bool:
|
|
400
|
-
|
|
547
|
+
"""Return whether the entity should be spawned.
|
|
548
|
+
|
|
549
|
+
Runs when this entity is first in the spawn queue.
|
|
550
|
+
"""
|
|
401
551
|
|
|
402
552
|
def initialize(self):
|
|
403
|
-
|
|
553
|
+
"""Initialize this entity.
|
|
554
|
+
|
|
555
|
+
Runs when this entity is spawned.
|
|
556
|
+
"""
|
|
404
557
|
|
|
405
558
|
def update_sequential(self):
|
|
406
|
-
|
|
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
|
+
"""
|
|
407
566
|
|
|
408
567
|
def update_parallel(self):
|
|
409
|
-
|
|
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
|
+
"""
|
|
410
574
|
|
|
411
575
|
def touch(self):
|
|
412
|
-
|
|
576
|
+
"""Handle user input.
|
|
577
|
+
|
|
578
|
+
Runs after `update_sequential` each frame.
|
|
579
|
+
"""
|
|
413
580
|
|
|
414
581
|
def terminate(self):
|
|
415
|
-
|
|
582
|
+
"""Finalize before despawning.
|
|
583
|
+
|
|
584
|
+
Runs when the entity is despawned.
|
|
585
|
+
"""
|
|
416
586
|
|
|
417
587
|
@property
|
|
418
588
|
@meta_fn
|
|
419
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
|
+
"""
|
|
420
594
|
if not ctx():
|
|
421
595
|
raise RuntimeError("Calling despawn is only allowed within a callback")
|
|
422
596
|
match self._data_:
|
|
423
|
-
case
|
|
424
|
-
return
|
|
597
|
+
case _ArchetypeSelfData():
|
|
598
|
+
return _deref(ctx().blocks.EntityDespawn, 0, Num)
|
|
425
599
|
case _:
|
|
426
600
|
raise RuntimeError("Despawn is only accessible from the entity itself")
|
|
427
601
|
|
|
@@ -431,8 +605,8 @@ class PlayArchetype(BaseArchetype):
|
|
|
431
605
|
if not ctx():
|
|
432
606
|
raise RuntimeError("Calling despawn is only allowed within a callback")
|
|
433
607
|
match self._data_:
|
|
434
|
-
case
|
|
435
|
-
|
|
608
|
+
case _ArchetypeSelfData():
|
|
609
|
+
_deref(ctx().blocks.EntityDespawn, 0, Num)._set_(value)
|
|
436
610
|
case _:
|
|
437
611
|
raise RuntimeError("Despawn is only accessible from the entity itself")
|
|
438
612
|
|
|
@@ -442,80 +616,128 @@ class PlayArchetype(BaseArchetype):
|
|
|
442
616
|
if not ctx():
|
|
443
617
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
444
618
|
match self._data_:
|
|
445
|
-
case
|
|
446
|
-
return
|
|
447
|
-
case
|
|
448
|
-
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)
|
|
449
623
|
case _:
|
|
450
624
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
451
625
|
|
|
452
626
|
@property
|
|
453
627
|
def index(self) -> int:
|
|
628
|
+
"""The index of this entity."""
|
|
454
629
|
return self._info.index
|
|
455
630
|
|
|
456
631
|
@property
|
|
457
632
|
def is_waiting(self) -> bool:
|
|
633
|
+
"""Whether this entity is waiting to be spawned."""
|
|
458
634
|
return self._info.state == 0
|
|
459
635
|
|
|
460
636
|
@property
|
|
461
637
|
def is_active(self) -> bool:
|
|
638
|
+
"""Whether this entity is active."""
|
|
462
639
|
return self._info.state == 1
|
|
463
640
|
|
|
464
641
|
@property
|
|
465
642
|
def is_despawned(self) -> bool:
|
|
643
|
+
"""Whether this entity is despawned."""
|
|
466
644
|
return self._info.state == 2
|
|
467
645
|
|
|
468
646
|
@property
|
|
469
647
|
def life(self) -> ArchetypeLife:
|
|
648
|
+
"""How this entity contributes to life."""
|
|
470
649
|
if not ctx():
|
|
471
650
|
raise RuntimeError("Calling life is only allowed within a callback")
|
|
472
651
|
match self._data_:
|
|
473
|
-
case
|
|
474
|
-
return
|
|
652
|
+
case _ArchetypeSelfData() | _ArchetypeReferenceData():
|
|
653
|
+
return _deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
475
654
|
case _:
|
|
476
655
|
raise RuntimeError("Life is not available in level data")
|
|
477
656
|
|
|
478
657
|
@property
|
|
479
658
|
def result(self) -> PlayEntityInput:
|
|
659
|
+
"""The result of this entity.
|
|
660
|
+
|
|
661
|
+
Only meaningful for scored entities.
|
|
662
|
+
"""
|
|
480
663
|
if not ctx():
|
|
481
664
|
raise RuntimeError("Calling result is only allowed within a callback")
|
|
482
665
|
match self._data_:
|
|
483
|
-
case
|
|
484
|
-
return
|
|
666
|
+
case _ArchetypeSelfData():
|
|
667
|
+
return _deref(ctx().blocks.EntityInput, 0, PlayEntityInput)
|
|
485
668
|
case _:
|
|
486
669
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
487
670
|
|
|
488
671
|
def ref(self):
|
|
489
|
-
|
|
672
|
+
"""Get a reference to this entity for creating level data.
|
|
673
|
+
|
|
674
|
+
Not valid elsewhere.
|
|
675
|
+
"""
|
|
676
|
+
if not isinstance(self._data_, _ArchetypeLevelData):
|
|
490
677
|
raise RuntimeError("Entity is not level data")
|
|
491
678
|
result = EntityRef[type(self)](index=-1)
|
|
492
679
|
result._ref_ = self
|
|
493
680
|
return result
|
|
494
681
|
|
|
495
682
|
|
|
496
|
-
class WatchArchetype(
|
|
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
|
+
"""
|
|
698
|
+
|
|
497
699
|
_supported_callbacks_ = WATCH_ARCHETYPE_CALLBACKS
|
|
498
700
|
|
|
499
701
|
def preprocess(self):
|
|
500
|
-
|
|
702
|
+
"""Perform upfront processing.
|
|
703
|
+
|
|
704
|
+
Runs first when the level is loaded.
|
|
705
|
+
"""
|
|
501
706
|
|
|
502
707
|
def spawn_time(self) -> float:
|
|
503
|
-
|
|
708
|
+
"""Return the spawn time of the entity."""
|
|
504
709
|
|
|
505
710
|
def despawn_time(self) -> float:
|
|
506
|
-
|
|
711
|
+
"""Return the despawn time of the entity."""
|
|
507
712
|
|
|
508
713
|
def initialize(self):
|
|
509
|
-
|
|
714
|
+
"""Initialize this entity.
|
|
715
|
+
|
|
716
|
+
Runs when this entity is spawned.
|
|
717
|
+
"""
|
|
510
718
|
|
|
511
719
|
def update_sequential(self):
|
|
512
|
-
|
|
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
|
+
"""
|
|
513
727
|
|
|
514
728
|
def update_parallel(self):
|
|
515
|
-
|
|
729
|
+
"""Parallel update callback.
|
|
730
|
+
|
|
731
|
+
Runs after `touch` each frame.
|
|
732
|
+
|
|
733
|
+
This is where most gameplay logic should be placed.
|
|
734
|
+
"""
|
|
516
735
|
|
|
517
736
|
def terminate(self):
|
|
518
|
-
|
|
737
|
+
"""Finalize before despawning.
|
|
738
|
+
|
|
739
|
+
Runs when the entity is despawned.
|
|
740
|
+
"""
|
|
519
741
|
|
|
520
742
|
@property
|
|
521
743
|
@meta_fn
|
|
@@ -523,43 +745,56 @@ class WatchArchetype(BaseArchetype):
|
|
|
523
745
|
if not ctx():
|
|
524
746
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
525
747
|
match self._data_:
|
|
526
|
-
case
|
|
527
|
-
return
|
|
528
|
-
case
|
|
529
|
-
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)
|
|
530
752
|
case _:
|
|
531
753
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
532
754
|
|
|
533
755
|
@property
|
|
534
756
|
def index(self) -> int:
|
|
757
|
+
"""The index of this entity."""
|
|
535
758
|
return self._info.index
|
|
536
759
|
|
|
537
760
|
@property
|
|
538
761
|
def is_active(self) -> bool:
|
|
762
|
+
"""Whether this entity is active."""
|
|
539
763
|
return self._info.state == 1
|
|
540
764
|
|
|
541
765
|
@property
|
|
542
766
|
def life(self) -> ArchetypeLife:
|
|
767
|
+
"""How this entity contributes to life."""
|
|
543
768
|
if not ctx():
|
|
544
769
|
raise RuntimeError("Calling life is only allowed within a callback")
|
|
545
770
|
match self._data_:
|
|
546
|
-
case
|
|
547
|
-
return
|
|
771
|
+
case _ArchetypeSelfData() | _ArchetypeReferenceData():
|
|
772
|
+
return _deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
548
773
|
case _:
|
|
549
774
|
raise RuntimeError("Life is not available in level data")
|
|
550
775
|
|
|
551
776
|
@property
|
|
552
777
|
def result(self) -> WatchEntityInput:
|
|
778
|
+
"""The result of this entity.
|
|
779
|
+
|
|
780
|
+
Only meaningful for scored entities.
|
|
781
|
+
"""
|
|
553
782
|
if not ctx():
|
|
554
783
|
raise RuntimeError("Calling result is only allowed within a callback")
|
|
555
784
|
match self._data_:
|
|
556
|
-
case
|
|
557
|
-
return
|
|
785
|
+
case _ArchetypeSelfData():
|
|
786
|
+
return _deref(ctx().blocks.EntityInput, 0, WatchEntityInput)
|
|
558
787
|
case _:
|
|
559
788
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
560
789
|
|
|
561
790
|
@property
|
|
562
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
|
+
"""
|
|
563
798
|
return self.result.target_time
|
|
564
799
|
|
|
565
800
|
@target_time.setter
|
|
@@ -567,56 +802,86 @@ class WatchArchetype(BaseArchetype):
|
|
|
567
802
|
self.result.target_time = value
|
|
568
803
|
|
|
569
804
|
|
|
570
|
-
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
|
+
|
|
571
821
|
_supported_callbacks_ = PREVIEW_CALLBACKS
|
|
572
822
|
|
|
573
823
|
def preprocess(self):
|
|
574
|
-
|
|
824
|
+
"""Perform upfront processing.
|
|
825
|
+
|
|
826
|
+
Runs first when the level is loaded.
|
|
827
|
+
"""
|
|
575
828
|
|
|
576
829
|
def render(self):
|
|
577
|
-
|
|
830
|
+
"""Render the entity.
|
|
831
|
+
|
|
832
|
+
Runs after `preprocess`.
|
|
833
|
+
"""
|
|
578
834
|
|
|
579
835
|
@property
|
|
580
836
|
def _info(self) -> PreviewEntityInfo:
|
|
581
837
|
if not ctx():
|
|
582
838
|
raise RuntimeError("Calling info is only allowed within a callback")
|
|
583
839
|
match self._data_:
|
|
584
|
-
case
|
|
585
|
-
return
|
|
586
|
-
case
|
|
587
|
-
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)
|
|
588
844
|
case _:
|
|
589
845
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
590
846
|
|
|
591
847
|
@property
|
|
592
848
|
def index(self) -> int:
|
|
849
|
+
"""The index of this entity."""
|
|
593
850
|
return self._info.index
|
|
594
851
|
|
|
595
852
|
|
|
596
853
|
@meta_fn
|
|
597
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
|
+
"""
|
|
598
859
|
if not ctx():
|
|
599
860
|
raise RuntimeError("Calling entity_info_at is only allowed within a callback")
|
|
600
861
|
match ctx().global_state.mode:
|
|
601
862
|
case Mode.PLAY:
|
|
602
|
-
return
|
|
863
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
|
|
603
864
|
case Mode.WATCH:
|
|
604
|
-
return
|
|
865
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
605
866
|
case Mode.PREVIEW:
|
|
606
|
-
return
|
|
867
|
+
return _deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
|
|
607
868
|
case _:
|
|
608
869
|
raise RuntimeError(f"Entity info is not available in mode '{ctx().global_state.mode}'")
|
|
609
870
|
|
|
610
871
|
|
|
611
872
|
@meta_fn
|
|
612
|
-
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
|
+
"""
|
|
613
878
|
archetype = validate_value(archetype)
|
|
614
879
|
archetype = archetype._as_py_()
|
|
615
880
|
if not ctx():
|
|
616
881
|
raise RuntimeError("Calling archetype_life_of is only allowed within a callback")
|
|
617
882
|
match ctx().global_state.mode:
|
|
618
883
|
case Mode.PLAY | Mode.WATCH:
|
|
619
|
-
return
|
|
884
|
+
return _deref(ctx().blocks.ArchetypeLife, archetype.id() * ArchetypeLife._size_(), ArchetypeLife)
|
|
620
885
|
case _:
|
|
621
886
|
raise RuntimeError(f"Archetype life is not available in mode '{ctx().global_state.mode}'")
|
|
622
887
|
|
|
@@ -639,10 +904,19 @@ class PreviewEntityInfo(Record):
|
|
|
639
904
|
|
|
640
905
|
|
|
641
906
|
class ArchetypeLife(Record):
|
|
907
|
+
"""How an entity contributes to life."""
|
|
908
|
+
|
|
642
909
|
perfect_increment: Num
|
|
910
|
+
"""Life increment for a perfect judgment."""
|
|
911
|
+
|
|
643
912
|
great_increment: Num
|
|
913
|
+
"""Life increment for a great judgment."""
|
|
914
|
+
|
|
644
915
|
good_increment: Num
|
|
916
|
+
"""Life increment for a good judgment."""
|
|
917
|
+
|
|
645
918
|
miss_increment: Num
|
|
919
|
+
"""Life increment for a miss judgment."""
|
|
646
920
|
|
|
647
921
|
def update(
|
|
648
922
|
self,
|
|
@@ -651,6 +925,7 @@ class ArchetypeLife(Record):
|
|
|
651
925
|
good_increment: Num | None = None,
|
|
652
926
|
miss_increment: Num | None = None,
|
|
653
927
|
):
|
|
928
|
+
"""Update the life increments."""
|
|
654
929
|
if perfect_increment is not None:
|
|
655
930
|
self.perfect_increment = perfect_increment
|
|
656
931
|
if great_increment is not None:
|
|
@@ -674,12 +949,14 @@ class WatchEntityInput(Record):
|
|
|
674
949
|
bucket_value: float
|
|
675
950
|
|
|
676
951
|
|
|
677
|
-
class EntityRef[A:
|
|
952
|
+
class EntityRef[A: _BaseArchetype](Record):
|
|
953
|
+
"""Reference to another entity."""
|
|
954
|
+
|
|
678
955
|
index: int
|
|
679
956
|
|
|
680
957
|
@classmethod
|
|
681
958
|
def archetype(cls) -> type[A]:
|
|
682
|
-
return cls.
|
|
959
|
+
return cls.type_var_value(A)
|
|
683
960
|
|
|
684
961
|
def get(self) -> A:
|
|
685
962
|
return self.archetype().at(self.index)
|
|
@@ -695,5 +972,10 @@ class EntityRef[A: BaseArchetype](Record):
|
|
|
695
972
|
|
|
696
973
|
|
|
697
974
|
class StandardArchetypeName(StrEnum):
|
|
975
|
+
"""Standard archetype names."""
|
|
976
|
+
|
|
698
977
|
BPM_CHANGE = "#BPM_CHANGE"
|
|
978
|
+
"""Bpm change marker"""
|
|
979
|
+
|
|
699
980
|
TIMESCALE_CHANGE = "#TIMESCALE_CHANGE"
|
|
981
|
+
"""Timescale change marker"""
|