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.

Files changed (77) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/compile.py +8 -8
  21. sonolus/build/engine.py +10 -5
  22. sonolus/script/archetype.py +419 -137
  23. sonolus/script/array.py +25 -8
  24. sonolus/script/array_like.py +297 -0
  25. sonolus/script/bucket.py +73 -11
  26. sonolus/script/containers.py +234 -51
  27. sonolus/script/debug.py +8 -8
  28. sonolus/script/easing.py +147 -105
  29. sonolus/script/effect.py +60 -0
  30. sonolus/script/engine.py +71 -4
  31. sonolus/script/globals.py +66 -32
  32. sonolus/script/instruction.py +79 -25
  33. sonolus/script/internal/builtin_impls.py +138 -27
  34. sonolus/script/internal/constant.py +139 -0
  35. sonolus/script/internal/context.py +14 -5
  36. sonolus/script/internal/dict_impl.py +65 -0
  37. sonolus/script/internal/generic.py +6 -9
  38. sonolus/script/internal/impl.py +38 -13
  39. sonolus/script/internal/introspection.py +5 -2
  40. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  41. sonolus/script/internal/native.py +3 -3
  42. sonolus/script/internal/random.py +67 -0
  43. sonolus/script/internal/range.py +81 -0
  44. sonolus/script/internal/transient.py +51 -0
  45. sonolus/script/internal/tuple_impl.py +113 -0
  46. sonolus/script/interval.py +234 -16
  47. sonolus/script/iterator.py +120 -167
  48. sonolus/script/level.py +24 -0
  49. sonolus/script/num.py +79 -47
  50. sonolus/script/options.py +78 -12
  51. sonolus/script/particle.py +37 -4
  52. sonolus/script/pointer.py +4 -4
  53. sonolus/script/print.py +22 -1
  54. sonolus/script/project.py +8 -0
  55. sonolus/script/{graphics.py → quad.py} +75 -12
  56. sonolus/script/record.py +44 -13
  57. sonolus/script/runtime.py +50 -1
  58. sonolus/script/sprite.py +197 -112
  59. sonolus/script/text.py +2 -0
  60. sonolus/script/timing.py +72 -0
  61. sonolus/script/transform.py +296 -66
  62. sonolus/script/ui.py +134 -78
  63. sonolus/script/values.py +6 -13
  64. sonolus/script/vec.py +118 -3
  65. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  66. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  67. sonolus/backend/dead_code.py +0 -80
  68. sonolus/backend/optimize.py +0 -37
  69. sonolus/backend/simplify.py +0 -47
  70. sonolus/script/comptime.py +0 -160
  71. sonolus/script/random.py +0 -14
  72. sonolus/script/range.py +0 -58
  73. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  74. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  75. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
  76. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -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 deref
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
- ENTITY_MEMORY_SIZE = 64
29
- ENTITY_DATA_SIZE = 32
30
- ENTITY_SHARED_MEMORY_SIZE = 32
28
+ _ENTITY_MEMORY_SIZE = 64
29
+ _ENTITY_DATA_SIZE = 32
30
+ _ENTITY_SHARED_MEMORY_SIZE = 32
31
31
 
32
32
 
33
- class StorageType(Enum):
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 ArchetypeFieldInfo:
41
+ class _ArchetypeFieldInfo:
42
42
  name: str | None
43
- storage: StorageType
43
+ storage: _StorageType
44
44
 
45
45
 
46
- class ArchetypeField(SonolusDescriptor):
47
- def __init__(self, name: str, data_name: str, storage: StorageType, offset: int, type_: type[Value]):
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: BaseArchetype, owner):
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 StorageType.IMPORTED:
59
+ case _StorageType.IMPORTED:
60
60
  match instance._data_:
61
- case ArchetypeSelfData():
62
- result = deref(ctx().blocks.EntityData, self.offset, self.type)
63
- case ArchetypeReferenceData(index=index):
64
- result = deref(ctx().blocks.EntityDataArray, self.offset + index * ENTITY_DATA_SIZE, self.type)
65
- case ArchetypeLevelData(values=values):
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 StorageType.EXPORTED:
69
+ case _StorageType.EXPORTED:
68
70
  raise RuntimeError("Exported fields are write-only")
69
- case StorageType.MEMORY:
71
+ case _StorageType.MEMORY:
70
72
  match instance._data_:
71
- case ArchetypeSelfData():
72
- result = deref(ctx().blocks.EntityMemory, self.offset, self.type)
73
- case ArchetypeReferenceData():
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 ArchetypeLevelData():
77
+ case _ArchetypeLevelData():
76
78
  raise RuntimeError("Entity memory is not available in level data")
77
- case StorageType.SHARED:
79
+ case _StorageType.SHARED:
78
80
  match instance._data_:
79
- case ArchetypeSelfData():
80
- result = deref(ctx().blocks.EntitySharedMemory, self.offset, self.type)
81
- case ArchetypeReferenceData(index=index):
82
- result = deref(
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 * ENTITY_SHARED_MEMORY_SIZE,
86
+ Num._accept_(self.offset) + index * _ENTITY_SHARED_MEMORY_SIZE,
85
87
  self.type,
86
88
  )
87
- case ArchetypeLevelData():
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: BaseArchetype, value):
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 StorageType.IMPORTED:
105
+ case _StorageType.IMPORTED:
104
106
  match instance._data_:
105
- case ArchetypeSelfData():
106
- target = deref(ctx().blocks.EntityData, self.offset, self.type)
107
- case ArchetypeReferenceData(index=index):
108
- target = deref(ctx().blocks.EntityDataArray, self.offset + index * ENTITY_DATA_SIZE, self.type)
109
- case ArchetypeLevelData(values=values):
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 StorageType.EXPORTED:
115
+ case _StorageType.EXPORTED:
112
116
  match instance._data_:
113
- case ArchetypeSelfData():
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 ArchetypeReferenceData():
124
+ case _ArchetypeReferenceData():
121
125
  raise RuntimeError("Exported fields of other entities are not accessible")
122
- case ArchetypeLevelData():
126
+ case _ArchetypeLevelData():
123
127
  raise RuntimeError("Exported fields are not available in level data")
124
- case StorageType.MEMORY:
128
+ case _StorageType.MEMORY:
125
129
  match instance._data_:
126
- case ArchetypeSelfData():
127
- target = deref(ctx().blocks.EntityMemory, self.offset, self.type)
128
- case ArchetypeReferenceData():
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 ArchetypeLevelData():
134
+ case _ArchetypeLevelData():
131
135
  raise RuntimeError("Entity memory is not available in level data")
132
- case StorageType.SHARED:
136
+ case _StorageType.SHARED:
133
137
  match instance._data_:
134
- case ArchetypeSelfData():
135
- target = deref(ctx().blocks.EntitySharedMemory, self.offset, self.type)
136
- case ArchetypeReferenceData(index=index):
137
- target = deref(
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 * ENTITY_SHARED_MEMORY_SIZE,
143
+ Num._accept_(self.offset) + index * _ENTITY_SHARED_MEMORY_SIZE,
140
144
  self.type,
141
145
  )
142
- case ArchetypeLevelData():
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
- return ArchetypeFieldInfo(name, StorageType.IMPORTED)
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
- return ArchetypeFieldInfo(name, StorageType.EXPORTED)
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
- return ArchetypeFieldInfo(None, StorageType.MEMORY)
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
- return ArchetypeFieldInfo(None, StorageType.SHARED)
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
- _annotation_defaults: dict[Callable, ArchetypeFieldInfo] = {
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 ArchetypeSelfData:
289
+ class _ArchetypeSelfData:
194
290
  pass
195
291
 
196
292
 
197
- class ArchetypeReferenceData:
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 ArchetypeLevelData:
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 ArchetypeData = ArchetypeSelfData | ArchetypeReferenceData | ArchetypeLevelData
307
+ type _ArchetypeData = _ArchetypeSelfData | _ArchetypeReferenceData | _ArchetypeLevelData
212
308
 
213
309
 
214
- class BaseArchetype:
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, ArchetypeField]]
221
- _exported_fields_: ClassVar[dict[str, ArchetypeField]]
222
- _memory_fields_: ClassVar[dict[str, ArchetypeField]]
223
- _shared_memory_fields_: ClassVar[dict[str, ArchetypeField]]
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_: ArchetypeData
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_ = ArchetypeLevelData(values=values)
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_ = ArchetypeSelfData()
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_ = ArchetypeReferenceData(index=Num._accept_(index))
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_, ArchetypeLevelData):
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__ == BaseArchetype.__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 get_field_specifiers(cls).items():
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, ArchetypeFieldInfo):
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 StorageType.IMPORTED:
341
- cls._imported_fields_[name] = ArchetypeField(
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 StorageType.EXPORTED:
347
- cls._exported_fields_[name] = ArchetypeField(
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 StorageType.MEMORY:
353
- cls._memory_fields_[name] = ArchetypeField(
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 StorageType.SHARED:
359
- cls._shared_memory_fields_[name] = ArchetypeField(
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(BaseArchetype):
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
- pass
535
+ """Perform upfront processing.
536
+
537
+ Runs first when the level is loaded.
538
+ """
395
539
 
396
540
  def spawn_order(self) -> float:
397
- pass
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
- pass
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
- pass
553
+ """Initialize this entity.
554
+
555
+ Runs when this entity is spawned.
556
+ """
404
557
 
405
558
  def update_sequential(self):
406
- pass
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
- pass
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
- pass
576
+ """Handle user input.
577
+
578
+ Runs after `update_sequential` each frame.
579
+ """
413
580
 
414
581
  def terminate(self):
415
- pass
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 ArchetypeSelfData():
424
- return deref(ctx().blocks.EntityDespawn, 0, Num)
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 ArchetypeSelfData():
435
- deref(ctx().blocks.EntityDespawn, 0, Num)._set_(value)
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 ArchetypeSelfData():
446
- return deref(ctx().blocks.EntityInfo, 0, PlayEntityInfo)
447
- case ArchetypeReferenceData(index=index):
448
- return deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
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 ArchetypeSelfData() | ArchetypeReferenceData():
474
- return deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
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 ArchetypeSelfData():
484
- return deref(ctx().blocks.EntityInput, 0, PlayEntityInput)
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
- if not isinstance(self._data_, ArchetypeLevelData):
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(BaseArchetype):
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
- pass
702
+ """Perform upfront processing.
703
+
704
+ Runs first when the level is loaded.
705
+ """
501
706
 
502
707
  def spawn_time(self) -> float:
503
- pass
708
+ """Return the spawn time of the entity."""
504
709
 
505
710
  def despawn_time(self) -> float:
506
- pass
711
+ """Return the despawn time of the entity."""
507
712
 
508
713
  def initialize(self):
509
- pass
714
+ """Initialize this entity.
715
+
716
+ Runs when this entity is spawned.
717
+ """
510
718
 
511
719
  def update_sequential(self):
512
- pass
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
- pass
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
- pass
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 ArchetypeSelfData():
527
- return deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
528
- case ArchetypeReferenceData(index=index):
529
- return deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), PlayEntityInfo)
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 ArchetypeSelfData() | ArchetypeReferenceData():
547
- return deref(ctx().blocks.ArchetypeLife, self.id() * ArchetypeLife._size_(), ArchetypeLife)
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 ArchetypeSelfData():
557
- return deref(ctx().blocks.EntityInput, 0, WatchEntityInput)
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(BaseArchetype):
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
- pass
824
+ """Perform upfront processing.
825
+
826
+ Runs first when the level is loaded.
827
+ """
575
828
 
576
829
  def render(self):
577
- pass
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 ArchetypeSelfData():
585
- return deref(ctx().blocks.EntityInfo, 0, PreviewEntityInfo)
586
- case ArchetypeReferenceData(index=index):
587
- return deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
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 deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
863
+ return _deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
603
864
  case Mode.WATCH:
604
- return deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
865
+ return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
605
866
  case Mode.PREVIEW:
606
- return deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
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[BaseArchetype] | BaseArchetype) -> ArchetypeLife:
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 deref(ctx().blocks.ArchetypeLife, archetype.id() * ArchetypeLife._size_(), ArchetypeLife)
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: BaseArchetype](Record):
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._get_type_arg_(A)
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"""