sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

Files changed (79) 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/cli.py +14 -3
  21. sonolus/build/compile.py +8 -8
  22. sonolus/build/engine.py +10 -5
  23. sonolus/build/project.py +30 -1
  24. sonolus/script/archetype.py +429 -138
  25. sonolus/script/array.py +25 -8
  26. sonolus/script/array_like.py +297 -0
  27. sonolus/script/bucket.py +73 -11
  28. sonolus/script/containers.py +234 -51
  29. sonolus/script/debug.py +8 -8
  30. sonolus/script/easing.py +147 -105
  31. sonolus/script/effect.py +60 -0
  32. sonolus/script/engine.py +71 -4
  33. sonolus/script/globals.py +66 -32
  34. sonolus/script/instruction.py +79 -25
  35. sonolus/script/internal/builtin_impls.py +138 -27
  36. sonolus/script/internal/constant.py +139 -0
  37. sonolus/script/internal/context.py +14 -5
  38. sonolus/script/internal/dict_impl.py +65 -0
  39. sonolus/script/internal/generic.py +6 -9
  40. sonolus/script/internal/impl.py +38 -13
  41. sonolus/script/internal/introspection.py +5 -2
  42. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  43. sonolus/script/internal/native.py +3 -3
  44. sonolus/script/internal/random.py +67 -0
  45. sonolus/script/internal/range.py +81 -0
  46. sonolus/script/internal/transient.py +51 -0
  47. sonolus/script/internal/tuple_impl.py +113 -0
  48. sonolus/script/interval.py +234 -16
  49. sonolus/script/iterator.py +120 -167
  50. sonolus/script/level.py +24 -0
  51. sonolus/script/num.py +79 -47
  52. sonolus/script/options.py +78 -12
  53. sonolus/script/particle.py +37 -4
  54. sonolus/script/pointer.py +4 -4
  55. sonolus/script/print.py +22 -1
  56. sonolus/script/project.py +59 -0
  57. sonolus/script/{graphics.py → quad.py} +75 -12
  58. sonolus/script/record.py +44 -13
  59. sonolus/script/runtime.py +50 -1
  60. sonolus/script/sprite.py +198 -115
  61. sonolus/script/text.py +2 -0
  62. sonolus/script/timing.py +72 -0
  63. sonolus/script/transform.py +296 -66
  64. sonolus/script/ui.py +134 -78
  65. sonolus/script/values.py +6 -13
  66. sonolus/script/vec.py +118 -3
  67. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
  68. sonolus_py-0.1.6.dist-info/RECORD +89 -0
  69. sonolus/backend/dead_code.py +0 -80
  70. sonolus/backend/optimize.py +0 -37
  71. sonolus/backend/simplify.py +0 -47
  72. sonolus/script/comptime.py +0 -160
  73. sonolus/script/random.py +0 -14
  74. sonolus/script/range.py +0 -58
  75. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  76. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
  78. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
  79. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
sonolus/script/level.py CHANGED
@@ -5,6 +5,19 @@ from sonolus.script.archetype import PlayArchetype, StandardArchetypeName, Stand
5
5
 
6
6
 
7
7
  class Level:
8
+ """A Sonolus level.
9
+
10
+ Args:
11
+ name: The name of the level.
12
+ title: The title of the level.
13
+ rating: The rating of the level.
14
+ artists: The artists of the level.
15
+ author: The author of the level.
16
+ cover: The cover of the level.
17
+ bgm: The background music of the level.
18
+ data: The data of the level.
19
+ """
20
+
8
21
  version = 1
9
22
 
10
23
  def __init__(
@@ -30,6 +43,13 @@ class Level:
30
43
 
31
44
 
32
45
  class LevelData:
46
+ """The data of a Sonolus level.
47
+
48
+ Args:
49
+ bgm_offset: The background music audio offset.
50
+ entities: The entities of the level.
51
+ """
52
+
33
53
  bgm_offset: float
34
54
  entities: list[PlayArchetype]
35
55
 
@@ -39,6 +59,8 @@ class LevelData:
39
59
 
40
60
 
41
61
  class BpmChange(PlayArchetype):
62
+ """The standard bpm change archetype."""
63
+
42
64
  name = StandardArchetypeName.BPM_CHANGE
43
65
 
44
66
  beat: StandardImport.BEAT
@@ -46,6 +68,8 @@ class BpmChange(PlayArchetype):
46
68
 
47
69
 
48
70
  class TimescaleChange(PlayArchetype):
71
+ """The standard timescale change archetype."""
72
+
49
73
  name = StandardArchetypeName.TIMESCALE_CHANGE
50
74
 
51
75
  beat: StandardImport.BEAT
sonolus/script/num.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  import operator
5
5
  from collections.abc import Callable, Iterable
6
- from typing import TYPE_CHECKING, Any, Self, TypeIs, final
6
+ from typing import TYPE_CHECKING, Any, Self, TypeIs, final, runtime_checkable
7
7
 
8
8
  from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
9
9
  from sonolus.backend.ops import Op
@@ -14,18 +14,18 @@ from sonolus.script.internal.impl import meta_fn
14
14
  from sonolus.script.internal.value import Value
15
15
 
16
16
 
17
- class NumMeta(type):
17
+ class _NumMeta(type):
18
18
  def __instancecheck__(cls, instance):
19
- return isinstance(instance, float | int | bool) or is_num(instance)
19
+ return isinstance(instance, float | int | bool) or _is_num(instance)
20
20
 
21
21
 
22
- def is_num(value: Any) -> TypeIs[Num]:
22
+ def _is_num(value: Any) -> TypeIs[Num]:
23
23
  """Check if a value is a precisely Num instance."""
24
24
  return type.__instancecheck__(Num, value) # type: ignore # noqa: PLC2801
25
25
 
26
26
 
27
27
  @final
28
- class _Num(Value, metaclass=NumMeta):
28
+ class _Num(Value, metaclass=_NumMeta):
29
29
  # This works for ints, floats, and bools
30
30
  # Since we don't support complex numbers, real is equal to the original number
31
31
  __match_args__ = ("real",)
@@ -37,7 +37,7 @@ class _Num(Value, metaclass=NumMeta):
37
37
  raise TypeError("Cannot create a Num from a complex number")
38
38
  if isinstance(data, int):
39
39
  data = float(data)
40
- if is_num(data):
40
+ if _is_num(data):
41
41
  raise InternalError("Cannot create a Num from a Num")
42
42
  self.data = data
43
43
 
@@ -73,7 +73,7 @@ class _Num(Value, metaclass=NumMeta):
73
73
  def _accept_(cls, value: Any) -> Self:
74
74
  if not cls._accepts_(value):
75
75
  raise TypeError(f"Cannot accept {value}")
76
- if is_num(value):
76
+ if _is_num(value):
77
77
  return value
78
78
  return cls(value)
79
79
 
@@ -122,7 +122,10 @@ class _Num(Value, metaclass=NumMeta):
122
122
  raise ValueError("Cannot assign to a number")
123
123
 
124
124
  def _copy_(self) -> Self:
125
- return self
125
+ if ctx():
126
+ return self._get_()
127
+ else:
128
+ return Num(self.data)
126
129
 
127
130
  @classmethod
128
131
  def _alloc_(cls) -> Self:
@@ -168,7 +171,7 @@ class _Num(Value, metaclass=NumMeta):
168
171
  def __eq__(self, other) -> Self:
169
172
  def const_fn(a: Self, b: Self) -> Num | None:
170
173
  if a._is_py_() and b._is_py_():
171
- return Num(a.data == b.data)
174
+ return Num(a._as_py_() == b._as_py_())
172
175
  if a.data == b.data:
173
176
  return Num(True)
174
177
  return None
@@ -184,7 +187,7 @@ class _Num(Value, metaclass=NumMeta):
184
187
  def __ne__(self, other) -> Self:
185
188
  def const_fn(a: Self, b: Self) -> Num | None:
186
189
  if a._is_py_() and b._is_py_():
187
- return Num(a.data != b.data)
190
+ return Num(a._as_py_() != b._as_py_())
188
191
  if a.data == b.data:
189
192
  return Num(False)
190
193
  return None
@@ -195,7 +198,7 @@ class _Num(Value, metaclass=NumMeta):
195
198
  def __lt__(self, other) -> Self:
196
199
  def const_fn(a: Self, b: Self) -> Num | None:
197
200
  if a._is_py_() and b._is_py_():
198
- return Num(a.data < b.data)
201
+ return Num(a._as_py_() < b._as_py_())
199
202
  if a.data == b.data:
200
203
  return Num(False)
201
204
  return None
@@ -206,7 +209,9 @@ class _Num(Value, metaclass=NumMeta):
206
209
  def __le__(self, other) -> Self:
207
210
  def const_fn(a: Self, b: Self) -> Num | None:
208
211
  if a._is_py_() and b._is_py_():
209
- return Num(a.data <= b.data)
212
+ return Num(a._as_py_() <= b._as_py_())
213
+ if a.data == b.data:
214
+ return Num(True)
210
215
  return None
211
216
 
212
217
  return self._bin_op(other, const_fn, Op.LessOr)
@@ -215,7 +220,7 @@ class _Num(Value, metaclass=NumMeta):
215
220
  def __gt__(self, other) -> Self:
216
221
  def const_fn(a: Self, b: Self) -> Num | None:
217
222
  if a._is_py_() and b._is_py_():
218
- return Num(a.data > b.data)
223
+ return Num(a._as_py_() > b._as_py_())
219
224
  if a.data == b.data:
220
225
  return Num(False)
221
226
  return None
@@ -226,7 +231,9 @@ class _Num(Value, metaclass=NumMeta):
226
231
  def __ge__(self, other) -> Self:
227
232
  def const_fn(a: Self, b: Self) -> Num | None:
228
233
  if a._is_py_() and b._is_py_():
229
- return Num(a.data >= b.data)
234
+ return Num(a._as_py_() >= b._as_py_())
235
+ if a.data == b.data:
236
+ return Num(True)
230
237
  return None
231
238
 
232
239
  return self._bin_op(other, const_fn, Op.GreaterOr)
@@ -235,10 +242,10 @@ class _Num(Value, metaclass=NumMeta):
235
242
  def __add__(self, other) -> Self:
236
243
  def const_fn(a: Self, b: Self) -> Num | None:
237
244
  if a._is_py_() and b._is_py_():
238
- return Num(a.data + b.data)
239
- if a._is_py_() and a.data == 0:
245
+ return Num(a._as_py_() + b._as_py_())
246
+ if a._is_py_() and a._as_py_() == 0:
240
247
  return b
241
- if b._is_py_() and b.data == 0:
248
+ if b._is_py_() and b._as_py_() == 0:
242
249
  return a
243
250
  return None
244
251
 
@@ -248,10 +255,10 @@ class _Num(Value, metaclass=NumMeta):
248
255
  def __sub__(self, other) -> Self:
249
256
  def const_fn(a: Self, b: Self) -> Num | None:
250
257
  if a._is_py_() and b._is_py_():
251
- return Num(a.data - b.data)
252
- if a._is_py_() and a.data == 0:
258
+ return Num(a._as_py_() - b._as_py_())
259
+ if a._is_py_() and a._as_py_() == 0:
253
260
  return -b
254
- if b._is_py_() and b.data == 0:
261
+ if b._is_py_() and b._as_py_() == 0:
255
262
  return a
256
263
  return None
257
264
 
@@ -261,16 +268,16 @@ class _Num(Value, metaclass=NumMeta):
261
268
  def __mul__(self, other) -> Self:
262
269
  def const_fn(a: Self, b: Self) -> Num | None:
263
270
  if a._is_py_() and b._is_py_():
264
- return Num(a.data * b.data)
271
+ return Num(a._as_py_() * b._as_py_())
265
272
  if a._is_py_():
266
- if a.data == 0:
273
+ if a._as_py_() == 0:
267
274
  return Num(0)
268
- if a.data == 1:
275
+ if a._as_py_() == 1:
269
276
  return b
270
277
  if b._is_py_():
271
- if b.data == 0:
278
+ if b._as_py_() == 0:
272
279
  return Num(0)
273
- if b.data == 1:
280
+ if b._as_py_() == 1:
274
281
  return a
275
282
  return None
276
283
 
@@ -280,13 +287,13 @@ class _Num(Value, metaclass=NumMeta):
280
287
  def __truediv__(self, other) -> Self:
281
288
  def const_fn(a: Self, b: Self) -> Num | None:
282
289
  if a._is_py_() and b._is_py_():
283
- if b.data == 0:
290
+ if b._as_py_() == 0:
284
291
  return None
285
- return Num(a.data / b.data)
292
+ return Num(a._as_py_() / b._as_py_())
286
293
  if b._is_py_():
287
- if b.data == 1:
294
+ if b._as_py_() == 1:
288
295
  return a
289
- if b.data == -1:
296
+ if b._as_py_() == -1:
290
297
  return -a
291
298
  return None
292
299
 
@@ -296,13 +303,13 @@ class _Num(Value, metaclass=NumMeta):
296
303
  def __floordiv__(self, other) -> Self:
297
304
  def const_fn(a: Self, b: Self) -> Num | None:
298
305
  if a._is_py_() and b._is_py_():
299
- if b.data == 0:
306
+ if b._as_py_() == 0:
300
307
  return None
301
- return Num(a.data // b.data)
308
+ return Num(a._as_py_() // b._as_py_())
302
309
  if b._is_py_():
303
- if b.data == 1:
310
+ if b._as_py_() == 1:
304
311
  return a
305
- if b.data == -1:
312
+ if b._as_py_() == -1:
306
313
  return -a
307
314
  return None
308
315
 
@@ -312,9 +319,9 @@ class _Num(Value, metaclass=NumMeta):
312
319
  def __mod__(self, other) -> Self:
313
320
  def const_fn(a: Self, b: Self) -> Num | None:
314
321
  if a._is_py_() and b._is_py_():
315
- if b.data == 0:
322
+ if b._as_py_() == 0:
316
323
  return None
317
- return Num(a.data % b.data)
324
+ return Num(a._as_py_() % b._as_py_())
318
325
  return None
319
326
 
320
327
  return self._bin_op(other, const_fn, Op.Mod)
@@ -324,13 +331,13 @@ class _Num(Value, metaclass=NumMeta):
324
331
  def const_fn(a: Self, b: Self) -> Num | None:
325
332
  if a._is_py_() and b._is_py_():
326
333
  try:
327
- return Num(a.data**b.data)
334
+ return Num(a._as_py_() ** b._as_py_())
328
335
  except OverflowError:
329
336
  return None
330
337
  if b._is_py_():
331
- if b.data == 0:
338
+ if b._as_py_() == 0:
332
339
  return Num(1)
333
- if b.data == 1:
340
+ if b._as_py_() == 1:
334
341
  return a
335
342
  return None
336
343
 
@@ -360,9 +367,9 @@ class _Num(Value, metaclass=NumMeta):
360
367
  def and_(self, other) -> Self:
361
368
  def const_fn(a: Self, b: Self) -> Num | None:
362
369
  if a._is_py_() and b._is_py_():
363
- return Num(a.data and b.data)
370
+ return Num(a._as_py_() and b._as_py_())
364
371
  if a._is_py_():
365
- if a.data == 0:
372
+ if a._as_py_() == 0:
366
373
  return a
367
374
  else:
368
375
  return b
@@ -373,9 +380,9 @@ class _Num(Value, metaclass=NumMeta):
373
380
  def or_(self, other) -> Self:
374
381
  def const_fn(a: Self, b: Self) -> Num | None:
375
382
  if a._is_py_() and b._is_py_():
376
- return Num(a.data or b.data)
383
+ return Num(a._as_py_() or b._as_py_())
377
384
  if a._is_py_():
378
- if a.data == 0:
385
+ if a._as_py_() == 0:
379
386
  return b
380
387
  else:
381
388
  return a
@@ -396,11 +403,36 @@ class _Num(Value, metaclass=NumMeta):
396
403
 
397
404
 
398
405
  if TYPE_CHECKING:
399
-
400
- class __Num(float, int, bool, _Num): # type: ignore
401
- pass
402
-
403
- Num = __Num | _Num | float | int | bool
406
+ from typing import Protocol
407
+
408
+ @runtime_checkable
409
+ class Num[T](Protocol, int, bool, float):
410
+ def __add__(self, other: T, /) -> Num: ...
411
+ def __sub__(self, other: T, /) -> Num: ...
412
+ def __mul__(self, other: T, /) -> Num: ...
413
+ def __truediv__(self, other: T, /) -> Num: ...
414
+ def __floordiv__(self, other: T, /) -> Num: ...
415
+ def __mod__(self, other: T, /) -> Num: ...
416
+ def __pow__(self, other: T, /) -> Num: ...
417
+
418
+ def __neg__(self, /) -> Num: ...
419
+ def __pos__(self, /) -> Num: ...
420
+ def __abs__(self, /) -> Num: ...
421
+
422
+ def __eq__(self, other: Any, /) -> bool: ...
423
+ def __ne__(self, other: Any, /) -> bool: ...
424
+ def __lt__(self, other: T, /) -> bool: ...
425
+ def __le__(self, other: T, /) -> bool: ...
426
+ def __gt__(self, other: T, /) -> bool: ...
427
+ def __ge__(self, other: T, /) -> bool: ...
428
+
429
+ def __hash__(self, /) -> int: ...
430
+
431
+ @property
432
+ def real(self) -> Num: ...
433
+
434
+ @property
435
+ def imag(self) -> Num: ...
404
436
  else:
405
437
  # Need to do this to satisfy type checkers (especially Pycharm)
406
438
  _Num.__name__ = "Num"
sonolus/script/options.py CHANGED
@@ -13,7 +13,7 @@ from sonolus.script.num import Num
13
13
 
14
14
 
15
15
  @dataclass
16
- class SliderOption:
16
+ class _SliderOption:
17
17
  name: str | None
18
18
  standard: bool
19
19
  advanced: bool
@@ -43,7 +43,7 @@ class SliderOption:
43
43
 
44
44
 
45
45
  @dataclass
46
- class ToggleOption:
46
+ class _ToggleOption:
47
47
  name: str | None
48
48
  standard: bool
49
49
  advanced: bool
@@ -64,7 +64,7 @@ class ToggleOption:
64
64
 
65
65
 
66
66
  @dataclass
67
- class SelectOption:
67
+ class _SelectOption:
68
68
  name: str | None
69
69
  standard: bool
70
70
  advanced: bool
@@ -98,7 +98,20 @@ def slider_option(
98
98
  unit: str | None = None,
99
99
  scope: str | None = None,
100
100
  ) -> Any:
101
- return SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
101
+ """Define a slider option.
102
+
103
+ Args:
104
+ name: The name of the option.
105
+ standard: Whether the option is standard.
106
+ advanced: Whether the option is advanced.
107
+ default: The default value of the option.
108
+ min: The minimum value of the option.
109
+ max: The maximum value of the option.
110
+ step: The step value of the option.
111
+ unit: The unit of the option.
112
+ scope: The scope of the option.
113
+ """
114
+ return _SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
102
115
 
103
116
 
104
117
  def toggle_option(
@@ -109,7 +122,16 @@ def toggle_option(
109
122
  default: bool,
110
123
  scope: str | None = None,
111
124
  ) -> Any:
112
- return ToggleOption(name, standard, advanced, scope, default)
125
+ """Define a toggle option.
126
+
127
+ Args:
128
+ name: The name of the option.
129
+ standard: Whether the option is standard.
130
+ advanced: Whether the option is advanced.
131
+ default: The default value of the option.
132
+ scope: The scope of the option.
133
+ """
134
+ return _ToggleOption(name, standard, advanced, scope, default)
113
135
 
114
136
 
115
137
  def select_option(
@@ -121,18 +143,28 @@ def select_option(
121
143
  values: list[str],
122
144
  scope: str | None = None,
123
145
  ) -> Any:
124
- return SelectOption(name, standard, advanced, scope, default, values)
146
+ """Define a select option.
147
+
148
+ Args:
149
+ name: The name of the option.
150
+ standard: Whether the option is standard.
151
+ advanced: Whether the option is advanced.
152
+ default: The default value of the option.
153
+ values: The values of the option.
154
+ scope: The scope of the option.
155
+ """
156
+ return _SelectOption(name, standard, advanced, scope, default, values)
125
157
 
126
158
 
127
159
  type Options = NewType("Options", Any)
128
- type OptionInfo = SliderOption | ToggleOption | SelectOption
160
+ type _OptionInfo = _SliderOption | _ToggleOption | _SelectOption
129
161
 
130
162
 
131
- class OptionField(SonolusDescriptor):
132
- info: OptionInfo
163
+ class _OptionField(SonolusDescriptor):
164
+ info: _OptionInfo
133
165
  index: int
134
166
 
135
- def __init__(self, info: OptionInfo, index: int):
167
+ def __init__(self, info: _OptionInfo, index: int):
136
168
  self.info = info
137
169
  self.index = index
138
170
 
@@ -160,6 +192,40 @@ class OptionField(SonolusDescriptor):
160
192
 
161
193
  @dataclass_transform()
162
194
  def options[T](cls: type[T]) -> T | Options:
195
+ """Decorator to define options.
196
+
197
+ Usage:
198
+ ```python
199
+ @options
200
+ class Options:
201
+ slider_option: float = slider_option(
202
+ name='Slider Option',
203
+ standard=True,
204
+ advanced=False,
205
+ default=0.5,
206
+ min=0,
207
+ max=1,
208
+ step=0.1,
209
+ unit='unit',
210
+ scope='scope',
211
+ )
212
+ toggle_option: bool = toggle_option(
213
+ name='Toggle Option',
214
+ standard=True,
215
+ advanced=False,
216
+ default=True,
217
+ scope='scope',
218
+ )
219
+ select_option: int = select_option(
220
+ name='Select Option',
221
+ standard=True,
222
+ advanced=False,
223
+ default='value',
224
+ values=['value'],
225
+ scope='scope',
226
+ )
227
+ ```
228
+ """
163
229
  if len(cls.__bases__) != 1:
164
230
  raise ValueError("Options class must not inherit from any class (except object)")
165
231
  instance = cls()
@@ -175,12 +241,12 @@ def options[T](cls: type[T]) -> T | Options:
175
241
  if annotation_type is not Num:
176
242
  raise TypeError(f"Invalid annotation type for options: {annotation_type}")
177
243
  annotation_value = annotation_values[0]
178
- if not isinstance(annotation_value, SliderOption | ToggleOption | SelectOption):
244
+ if not isinstance(annotation_value, _SliderOption | _ToggleOption | _SelectOption):
179
245
  raise TypeError(f"Invalid annotation value for options: {annotation_value}")
180
246
  if annotation_value.name is None:
181
247
  annotation_value.name = name
182
248
  entries.append(annotation_value)
183
- setattr(cls, name, OptionField(annotation_value, i))
249
+ setattr(cls, name, _OptionField(annotation_value, i))
184
250
  instance._options_ = entries
185
251
  instance._is_comptime_value_ = True
186
252
  return instance
@@ -4,29 +4,50 @@ from dataclasses import dataclass
4
4
  from typing import Annotated, Any, NewType, dataclass_transform, get_origin
5
5
 
6
6
  from sonolus.backend.ops import Op
7
- from sonolus.script.graphics import QuadLike, flatten_quad
8
7
  from sonolus.script.internal.introspection import get_field_specifiers
9
8
  from sonolus.script.internal.native import native_function
9
+ from sonolus.script.quad import QuadLike, flatten_quad
10
10
  from sonolus.script.record import Record
11
11
 
12
12
 
13
13
  class Particle(Record):
14
+ """A particle effect."""
15
+
14
16
  id: int
15
17
 
16
18
  def is_available(self) -> bool:
19
+ """Check if the particle effect is available."""
17
20
  return _has_particle_effect(self.id)
18
21
 
19
22
  def spawn(self, quad: QuadLike, duration: float, loop: bool = False) -> ParticleHandle:
23
+ """Spawn the particle effect.
24
+
25
+ Args:
26
+ quad: The quad to spawn the particle effect on.
27
+ duration: The duration of the particle effect.
28
+ loop: Whether to loop the particle effect.
29
+
30
+ Returns:
31
+ ParticleHandle: A handle to the spawned particle effect.
32
+ """
20
33
  return ParticleHandle(_spawn_particle_effect(self.id, *flatten_quad(quad), duration, loop))
21
34
 
22
35
 
23
36
  class ParticleHandle(Record):
37
+ """A handle to a looping particle effect."""
38
+
24
39
  id: int
25
40
 
26
41
  def move(self, quad: QuadLike) -> None:
42
+ """Move the particle effect to a new location.
43
+
44
+ Args:
45
+ quad: The new quad to move the particle effect to.
46
+ """
27
47
  _move_particle_effect(self.id, *flatten_quad(quad))
28
48
 
29
49
  def destroy(self) -> None:
50
+ """Destroy the particle effect."""
30
51
  _destroy_particle_effect(self.id)
31
52
 
32
53
 
@@ -65,12 +86,12 @@ def _destroy_particle_effect(handle: int) -> None:
65
86
 
66
87
 
67
88
  @dataclass
68
- class ParticleInfo:
89
+ class _ParticleInfo:
69
90
  name: str
70
91
 
71
92
 
72
93
  def particle(name: str) -> Any:
73
- return ParticleInfo(name)
94
+ return _ParticleInfo(name)
74
95
 
75
96
 
76
97
  type Particles = NewType("Particles", Any)
@@ -78,6 +99,16 @@ type Particles = NewType("Particles", Any)
78
99
 
79
100
  @dataclass_transform()
80
101
  def particles[T](cls: type[T]) -> T | Particles:
102
+ """Decorator to define particles.
103
+
104
+ Usage:
105
+ ```python
106
+ @particles
107
+ class Particles:
108
+ tap: StandardParticle.NOTE_CIRCULAR_TAP_RED
109
+ other: Particle = particle("other")
110
+ ```
111
+ """
81
112
  if len(cls.__bases__) != 1:
82
113
  raise ValueError("Particles class must not inherit from any class (except object)")
83
114
  instance = cls()
@@ -89,7 +120,7 @@ def particles[T](cls: type[T]) -> T | Particles:
89
120
  annotation_values = annotation.__metadata__
90
121
  if annotation_type is not Particle:
91
122
  raise TypeError(f"Invalid annotation for particles: {annotation}, expected annotation of type Particle")
92
- if len(annotation_values) != 1 or not isinstance(annotation_values[0], ParticleInfo):
123
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], _ParticleInfo):
93
124
  raise TypeError(
94
125
  f"Invalid annotation for particles: {annotation}, expected a single string annotation value"
95
126
  )
@@ -102,6 +133,8 @@ def particles[T](cls: type[T]) -> T | Particles:
102
133
 
103
134
 
104
135
  class StandardParticle:
136
+ """Standard particles."""
137
+
105
138
  NOTE_CIRCULAR_TAP_NEUTRAL = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_NEUTRAL")]
106
139
  NOTE_CIRCULAR_TAP_RED = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_RED")]
107
140
  NOTE_CIRCULAR_TAP_GREEN = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_GREEN")]
sonolus/script/pointer.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from sonolus.backend.place import BlockPlace
2
2
  from sonolus.script.internal.impl import meta_fn, validate_value
3
3
  from sonolus.script.internal.value import Value
4
- from sonolus.script.num import Num, is_num
4
+ from sonolus.script.num import Num, _is_num
5
5
 
6
6
 
7
7
  @meta_fn
8
- def deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
8
+ def _deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
9
9
  block = Num._accept_(block)
10
10
  offset = Num._accept_(offset)
11
11
  type_ = validate_value(type_)._as_py_()
@@ -14,7 +14,7 @@ def deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
14
14
  if not isinstance(block, int):
15
15
  raise TypeError("block must be an integer")
16
16
  else:
17
- if not is_num(block):
17
+ if not _is_num(block):
18
18
  raise TypeError("block must be a Num")
19
19
  block = block.index()
20
20
  if offset._is_py_():
@@ -22,7 +22,7 @@ def deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
22
22
  if not isinstance(offset, int):
23
23
  raise TypeError("offset must be an integer")
24
24
  else:
25
- if not is_num(offset):
25
+ if not _is_num(offset):
26
26
  raise TypeError("offset must be a Num")
27
27
  offset = offset.index()
28
28
  if not (isinstance(type_, type) and issubclass(type_, Value)):
sonolus/script/print.py CHANGED
@@ -7,6 +7,8 @@ from sonolus.script.vec import Vec2
7
7
 
8
8
 
9
9
  class PrintFormat(IntEnum):
10
+ """Print format."""
11
+
10
12
  NUMBER = 0
11
13
  PERCENTAGE = 1
12
14
  TIME = 10
@@ -19,6 +21,8 @@ class PrintFormat(IntEnum):
19
21
 
20
22
 
21
23
  class PrintColor(IntEnum):
24
+ """Print color."""
25
+
22
26
  THEME = -1
23
27
  NEUTRAL = 0
24
28
  RED = 1
@@ -45,7 +49,7 @@ def _print(
45
49
  alpha: float,
46
50
  horizontal_align: HorizontalAlign,
47
51
  background: bool,
48
- ):
52
+ ) -> None:
49
53
  raise NotImplementedError
50
54
 
51
55
 
@@ -63,6 +67,23 @@ def print_number(
63
67
  horizontal_align: HorizontalAlign = HorizontalAlign.LEFT,
64
68
  background: bool = False,
65
69
  ):
70
+ """Print a number.
71
+
72
+ Only supported in preview mode.
73
+
74
+ Args:
75
+ value: The value to print.
76
+ fmt: The print format.
77
+ decimal_places: The number of decimal places.
78
+ anchor: The anchor.
79
+ pivot: The pivot.
80
+ dimensions: The dimensions.
81
+ rotation: The rotation.
82
+ color: The color.
83
+ alpha: The alpha.
84
+ horizontal_align: The horizontal alignment.
85
+ background: Whether to show a background.
86
+ """
66
87
  _print(
67
88
  value,
68
89
  fmt,