sonolus.py 0.3.2__py3-none-any.whl → 0.3.4__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 +16 -4
- sonolus/backend/visitor.py +25 -9
- sonolus/script/archetype.py +40 -22
- sonolus/script/array.py +8 -5
- sonolus/script/array_like.py +44 -19
- sonolus/script/containers.py +24 -4
- sonolus/script/debug.py +4 -4
- sonolus/script/engine.py +2 -2
- sonolus/script/instruction.py +1 -1
- sonolus/script/internal/builtin_impls.py +10 -5
- sonolus/script/internal/context.py +5 -1
- sonolus/script/internal/math_impls.py +17 -0
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/range.py +30 -7
- sonolus/script/internal/tuple_impl.py +3 -1
- sonolus/script/internal/value.py +1 -1
- sonolus/script/interval.py +61 -3
- sonolus/script/num.py +13 -13
- sonolus/script/pointer.py +1 -1
- sonolus/script/quad.py +39 -3
- sonolus/script/record.py +7 -0
- sonolus/script/runtime.py +28 -0
- sonolus/script/stream.py +25 -16
- sonolus/script/transform.py +4 -4
- sonolus/script/vec.py +19 -7
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.4.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.4.dist-info}/RECORD +30 -30
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.4.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.4.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from typing import overload
|
|
3
3
|
|
|
4
|
+
from sonolus.script.array import Array
|
|
4
5
|
from sonolus.script.array_like import ArrayLike
|
|
5
6
|
from sonolus.script.internal.context import ctx
|
|
6
7
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
@@ -129,6 +130,8 @@ def _max(*args, key: callable = _identity):
|
|
|
129
130
|
(iterable,) = args
|
|
130
131
|
if isinstance(iterable, ArrayLike):
|
|
131
132
|
return compile_and_call(iterable._max_, key=key)
|
|
133
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
134
|
+
return compile_and_call(Array(*iterable.value)._max_, key=key)
|
|
132
135
|
else:
|
|
133
136
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
134
137
|
else:
|
|
@@ -169,6 +172,8 @@ def _min(*args, key: callable = _identity):
|
|
|
169
172
|
(iterable,) = args
|
|
170
173
|
if isinstance(iterable, ArrayLike):
|
|
171
174
|
return compile_and_call(iterable._min_, key=key)
|
|
175
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
176
|
+
return compile_and_call(Array(*iterable.value)._min_, key=key)
|
|
172
177
|
else:
|
|
173
178
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
174
179
|
else:
|
|
@@ -221,12 +226,12 @@ def _float(value=0.0):
|
|
|
221
226
|
return value
|
|
222
227
|
|
|
223
228
|
|
|
224
|
-
@meta_fn
|
|
225
229
|
def _bool(value=False):
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
# Relies on the compiler to perform the conversion in a boolean context
|
|
231
|
+
if value: # noqa: SIM103
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
return False
|
|
230
235
|
|
|
231
236
|
|
|
232
237
|
_int._type_mapping_ = Num
|
|
@@ -97,6 +97,22 @@ def _log(x: float, base: float | None = None) -> float:
|
|
|
97
97
|
return _ln(x) / _ln(base)
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def _sqrt(x: float) -> float:
|
|
101
|
+
return x**0.5
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@native_function(Op.Degree)
|
|
105
|
+
def _degrees(x: float) -> float:
|
|
106
|
+
"""Convert radians to degrees."""
|
|
107
|
+
return math.degrees(x)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@native_function(Op.Radian)
|
|
111
|
+
def _radians(x: float) -> float:
|
|
112
|
+
"""Convert degrees to radians."""
|
|
113
|
+
return math.radians(x)
|
|
114
|
+
|
|
115
|
+
|
|
100
116
|
@native_function(Op.Rem)
|
|
101
117
|
def _remainder(x: float, y: float) -> float:
|
|
102
118
|
# This is different from math.remainder in Python's math package, which could be confusing
|
|
@@ -119,4 +135,5 @@ MATH_BUILTIN_IMPLS = {
|
|
|
119
135
|
id(math.trunc): _trunc,
|
|
120
136
|
id(round): _round,
|
|
121
137
|
id(math.log): _log,
|
|
138
|
+
id(math.sqrt): _sqrt,
|
|
122
139
|
}
|
|
@@ -9,7 +9,7 @@ from sonolus.script.internal.impl import meta_fn, validate_value
|
|
|
9
9
|
from sonolus.script.num import Num, _is_num
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def native_call(op: Op, *args:
|
|
12
|
+
def native_call(op: Op, *args: int | float | bool) -> Num:
|
|
13
13
|
if not ctx():
|
|
14
14
|
raise RuntimeError("Unexpected native call")
|
|
15
15
|
args = tuple(validate_value(arg) for arg in args)
|
|
@@ -21,12 +21,12 @@ def native_call(op: Op, *args: Num) -> Num:
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
24
|
-
def decorator(fn: Callable[P,
|
|
24
|
+
def decorator(fn: Callable[P, int | float | bool]) -> Callable[P, Num]:
|
|
25
25
|
signature = inspect.signature(fn)
|
|
26
26
|
|
|
27
27
|
@functools.wraps(fn)
|
|
28
28
|
@meta_fn
|
|
29
|
-
def wrapper(*args:
|
|
29
|
+
def wrapper(*args: int | float | bool) -> Num:
|
|
30
30
|
if len(args) < sum(1 for p in signature.parameters.values() if p.default == inspect.Parameter.empty):
|
|
31
31
|
raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
|
|
32
32
|
if ctx():
|
sonolus/script/internal/range.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
from sonolus.script.array_like import ArrayLike
|
|
1
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
2
|
+
from sonolus.script.internal.context import ctx
|
|
3
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
2
4
|
from sonolus.script.iterator import SonolusIterator
|
|
3
5
|
from sonolus.script.num import Num
|
|
4
6
|
from sonolus.script.record import Record
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
class Range(Record, ArrayLike[
|
|
9
|
+
class Range(Record, ArrayLike[int]):
|
|
8
10
|
start: int
|
|
9
11
|
stop: int
|
|
10
12
|
step: int
|
|
11
13
|
|
|
12
|
-
def __new__(cls, start:
|
|
14
|
+
def __new__(cls, start: int, stop: int | None = None, step: int = 1):
|
|
13
15
|
if stop is None:
|
|
14
16
|
start, stop = 0, start
|
|
15
17
|
return super().__new__(cls, start, stop, step)
|
|
@@ -35,14 +37,14 @@ class Range(Record, ArrayLike[Num]):
|
|
|
35
37
|
return 0
|
|
36
38
|
return (diff - self.step - 1) // -self.step
|
|
37
39
|
|
|
38
|
-
def __getitem__(self, index:
|
|
39
|
-
return self.start + index * self.step
|
|
40
|
+
def __getitem__(self, index: int) -> int:
|
|
41
|
+
return self.start + get_positive_index(index, len(self)) * self.step
|
|
40
42
|
|
|
41
|
-
def __setitem__(self, index:
|
|
43
|
+
def __setitem__(self, index: int, value: int):
|
|
42
44
|
raise TypeError("Range does not support item assignment")
|
|
43
45
|
|
|
44
46
|
@property
|
|
45
|
-
def last(self) ->
|
|
47
|
+
def last(self) -> int:
|
|
46
48
|
return self[len(self) - 1]
|
|
47
49
|
|
|
48
50
|
def __eq__(self, other):
|
|
@@ -79,3 +81,24 @@ class RangeIterator(Record, SonolusIterator):
|
|
|
79
81
|
|
|
80
82
|
def advance(self):
|
|
81
83
|
self.value += self.step
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@meta_fn
|
|
87
|
+
def range_or_tuple(start: int, stop: int | None = None, step: int = 1) -> Range | tuple[int, ...]:
|
|
88
|
+
if stop is None:
|
|
89
|
+
start, stop = 0, start
|
|
90
|
+
if not ctx():
|
|
91
|
+
return range(start, stop, step) # type: ignore
|
|
92
|
+
start = Num._accept_(start)
|
|
93
|
+
stop = Num._accept_(stop) if stop is not None else None
|
|
94
|
+
step = Num._accept_(step)
|
|
95
|
+
if start._is_py_() and stop._is_py_() and step._is_py_():
|
|
96
|
+
start_int = start._as_py_()
|
|
97
|
+
stop_int = stop._as_py_() if stop is not None else None
|
|
98
|
+
if stop_int is None:
|
|
99
|
+
start_int, stop_int = 0, start_int
|
|
100
|
+
step_int = step._as_py_()
|
|
101
|
+
if start_int % 1 != 0 or stop_int % 1 != 0 or step_int % 1 != 0:
|
|
102
|
+
raise TypeError("Range arguments must be integers")
|
|
103
|
+
return validate_value(tuple(range(int(start_int), int(stop_int), int(step_int)))) # type: ignore
|
|
104
|
+
return Range(start, stop, step)
|
|
@@ -21,8 +21,10 @@ class TupleImpl(TransientValue):
|
|
|
21
21
|
raise TypeError(f"Cannot index tuple with {item}")
|
|
22
22
|
if int(item) != item:
|
|
23
23
|
raise TypeError(f"Cannot index tuple with non-integer {item}")
|
|
24
|
-
if not (
|
|
24
|
+
if not (-len(self.value) <= item < len(self.value)):
|
|
25
25
|
raise IndexError(f"Tuple index out of range: {item}")
|
|
26
|
+
if item < 0:
|
|
27
|
+
item += len(self.value)
|
|
26
28
|
return self.value[int(item)]
|
|
27
29
|
|
|
28
30
|
@meta_fn
|
sonolus/script/internal/value.py
CHANGED
sonolus/script/interval.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
|
+
from sonolus.script.array_like import ArrayLike
|
|
4
5
|
from sonolus.script.debug import static_error
|
|
5
6
|
from sonolus.script.internal.native import native_function
|
|
7
|
+
from sonolus.script.internal.range import range_or_tuple
|
|
6
8
|
from sonolus.script.num import Num
|
|
7
9
|
from sonolus.script.record import Record
|
|
8
10
|
|
|
@@ -34,7 +36,7 @@ class Interval(Record):
|
|
|
34
36
|
|
|
35
37
|
@property
|
|
36
38
|
def is_empty(self) -> bool:
|
|
37
|
-
"""Whether the has a start greater than its end."""
|
|
39
|
+
"""Whether the interval has a start greater than its end."""
|
|
38
40
|
return self.start > self.end
|
|
39
41
|
|
|
40
42
|
@property
|
|
@@ -296,7 +298,7 @@ def unlerp_clamped(a: float, b: float, x: float, /) -> float:
|
|
|
296
298
|
|
|
297
299
|
@native_function(Op.Remap)
|
|
298
300
|
def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
299
|
-
"""
|
|
301
|
+
"""Linearly remap a value from one interval to another.
|
|
300
302
|
|
|
301
303
|
Args:
|
|
302
304
|
a: The start of the input interval.
|
|
@@ -313,7 +315,7 @@ def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
|
313
315
|
|
|
314
316
|
@native_function(Op.RemapClamped)
|
|
315
317
|
def remap_clamped(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
316
|
-
"""
|
|
318
|
+
"""Linearly remap a value from one interval to another, clamped to the output interval.
|
|
317
319
|
|
|
318
320
|
Args:
|
|
319
321
|
a: The start of the input interval.
|
|
@@ -341,3 +343,59 @@ def clamp(x: float, a: float, b: float, /) -> float:
|
|
|
341
343
|
The clamped value.
|
|
342
344
|
"""
|
|
343
345
|
return max(a, min(b, x))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def interp(
|
|
349
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
350
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
351
|
+
x: float,
|
|
352
|
+
) -> float:
|
|
353
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
354
|
+
|
|
355
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
356
|
+
For values of x outside the range of xp, the slope of the first or last segment is used to extrapolate.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
xp: The x-coordinates of the points in increasing order.
|
|
360
|
+
fp: The y-coordinates of the points.
|
|
361
|
+
x: The x-coordinate to interpolate.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The interpolated value.
|
|
365
|
+
"""
|
|
366
|
+
assert len(xp) == len(fp)
|
|
367
|
+
assert len(xp) >= 2
|
|
368
|
+
for i in range_or_tuple(1, len(xp) - 1):
|
|
369
|
+
# At i == 1, x may be less than x[0], but since we're extrapolating, we use the first segment regardless.
|
|
370
|
+
if x <= xp[i]:
|
|
371
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
372
|
+
# x > xp[-2] so we can just use the last segment regardless of whether x is in it or to the right of it.
|
|
373
|
+
return remap(xp[-2], xp[-1], fp[-2], fp[-1], x)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def interp_clamped(
|
|
377
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
378
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
379
|
+
x: float,
|
|
380
|
+
) -> float:
|
|
381
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
382
|
+
|
|
383
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
384
|
+
For x-coordinates outside the range of the sequence, the respective endpoint of fp is returned.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
xp: The x-coordinates of the points in increasing order.
|
|
388
|
+
fp: The y-coordinates of the points.
|
|
389
|
+
x: The x-coordinate to interpolate.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
The interpolated value.
|
|
393
|
+
"""
|
|
394
|
+
assert len(xp) == len(fp)
|
|
395
|
+
assert len(xp) >= 2
|
|
396
|
+
if x <= xp[0]:
|
|
397
|
+
return fp[0]
|
|
398
|
+
for i in range_or_tuple(1, len(xp)):
|
|
399
|
+
if x <= xp[i]:
|
|
400
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
401
|
+
return fp[-1]
|
sonolus/script/num.py
CHANGED
|
@@ -439,17 +439,17 @@ if TYPE_CHECKING:
|
|
|
439
439
|
|
|
440
440
|
@runtime_checkable
|
|
441
441
|
class Num[T](Protocol, int, bool, float):
|
|
442
|
-
def __add__(self, other: T, /) -> Num: ...
|
|
443
|
-
def __sub__(self, other: T, /) -> Num: ...
|
|
444
|
-
def __mul__(self, other: T, /) -> Num: ...
|
|
445
|
-
def __truediv__(self, other: T, /) -> Num: ...
|
|
446
|
-
def __floordiv__(self, other: T, /) -> Num: ...
|
|
447
|
-
def __mod__(self, other: T, /) -> Num: ...
|
|
448
|
-
def __pow__(self, other: T, /) -> Num: ...
|
|
449
|
-
|
|
450
|
-
def __neg__(self, /) -> Num: ...
|
|
451
|
-
def __pos__(self, /) -> Num: ...
|
|
452
|
-
def __abs__(self, /) -> Num: ...
|
|
442
|
+
def __add__(self, other: T, /) -> Num | int | bool | float: ...
|
|
443
|
+
def __sub__(self, other: T, /) -> Num | int | bool | float: ...
|
|
444
|
+
def __mul__(self, other: T, /) -> Num | int | bool | float: ...
|
|
445
|
+
def __truediv__(self, other: T, /) -> Num | int | bool | float: ...
|
|
446
|
+
def __floordiv__(self, other: T, /) -> Num | int | bool | float: ...
|
|
447
|
+
def __mod__(self, other: T, /) -> Num | int | bool | float: ...
|
|
448
|
+
def __pow__(self, other: T, /) -> Num | int | bool | float: ...
|
|
449
|
+
|
|
450
|
+
def __neg__(self, /) -> Num | int | bool | float: ...
|
|
451
|
+
def __pos__(self, /) -> Num | int | bool | float: ...
|
|
452
|
+
def __abs__(self, /) -> Num | int | bool | float: ...
|
|
453
453
|
|
|
454
454
|
def __eq__(self, other: Any, /) -> bool: ...
|
|
455
455
|
def __ne__(self, other: Any, /) -> bool: ...
|
|
@@ -461,10 +461,10 @@ if TYPE_CHECKING:
|
|
|
461
461
|
def __hash__(self, /) -> int: ...
|
|
462
462
|
|
|
463
463
|
@property
|
|
464
|
-
def real(self) -> Num: ...
|
|
464
|
+
def real(self) -> Num | int | bool | float: ...
|
|
465
465
|
|
|
466
466
|
@property
|
|
467
|
-
def imag(self) -> Num: ...
|
|
467
|
+
def imag(self) -> Num | int | bool | float: ...
|
|
468
468
|
else:
|
|
469
469
|
# Need to do this to satisfy type checkers (especially Pycharm)
|
|
470
470
|
_Num.__name__ = "Num"
|
sonolus/script/pointer.py
CHANGED
|
@@ -6,7 +6,7 @@ from sonolus.script.num import Num, _is_num
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@meta_fn
|
|
9
|
-
def _deref[T: Value](block:
|
|
9
|
+
def _deref[T: Value](block: int, offset: int, type_: type[T]) -> T:
|
|
10
10
|
block = Num._accept_(block)
|
|
11
11
|
offset = Num._accept_(offset)
|
|
12
12
|
type_ = validate_value(type_)._as_py_()
|
sonolus/script/quad.py
CHANGED
|
@@ -28,6 +28,20 @@ class Quad(Record):
|
|
|
28
28
|
br: Vec2
|
|
29
29
|
"""The bottom-right corner of the quad."""
|
|
30
30
|
|
|
31
|
+
@classmethod
|
|
32
|
+
def zero(cls) -> Quad:
|
|
33
|
+
"""Return a quad with all corners set to (0, 0).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A new quad with all corners at the origin.
|
|
37
|
+
"""
|
|
38
|
+
return cls(
|
|
39
|
+
bl=Vec2.zero(),
|
|
40
|
+
tl=Vec2.zero(),
|
|
41
|
+
tr=Vec2.zero(),
|
|
42
|
+
br=Vec2.zero(),
|
|
43
|
+
)
|
|
44
|
+
|
|
31
45
|
@classmethod
|
|
32
46
|
def from_quad(cls, value: QuadLike, /) -> Quad:
|
|
33
47
|
"""Create a quad from a quad-like value."""
|
|
@@ -80,7 +94,14 @@ class Quad(Record):
|
|
|
80
94
|
).translate(self.center * (Vec2(1, 1) - factor))
|
|
81
95
|
|
|
82
96
|
def rotate(self, angle: float, /) -> Self:
|
|
83
|
-
"""Rotate the quad by the given angle about the origin and return a new quad.
|
|
97
|
+
"""Rotate the quad by the given angle about the origin and return a new quad.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A new quad rotated by the given angle.
|
|
104
|
+
"""
|
|
84
105
|
return Quad(
|
|
85
106
|
bl=self.bl.rotate(angle),
|
|
86
107
|
tl=self.tl.rotate(angle),
|
|
@@ -94,7 +115,15 @@ class Quad(Record):
|
|
|
94
115
|
/,
|
|
95
116
|
pivot: Vec2,
|
|
96
117
|
) -> Self:
|
|
97
|
-
"""Rotate the quad by the given angle about the given pivot and return a new quad.
|
|
118
|
+
"""Rotate the quad by the given angle about the given pivot and return a new quad.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
122
|
+
pivot: The pivot point for rotation.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
A new quad rotated about the pivot by the given angle.
|
|
126
|
+
"""
|
|
98
127
|
return Quad(
|
|
99
128
|
bl=self.bl.rotate_about(angle, pivot),
|
|
100
129
|
tl=self.tl.rotate_about(angle, pivot),
|
|
@@ -103,7 +132,14 @@ class Quad(Record):
|
|
|
103
132
|
)
|
|
104
133
|
|
|
105
134
|
def rotate_centered(self, angle: float, /) -> Self:
|
|
106
|
-
"""Rotate the quad by the given angle about its center and return a new quad.
|
|
135
|
+
"""Rotate the quad by the given angle about its center and return a new quad.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A new quad rotated about its center by the given angle.
|
|
142
|
+
"""
|
|
107
143
|
return self.rotate_about(angle, self.center)
|
|
108
144
|
|
|
109
145
|
def permute(self, count: int = 1, /) -> Self:
|
sonolus/script/record.py
CHANGED
|
@@ -23,6 +23,7 @@ from sonolus.script.num import Num
|
|
|
23
23
|
class RecordMeta(type):
|
|
24
24
|
@meta_fn
|
|
25
25
|
def __pos__[T](cls: type[T]) -> T:
|
|
26
|
+
"""Create a zero-initialized record instance."""
|
|
26
27
|
return cls._zero_()
|
|
27
28
|
|
|
28
29
|
|
|
@@ -53,6 +54,11 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
53
54
|
record_4 = +MyRecord # Create a zero-initialized record
|
|
54
55
|
record_5 = +MyGenericRecord[int, int]
|
|
55
56
|
```
|
|
57
|
+
|
|
58
|
+
Copying a record:
|
|
59
|
+
```python
|
|
60
|
+
record_copy = +record
|
|
61
|
+
```
|
|
56
62
|
"""
|
|
57
63
|
|
|
58
64
|
_value: dict[str, Value]
|
|
@@ -283,6 +289,7 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
283
289
|
def __hash__(self):
|
|
284
290
|
return hash(tuple(field.__get__(self) for field in self._fields))
|
|
285
291
|
|
|
292
|
+
@meta_fn
|
|
286
293
|
def __pos__(self) -> Self:
|
|
287
294
|
"""Return a copy of the record."""
|
|
288
295
|
return self._copy_()
|
sonolus/script/runtime.py
CHANGED
|
@@ -988,6 +988,25 @@ def time() -> float:
|
|
|
988
988
|
return 0
|
|
989
989
|
|
|
990
990
|
|
|
991
|
+
@meta_fn
|
|
992
|
+
def offset_adjusted_time() -> float:
|
|
993
|
+
"""Get the current time of the game adjusted by the input offset.
|
|
994
|
+
|
|
995
|
+
Returns 0 in preview mode and tutorial mode.
|
|
996
|
+
"""
|
|
997
|
+
if not ctx():
|
|
998
|
+
return 0
|
|
999
|
+
match ctx().global_state.mode:
|
|
1000
|
+
case Mode.PLAY:
|
|
1001
|
+
return _PlayRuntimeUpdate.time - _PlayRuntimeEnvironment.input_offset
|
|
1002
|
+
case Mode.WATCH:
|
|
1003
|
+
return _WatchRuntimeUpdate.time - _WatchRuntimeEnvironment.input_offset
|
|
1004
|
+
case Mode.TUTORIAL:
|
|
1005
|
+
return _TutorialRuntimeUpdate.time
|
|
1006
|
+
case _:
|
|
1007
|
+
return 0
|
|
1008
|
+
|
|
1009
|
+
|
|
991
1010
|
@meta_fn
|
|
992
1011
|
def delta_time() -> float:
|
|
993
1012
|
"""Get the time elapsed since the last frame.
|
|
@@ -1026,6 +1045,15 @@ def scaled_time() -> float:
|
|
|
1026
1045
|
return 0
|
|
1027
1046
|
|
|
1028
1047
|
|
|
1048
|
+
@meta_fn
|
|
1049
|
+
def prev_time() -> float:
|
|
1050
|
+
"""Get the time of the previous frame.
|
|
1051
|
+
|
|
1052
|
+
Returns 0 in preview mode.
|
|
1053
|
+
"""
|
|
1054
|
+
return time() - delta_time()
|
|
1055
|
+
|
|
1056
|
+
|
|
1029
1057
|
@meta_fn
|
|
1030
1058
|
def touches() -> ArrayLike[Touch]:
|
|
1031
1059
|
"""Get the current touches of the game."""
|
sonolus/script/stream.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from math import inf
|
|
3
4
|
from typing import cast, dataclass_transform
|
|
4
5
|
|
|
5
6
|
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr
|
|
@@ -14,7 +15,7 @@ from sonolus.script.internal.value import BackingValue, Value
|
|
|
14
15
|
from sonolus.script.iterator import SonolusIterator
|
|
15
16
|
from sonolus.script.num import Num
|
|
16
17
|
from sonolus.script.record import Record
|
|
17
|
-
from sonolus.script.runtime import
|
|
18
|
+
from sonolus.script.runtime import prev_time, time
|
|
18
19
|
from sonolus.script.values import sizeof
|
|
19
20
|
|
|
20
21
|
|
|
@@ -79,12 +80,12 @@ def streams[T](cls: type[T]) -> T:
|
|
|
79
80
|
```python
|
|
80
81
|
@streams
|
|
81
82
|
class Streams:
|
|
82
|
-
stream_1: Stream[
|
|
83
|
+
stream_1: Stream[int] # A stream of int values
|
|
83
84
|
stream_2: Stream[Vec2] # A stream of Vec2 values
|
|
84
|
-
group_1: StreamGroup[
|
|
85
|
+
group_1: StreamGroup[int, 10] # A group of 10 int streams
|
|
85
86
|
group_2: StreamGroup[Vec2, 5] # A group of 5 Vec2 streams
|
|
86
87
|
|
|
87
|
-
data_field_1:
|
|
88
|
+
data_field_1: int # A data field of type int
|
|
88
89
|
data_field_2: Vec2 # A data field of type Vec2
|
|
89
90
|
```
|
|
90
91
|
"""
|
|
@@ -316,6 +317,14 @@ class Stream[T](Record):
|
|
|
316
317
|
_check_can_read_stream()
|
|
317
318
|
return self[self.next_key_inclusive(key)]
|
|
318
319
|
|
|
320
|
+
def get_previous_inclusive(self, key: int | float) -> T:
|
|
321
|
+
"""Get the value corresponding to the previous key, or the value at the given key if it is in the stream.
|
|
322
|
+
|
|
323
|
+
Equivalent to `self[self.previous_key_inclusive(key)]`.
|
|
324
|
+
"""
|
|
325
|
+
_check_can_read_stream()
|
|
326
|
+
return self[self.previous_key_inclusive(key)]
|
|
327
|
+
|
|
319
328
|
def iter_items_from(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
320
329
|
"""Iterate over the items in the stream in ascending order starting from the given key.
|
|
321
330
|
|
|
@@ -345,7 +354,7 @@ class Stream[T](Record):
|
|
|
345
354
|
```
|
|
346
355
|
"""
|
|
347
356
|
_check_can_read_stream()
|
|
348
|
-
return _StreamBoundedAscIterator(self, self.next_key(
|
|
357
|
+
return _StreamBoundedAscIterator(self, self.next_key(prev_time()), time())
|
|
349
358
|
|
|
350
359
|
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
351
360
|
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
@@ -391,7 +400,7 @@ class Stream[T](Record):
|
|
|
391
400
|
```
|
|
392
401
|
"""
|
|
393
402
|
_check_can_read_stream()
|
|
394
|
-
return _StreamBoundedAscKeyIterator(self, self.next_key(
|
|
403
|
+
return _StreamBoundedAscKeyIterator(self, self.next_key(prev_time()), time())
|
|
395
404
|
|
|
396
405
|
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
397
406
|
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
@@ -437,7 +446,7 @@ class Stream[T](Record):
|
|
|
437
446
|
```
|
|
438
447
|
"""
|
|
439
448
|
_check_can_read_stream()
|
|
440
|
-
return _StreamBoundedAscValueIterator(self, self.next_key(
|
|
449
|
+
return _StreamBoundedAscValueIterator(self, self.next_key(prev_time()), time())
|
|
441
450
|
|
|
442
451
|
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
443
452
|
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
@@ -513,7 +522,7 @@ class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
513
522
|
return self.current_key, self.stream[self.current_key]
|
|
514
523
|
|
|
515
524
|
def advance(self):
|
|
516
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
525
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
517
526
|
|
|
518
527
|
|
|
519
528
|
class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -528,7 +537,7 @@ class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]
|
|
|
528
537
|
return self.current_key, self.stream[self.current_key]
|
|
529
538
|
|
|
530
539
|
def advance(self):
|
|
531
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
540
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
532
541
|
|
|
533
542
|
|
|
534
543
|
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -542,7 +551,7 @@ class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
542
551
|
return self.current_key, self.stream[self.current_key]
|
|
543
552
|
|
|
544
553
|
def advance(self):
|
|
545
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
554
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
546
555
|
|
|
547
556
|
|
|
548
557
|
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -556,7 +565,7 @@ class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
556
565
|
return self.current_key
|
|
557
566
|
|
|
558
567
|
def advance(self):
|
|
559
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
568
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
560
569
|
|
|
561
570
|
|
|
562
571
|
class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -571,7 +580,7 @@ class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
571
580
|
return self.current_key
|
|
572
581
|
|
|
573
582
|
def advance(self):
|
|
574
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
583
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
575
584
|
|
|
576
585
|
|
|
577
586
|
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -585,7 +594,7 @@ class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
585
594
|
return self.current_key
|
|
586
595
|
|
|
587
596
|
def advance(self):
|
|
588
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
597
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
589
598
|
|
|
590
599
|
|
|
591
600
|
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -599,7 +608,7 @@ class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
599
608
|
return self.stream[self.current_key]
|
|
600
609
|
|
|
601
610
|
def advance(self):
|
|
602
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
611
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
603
612
|
|
|
604
613
|
|
|
605
614
|
class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -614,7 +623,7 @@ class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
614
623
|
return self.stream[self.current_key]
|
|
615
624
|
|
|
616
625
|
def advance(self):
|
|
617
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
626
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
618
627
|
|
|
619
628
|
|
|
620
629
|
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -628,7 +637,7 @@ class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
|
628
637
|
return self.stream[self.current_key]
|
|
629
638
|
|
|
630
639
|
def advance(self):
|
|
631
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
640
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
632
641
|
|
|
633
642
|
|
|
634
643
|
@native_function(Op.StreamGetNextKey)
|
sonolus/script/transform.py
CHANGED
|
@@ -137,7 +137,7 @@ class Transform2d(Record):
|
|
|
137
137
|
"""Rotate about the origin and return a new transform.
|
|
138
138
|
|
|
139
139
|
Args:
|
|
140
|
-
angle: The angle of rotation in radians.
|
|
140
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
141
141
|
|
|
142
142
|
Returns:
|
|
143
143
|
A new transform after rotation.
|
|
@@ -160,7 +160,7 @@ class Transform2d(Record):
|
|
|
160
160
|
"""Rotate about the pivot and return a new transform.
|
|
161
161
|
|
|
162
162
|
Args:
|
|
163
|
-
angle: The angle of rotation in radians.
|
|
163
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
164
164
|
pivot: The pivot point for rotation.
|
|
165
165
|
|
|
166
166
|
Returns:
|
|
@@ -470,7 +470,7 @@ class InvertibleTransform2d(Record):
|
|
|
470
470
|
"""Rotate about the origin and return a new transform.
|
|
471
471
|
|
|
472
472
|
Args:
|
|
473
|
-
angle: The angle of rotation in radians.
|
|
473
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
474
474
|
|
|
475
475
|
Returns:
|
|
476
476
|
A new invertible transform after rotation.
|
|
@@ -484,7 +484,7 @@ class InvertibleTransform2d(Record):
|
|
|
484
484
|
"""Rotate about the pivot and return a new transform.
|
|
485
485
|
|
|
486
486
|
Args:
|
|
487
|
-
angle: The angle of rotation in radians.
|
|
487
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
488
488
|
pivot: The pivot point for rotation.
|
|
489
489
|
|
|
490
490
|
Returns:
|