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/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
|
|
17
|
+
class _NumMeta(type):
|
|
18
18
|
def __instancecheck__(cls, instance):
|
|
19
|
-
return isinstance(instance, float | int | bool) or
|
|
19
|
+
return isinstance(instance, float | int | bool) or _is_num(instance)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
239
|
-
if a._is_py_() and a.
|
|
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.
|
|
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.
|
|
252
|
-
if a._is_py_() and a.
|
|
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.
|
|
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.
|
|
271
|
+
return Num(a._as_py_() * b._as_py_())
|
|
265
272
|
if a._is_py_():
|
|
266
|
-
if a.
|
|
273
|
+
if a._as_py_() == 0:
|
|
267
274
|
return Num(0)
|
|
268
|
-
if a.
|
|
275
|
+
if a._as_py_() == 1:
|
|
269
276
|
return b
|
|
270
277
|
if b._is_py_():
|
|
271
|
-
if b.
|
|
278
|
+
if b._as_py_() == 0:
|
|
272
279
|
return Num(0)
|
|
273
|
-
if b.
|
|
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.
|
|
290
|
+
if b._as_py_() == 0:
|
|
284
291
|
return None
|
|
285
|
-
return Num(a.
|
|
292
|
+
return Num(a._as_py_() / b._as_py_())
|
|
286
293
|
if b._is_py_():
|
|
287
|
-
if b.
|
|
294
|
+
if b._as_py_() == 1:
|
|
288
295
|
return a
|
|
289
|
-
if b.
|
|
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.
|
|
306
|
+
if b._as_py_() == 0:
|
|
300
307
|
return None
|
|
301
|
-
return Num(a.
|
|
308
|
+
return Num(a._as_py_() // b._as_py_())
|
|
302
309
|
if b._is_py_():
|
|
303
|
-
if b.
|
|
310
|
+
if b._as_py_() == 1:
|
|
304
311
|
return a
|
|
305
|
-
if b.
|
|
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.
|
|
322
|
+
if b._as_py_() == 0:
|
|
316
323
|
return None
|
|
317
|
-
return Num(a.
|
|
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.
|
|
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.
|
|
338
|
+
if b._as_py_() == 0:
|
|
332
339
|
return Num(1)
|
|
333
|
-
if b.
|
|
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.
|
|
370
|
+
return Num(a._as_py_() and b._as_py_())
|
|
364
371
|
if a._is_py_():
|
|
365
|
-
if a.
|
|
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.
|
|
383
|
+
return Num(a._as_py_() or b._as_py_())
|
|
377
384
|
if a._is_py_():
|
|
378
|
-
if a.
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
160
|
+
type _OptionInfo = _SliderOption | _ToggleOption | _SelectOption
|
|
129
161
|
|
|
130
162
|
|
|
131
|
-
class
|
|
132
|
-
info:
|
|
163
|
+
class _OptionField(SonolusDescriptor):
|
|
164
|
+
info: _OptionInfo
|
|
133
165
|
index: int
|
|
134
166
|
|
|
135
|
-
def __init__(self, info:
|
|
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,
|
|
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,
|
|
249
|
+
setattr(cls, name, _OptionField(annotation_value, i))
|
|
184
250
|
instance._options_ = entries
|
|
185
251
|
instance._is_comptime_value_ = True
|
|
186
252
|
return instance
|
sonolus/script/particle.py
CHANGED
|
@@ -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
|
|
89
|
+
class _ParticleInfo:
|
|
69
90
|
name: str
|
|
70
91
|
|
|
71
92
|
|
|
72
93
|
def particle(name: str) -> Any:
|
|
73
|
-
return
|
|
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],
|
|
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,
|
|
4
|
+
from sonolus.script.num import Num, _is_num
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@meta_fn
|
|
8
|
-
def
|
|
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
|
|
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
|
|
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,
|