sonolus.py 0.2.1__py3-none-any.whl → 0.3.1__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/ops.py +6 -0
- sonolus/script/archetype.py +1 -1
- sonolus/script/array.py +56 -27
- sonolus/script/array_like.py +1 -0
- sonolus/script/bucket.py +5 -3
- sonolus/script/internal/constant.py +3 -1
- sonolus/script/internal/generic.py +7 -0
- sonolus/script/internal/impl.py +10 -9
- sonolus/script/internal/value.py +13 -0
- sonolus/script/num.py +7 -4
- sonolus/script/particle.py +1 -0
- sonolus/script/quad.py +3 -3
- sonolus/script/record.py +13 -2
- sonolus/script/stream.py +553 -0
- sonolus/script/transform.py +3 -3
- sonolus/script/values.py +11 -0
- {sonolus_py-0.2.1.dist-info → sonolus_py-0.3.1.dist-info}/METADATA +1 -1
- {sonolus_py-0.2.1.dist-info → sonolus_py-0.3.1.dist-info}/RECORD +21 -20
- {sonolus_py-0.2.1.dist-info → sonolus_py-0.3.1.dist-info}/WHEEL +0 -0
- {sonolus_py-0.2.1.dist-info → sonolus_py-0.3.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.2.1.dist-info → sonolus_py-0.3.1.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/ops.py
CHANGED
|
@@ -180,6 +180,12 @@ class Op(StrEnum):
|
|
|
180
180
|
StackSetPointer = ("StackSetPointer", True, False, False)
|
|
181
181
|
StopLooped = ("StopLooped", True, False, False)
|
|
182
182
|
StopLoopedScheduled = ("StopLoopedScheduled", True, False, False)
|
|
183
|
+
# Streams are immutable when they're readable, so we can treat read operations as pure.
|
|
184
|
+
StreamGetNextKey = ("StreamGetNextKey", False, True, False)
|
|
185
|
+
StreamGetPreviousKey = ("StreamGetPreviousKey", False, True, False)
|
|
186
|
+
StreamGetValue = ("StreamGetValue", False, True, False)
|
|
187
|
+
StreamHas = ("StreamHas", False, True, False)
|
|
188
|
+
StreamSet = ("StreamSet", True, False, False)
|
|
183
189
|
Subtract = ("Subtract", False, True, False)
|
|
184
190
|
Switch = ("Switch", False, True, True)
|
|
185
191
|
SwitchInteger = ("SwitchInteger", False, True, True)
|
sonolus/script/archetype.py
CHANGED
|
@@ -679,7 +679,7 @@ class PlayArchetype(_BaseArchetype):
|
|
|
679
679
|
def should_spawn(self) -> bool:
|
|
680
680
|
"""Return whether the entity should be spawned.
|
|
681
681
|
|
|
682
|
-
Runs
|
|
682
|
+
Runs each frame while the entity is the first entity in the spawn queue.
|
|
683
683
|
"""
|
|
684
684
|
|
|
685
685
|
def initialize(self):
|
sonolus/script/array.py
CHANGED
|
@@ -12,7 +12,7 @@ from sonolus.script.internal.context import ctx
|
|
|
12
12
|
from sonolus.script.internal.error import InternalError
|
|
13
13
|
from sonolus.script.internal.generic import GenericValue
|
|
14
14
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
15
|
-
from sonolus.script.internal.value import DataValue, Value
|
|
15
|
+
from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
16
16
|
from sonolus.script.num import Num
|
|
17
17
|
|
|
18
18
|
|
|
@@ -27,16 +27,14 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
27
27
|
```
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
_value: list[T] | BlockPlace
|
|
30
|
+
_value: list[T] | BlockPlace | BackingSource
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
33
|
-
@meta_fn
|
|
34
33
|
def element_type(cls) -> type[T] | type[Value]:
|
|
35
34
|
"""Return the type of elements in this array type."""
|
|
36
35
|
return cls.type_var_value(T)
|
|
37
36
|
|
|
38
37
|
@classmethod
|
|
39
|
-
@meta_fn
|
|
40
38
|
def size(cls) -> int:
|
|
41
39
|
"""Return the size of this array type.
|
|
42
40
|
|
|
@@ -85,6 +83,10 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
85
83
|
def _is_value_type_(cls) -> bool:
|
|
86
84
|
return False
|
|
87
85
|
|
|
86
|
+
@classmethod
|
|
87
|
+
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
88
|
+
return cls._with_value(source)
|
|
89
|
+
|
|
88
90
|
@classmethod
|
|
89
91
|
def _from_place_(cls, place: BlockPlace) -> Self:
|
|
90
92
|
return cls._with_value(place)
|
|
@@ -124,6 +126,8 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
124
126
|
._from_place_(self._value.add_offset(i * self.element_type()._size_()))
|
|
125
127
|
._to_list_()
|
|
126
128
|
]
|
|
129
|
+
case backing_source if callable(backing_source):
|
|
130
|
+
return [backing_source(IRConst(i)) for i in range(self.size() * self.element_type()._size_())]
|
|
127
131
|
case _:
|
|
128
132
|
assert_unreachable()
|
|
129
133
|
|
|
@@ -135,7 +139,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
135
139
|
return self
|
|
136
140
|
|
|
137
141
|
def _set_(self, value: Self):
|
|
138
|
-
raise TypeError("Array does not support
|
|
142
|
+
raise TypeError("Array does not support _set_")
|
|
139
143
|
|
|
140
144
|
def _copy_from_(self, value: Self):
|
|
141
145
|
if not isinstance(value, type(self)):
|
|
@@ -150,6 +154,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
150
154
|
result._copy_from_(self)
|
|
151
155
|
return result
|
|
152
156
|
else:
|
|
157
|
+
assert isinstance(self._value, list)
|
|
153
158
|
return self._with_value([value._copy_() for value in self._value])
|
|
154
159
|
|
|
155
160
|
@classmethod
|
|
@@ -186,22 +191,36 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
186
191
|
return self._value[const_index]._get_()
|
|
187
192
|
else:
|
|
188
193
|
return self._value[const_index]._get_()._as_py_()
|
|
189
|
-
|
|
194
|
+
elif isinstance(self._value, BlockPlace):
|
|
190
195
|
return (
|
|
191
196
|
self.element_type()
|
|
192
197
|
._from_place_(self._value.add_offset(const_index * self.element_type()._size_()))
|
|
193
198
|
._get_()
|
|
194
199
|
)
|
|
200
|
+
elif callable(self._value):
|
|
201
|
+
return self.element_type()._from_backing_source_(
|
|
202
|
+
lambda offset: self._value((Num(offset) + Num(const_index * self.element_type()._size_())).ir())
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
raise InternalError("Unexpected array value")
|
|
195
206
|
else:
|
|
196
207
|
if not ctx():
|
|
197
208
|
raise InternalError("Unexpected non-constant index")
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
if isinstance(self._value, list | BlockPlace):
|
|
210
|
+
base = ctx().rom[tuple(self._to_list_())] if isinstance(self._value, list) else self._value
|
|
211
|
+
place = BlockPlace(
|
|
212
|
+
block=base.block,
|
|
213
|
+
index=(Num(base.index) + index * self.element_type()._size_()).index(),
|
|
214
|
+
offset=base.offset,
|
|
215
|
+
)
|
|
216
|
+
return self.element_type()._from_place_(place)._get_()
|
|
217
|
+
elif callable(self._value):
|
|
218
|
+
base_offset = index * Num(self.element_type()._size_())
|
|
219
|
+
return self.element_type()._from_backing_source_(
|
|
220
|
+
lambda offset: self._value((Num(offset) + base_offset).ir())
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
raise InternalError("Unexpected array value")
|
|
205
224
|
|
|
206
225
|
@meta_fn
|
|
207
226
|
def __setitem__(self, index: Num, value: T):
|
|
@@ -210,17 +229,25 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
210
229
|
if ctx():
|
|
211
230
|
if isinstance(self._value, list):
|
|
212
231
|
raise ValueError("Cannot mutate a compile time constant array")
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
elif isinstance(self._value, BlockPlace):
|
|
233
|
+
base = self._value
|
|
234
|
+
place = (
|
|
235
|
+
base.add_offset(int(index._as_py_()) * self.element_type()._size_())
|
|
236
|
+
if index._is_py_()
|
|
237
|
+
else BlockPlace(
|
|
238
|
+
block=base.block,
|
|
239
|
+
index=(Num(base.index) + index * self.element_type()._size_()).index(),
|
|
240
|
+
offset=base.offset,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
dst = self.element_type()._from_place_(place)
|
|
244
|
+
elif callable(self._value):
|
|
245
|
+
base_offset = index * Num(self.element_type()._size_())
|
|
246
|
+
dst = self.element_type()._from_backing_source_(
|
|
247
|
+
lambda offset: self._value((Num(offset) + base_offset).ir())
|
|
221
248
|
)
|
|
222
|
-
|
|
223
|
-
|
|
249
|
+
else:
|
|
250
|
+
raise InternalError("Unexpected array value")
|
|
224
251
|
if self.element_type()._is_value_type_():
|
|
225
252
|
dst._set_(value)
|
|
226
253
|
else:
|
|
@@ -259,11 +286,13 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
259
286
|
return hash(tuple(self[i] for i in range(self.size())))
|
|
260
287
|
|
|
261
288
|
def __str__(self):
|
|
262
|
-
if isinstance(self._value, BlockPlace):
|
|
289
|
+
if isinstance(self._value, BlockPlace) or callable(self._value):
|
|
263
290
|
return f"{type(self).__name__}({self._value}...)"
|
|
264
|
-
|
|
291
|
+
else:
|
|
292
|
+
return f"{type(self).__name__}({", ".join(str(self[i]) for i in range(self.size()))})"
|
|
265
293
|
|
|
266
294
|
def __repr__(self):
|
|
267
|
-
if isinstance(self._value, BlockPlace):
|
|
295
|
+
if isinstance(self._value, BlockPlace) or callable(self._value):
|
|
268
296
|
return f"{type(self).__name__}({self._value}...)"
|
|
269
|
-
|
|
297
|
+
else:
|
|
298
|
+
return f"{type(self).__name__}({", ".join(repr(self[i]) for i in range(self.size()))})"
|
sonolus/script/array_like.py
CHANGED
|
@@ -188,6 +188,7 @@ class ArrayLike[T](Sequence, ABC):
|
|
|
188
188
|
if len(self) < 15 or key is not None:
|
|
189
189
|
if key is None:
|
|
190
190
|
key = _identity
|
|
191
|
+
# May be worth adding a block sort variant for better performance on large arrays in the future
|
|
191
192
|
_insertion_sort(self, 0, len(self), key, reverse)
|
|
192
193
|
else:
|
|
193
194
|
# Heap sort is unstable, so if there's a key, we can't rely on it
|
sonolus/script/bucket.py
CHANGED
|
@@ -20,7 +20,7 @@ class JudgmentWindow(Record):
|
|
|
20
20
|
"""The window for judging the accuracy of a hit.
|
|
21
21
|
|
|
22
22
|
Usage:
|
|
23
|
-
```
|
|
23
|
+
```python
|
|
24
24
|
JudgmentWindow(perfect: Interval, great: Interval, good: Interval)
|
|
25
25
|
```
|
|
26
26
|
"""
|
|
@@ -62,7 +62,7 @@ class JudgmentWindow(Record):
|
|
|
62
62
|
target: The target time of the hit.
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
|
-
The
|
|
65
|
+
The [`Judgment`][sonolus.script.bucket.Judgment] of the hit.
|
|
66
66
|
"""
|
|
67
67
|
return _judge(
|
|
68
68
|
actual,
|
|
@@ -133,7 +133,9 @@ class Bucket(Record):
|
|
|
133
133
|
"""A bucket for entity judgment results.
|
|
134
134
|
|
|
135
135
|
Usage:
|
|
136
|
-
```
|
|
136
|
+
```python
|
|
137
|
+
Bucket(id: int)
|
|
138
|
+
```
|
|
137
139
|
"""
|
|
138
140
|
|
|
139
141
|
id: int
|
|
@@ -18,6 +18,8 @@ _MISSING = _Missing()
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ConstantValue(Value):
|
|
21
|
+
"""Wraps a python constant value usable in Sonolus scripts."""
|
|
22
|
+
|
|
21
23
|
_parameterized_: ClassVar[dict[Any, type[Self]]] = {}
|
|
22
24
|
_value: ClassVar[Any] = _MISSING
|
|
23
25
|
instance: ClassVar[Self | _Missing] = _MISSING
|
|
@@ -139,5 +141,5 @@ class ConstantValue(Value):
|
|
|
139
141
|
return hash(self.value())
|
|
140
142
|
|
|
141
143
|
|
|
142
|
-
class
|
|
144
|
+
class BasicConstantValue(ConstantValue):
|
|
143
145
|
"""For constants without any special behavior."""
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import typing
|
|
3
4
|
from enum import Enum
|
|
5
|
+
from types import UnionType
|
|
4
6
|
from typing import Annotated, Any, ClassVar, Literal, Self, TypeVar, get_origin
|
|
5
7
|
|
|
6
8
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
@@ -31,6 +33,11 @@ def validate_type_spec(spec: Any) -> PartialGeneric | TypeVar | type[Value]:
|
|
|
31
33
|
spec = validate_type_spec(spec.__mro__[1])
|
|
32
34
|
if isinstance(spec, PartialGeneric | TypeVar) or (isinstance(spec, type) and issubclass(spec, Value)):
|
|
33
35
|
return spec
|
|
36
|
+
if typing.get_origin(spec) is UnionType:
|
|
37
|
+
args = typing.get_args(spec)
|
|
38
|
+
validated_args = {validate_type_arg(arg) for arg in args}
|
|
39
|
+
if len(validated_args) == 1:
|
|
40
|
+
return validated_args.pop()
|
|
34
41
|
raise TypeError(f"Invalid type spec: {spec}")
|
|
35
42
|
|
|
36
43
|
|
sonolus/script/internal/impl.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from types import FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
5
|
+
from types import EllipsisType, FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
6
6
|
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_origin, overload
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
@@ -42,7 +42,7 @@ def validate_value(value: Any) -> Value:
|
|
|
42
42
|
|
|
43
43
|
def try_validate_value(value: Any) -> Value | None:
|
|
44
44
|
from sonolus.script.globals import _GlobalPlaceholder
|
|
45
|
-
from sonolus.script.internal.constant import
|
|
45
|
+
from sonolus.script.internal.constant import BasicConstantValue
|
|
46
46
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
47
47
|
from sonolus.script.internal.generic import PartialGeneric
|
|
48
48
|
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
@@ -65,8 +65,8 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
65
65
|
return value
|
|
66
66
|
case type():
|
|
67
67
|
if value in {int, float, bool}:
|
|
68
|
-
return
|
|
69
|
-
return
|
|
68
|
+
return BasicConstantValue.of(Num)
|
|
69
|
+
return BasicConstantValue.of(value)
|
|
70
70
|
case int() | float() | bool():
|
|
71
71
|
return Num._accept_(value)
|
|
72
72
|
case tuple():
|
|
@@ -78,17 +78,18 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
78
78
|
| TypeVar()
|
|
79
79
|
| FunctionType()
|
|
80
80
|
| MethodType()
|
|
81
|
-
| NotImplementedType()
|
|
82
81
|
| str()
|
|
83
|
-
| NoneType()
|
|
84
82
|
| ModuleType()
|
|
83
|
+
| NoneType()
|
|
84
|
+
| NotImplementedType()
|
|
85
|
+
| EllipsisType()
|
|
85
86
|
):
|
|
86
|
-
return
|
|
87
|
+
return BasicConstantValue.of(value)
|
|
87
88
|
case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
|
|
88
|
-
return
|
|
89
|
+
return BasicConstantValue.of(other_type)
|
|
89
90
|
case _GlobalPlaceholder():
|
|
90
91
|
return value.get()
|
|
91
92
|
case comptime_value if getattr(comptime_value, "_is_comptime_value_", False):
|
|
92
|
-
return
|
|
93
|
+
return BasicConstantValue.of(comptime_value)
|
|
93
94
|
case _:
|
|
94
95
|
return None
|
sonolus/script/internal/value.py
CHANGED
|
@@ -14,6 +14,19 @@ class BackingValue:
|
|
|
14
14
|
raise NotImplementedError()
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class ExprBackingValue(BackingValue):
|
|
18
|
+
"""A backing value that is backed by an expression."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, expr: IRExpr):
|
|
21
|
+
self._expr = expr
|
|
22
|
+
|
|
23
|
+
def read(self) -> IRExpr:
|
|
24
|
+
return self._expr
|
|
25
|
+
|
|
26
|
+
def write(self, value: IRExpr) -> IRStmt:
|
|
27
|
+
raise RuntimeError("Value is read-only, cannot write to it")
|
|
28
|
+
|
|
29
|
+
|
|
17
30
|
type DataValue = BlockPlace | BackingValue | float | int | bool
|
|
18
31
|
type BackingSource = Callable[[IRExpr], BackingValue]
|
|
19
32
|
|
sonolus/script/num.py
CHANGED
|
@@ -5,13 +5,13 @@ import operator
|
|
|
5
5
|
from collections.abc import Callable, Iterable
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Self, TypeGuard, final, runtime_checkable
|
|
7
7
|
|
|
8
|
-
from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
|
|
8
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRGet, IRPureInstr, IRSet
|
|
9
9
|
from sonolus.backend.ops import Op
|
|
10
10
|
from sonolus.backend.place import BlockPlace
|
|
11
11
|
from sonolus.script.internal.context import ctx
|
|
12
12
|
from sonolus.script.internal.error import InternalError
|
|
13
13
|
from sonolus.script.internal.impl import meta_fn
|
|
14
|
-
from sonolus.script.internal.value import BackingValue, DataValue, Value
|
|
14
|
+
from sonolus.script.internal.value import BackingValue, DataValue, ExprBackingValue, Value
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _NumMeta(type):
|
|
@@ -32,11 +32,13 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
32
32
|
|
|
33
33
|
data: DataValue
|
|
34
34
|
|
|
35
|
-
def __init__(self, data: DataValue):
|
|
35
|
+
def __init__(self, data: DataValue | IRExpr):
|
|
36
36
|
if isinstance(data, complex):
|
|
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 isinstance(data, IRConst | IRPureInstr | IRGet):
|
|
41
|
+
data = ExprBackingValue(data)
|
|
40
42
|
if _is_num(data):
|
|
41
43
|
raise InternalError("Cannot create a Num from a Num")
|
|
42
44
|
if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
|
|
@@ -112,10 +114,11 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
112
114
|
return Num(self.data)
|
|
113
115
|
|
|
114
116
|
def _set_(self, value: Self):
|
|
117
|
+
value = Num._accept_(value)
|
|
115
118
|
if ctx():
|
|
116
119
|
match self.data:
|
|
117
120
|
case BackingValue():
|
|
118
|
-
|
|
121
|
+
self.data.write(value.ir())
|
|
119
122
|
case BlockPlace():
|
|
120
123
|
ctx().check_writable(self.data)
|
|
121
124
|
ctx().add_statements(IRSet(self.data, value.ir()))
|
sonolus/script/particle.py
CHANGED
sonolus/script/quad.py
CHANGED
|
@@ -11,7 +11,7 @@ class Quad(Record):
|
|
|
11
11
|
"""A quad defined by its four corners.
|
|
12
12
|
|
|
13
13
|
Usage:
|
|
14
|
-
```
|
|
14
|
+
```python
|
|
15
15
|
Quad(bl: Vec2, tl: Vec2, tr: Vec2, br: Vec2)
|
|
16
16
|
```
|
|
17
17
|
"""
|
|
@@ -160,7 +160,7 @@ class Rect(Record):
|
|
|
160
160
|
"""A rectangle defined by its top, right, bottom, and left edges.
|
|
161
161
|
|
|
162
162
|
Usage:
|
|
163
|
-
```
|
|
163
|
+
```python
|
|
164
164
|
Rect(t: float, r: float, b: float, l: float)
|
|
165
165
|
```
|
|
166
166
|
"""
|
|
@@ -223,7 +223,7 @@ class Rect(Record):
|
|
|
223
223
|
return Vec2((self.l + self.r) / 2, (self.t + self.b) / 2)
|
|
224
224
|
|
|
225
225
|
def as_quad(self) -> Quad:
|
|
226
|
-
"""Convert the rectangle to a quad."""
|
|
226
|
+
"""Convert the rectangle to a [`Quad`][sonolus.script.quad.Quad]."""
|
|
227
227
|
return Quad(
|
|
228
228
|
bl=self.bl,
|
|
229
229
|
tl=self.tl,
|
sonolus/script/record.py
CHANGED
|
@@ -16,7 +16,7 @@ from sonolus.script.internal.generic import (
|
|
|
16
16
|
validate_type_spec,
|
|
17
17
|
)
|
|
18
18
|
from sonolus.script.internal.impl import meta_fn
|
|
19
|
-
from sonolus.script.internal.value import DataValue, Value
|
|
19
|
+
from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
20
20
|
from sonolus.script.num import Num
|
|
21
21
|
|
|
22
22
|
|
|
@@ -157,6 +157,17 @@ class Record(GenericValue):
|
|
|
157
157
|
def _is_value_type_(cls) -> bool:
|
|
158
158
|
return False
|
|
159
159
|
|
|
160
|
+
@classmethod
|
|
161
|
+
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
162
|
+
result = object.__new__(cls)
|
|
163
|
+
result._value = {
|
|
164
|
+
field.name: field.type._from_backing_source_(
|
|
165
|
+
lambda offset, field_offset=field.offset: source((Num(offset) + Num(field_offset)).ir())
|
|
166
|
+
)
|
|
167
|
+
for field in cls._fields
|
|
168
|
+
}
|
|
169
|
+
return result
|
|
170
|
+
|
|
160
171
|
@classmethod
|
|
161
172
|
def _from_place_(cls, place: BlockPlace) -> Self:
|
|
162
173
|
result = object.__new__(cls)
|
|
@@ -203,7 +214,7 @@ class Record(GenericValue):
|
|
|
203
214
|
return self
|
|
204
215
|
|
|
205
216
|
def _set_(self, value: Self):
|
|
206
|
-
raise TypeError("Record does not support
|
|
217
|
+
raise TypeError("Record does not support _set_")
|
|
207
218
|
|
|
208
219
|
def _copy_from_(self, value: Self):
|
|
209
220
|
value = self._accept_(value)
|
sonolus/script/stream.py
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import cast, dataclass_transform
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.ir import IRExpr, IRInstr, IRPureInstr, IRConst
|
|
6
|
+
from sonolus.backend.mode import Mode
|
|
7
|
+
from sonolus.backend.ops import Op
|
|
8
|
+
from sonolus.script.internal.context import ctx
|
|
9
|
+
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
10
|
+
from sonolus.script.internal.impl import meta_fn
|
|
11
|
+
from sonolus.script.internal.introspection import get_field_specifiers
|
|
12
|
+
from sonolus.script.internal.native import native_function
|
|
13
|
+
from sonolus.script.internal.value import BackingValue, Value
|
|
14
|
+
from sonolus.script.iterator import SonolusIterator
|
|
15
|
+
from sonolus.script.num import Num
|
|
16
|
+
from sonolus.script.record import Record
|
|
17
|
+
from sonolus.script.values import sizeof
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _StreamField(SonolusDescriptor):
|
|
21
|
+
offset: int
|
|
22
|
+
type_: type[Stream] | type[StreamGroup]
|
|
23
|
+
|
|
24
|
+
def __init__(self, offset: int, type_: type[Stream] | type[StreamGroup]):
|
|
25
|
+
self.offset = offset
|
|
26
|
+
self.type_ = type_
|
|
27
|
+
|
|
28
|
+
def __get__(self, instance, owner):
|
|
29
|
+
_check_can_read_or_write_stream()
|
|
30
|
+
return self.type_(self.offset)
|
|
31
|
+
|
|
32
|
+
def __set__(self, instance, value):
|
|
33
|
+
raise AttributeError("Cannot set attribute")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _StreamDataField(SonolusDescriptor):
|
|
37
|
+
offset: int
|
|
38
|
+
type_: type[Value]
|
|
39
|
+
|
|
40
|
+
def __init__(self, offset: int, type_: type[Value]):
|
|
41
|
+
self.offset = offset
|
|
42
|
+
self.type_ = type_
|
|
43
|
+
|
|
44
|
+
def _get(self):
|
|
45
|
+
return self.type_._from_backing_source_(lambda offset: _SparseStreamBacking(self.offset, Num(offset)))
|
|
46
|
+
|
|
47
|
+
def __get__(self, instance, owner):
|
|
48
|
+
_check_can_read_or_write_stream()
|
|
49
|
+
return self._get()._get_()
|
|
50
|
+
|
|
51
|
+
def __set__(self, instance, value):
|
|
52
|
+
_check_can_write_stream()
|
|
53
|
+
if self.type_._is_value_type_():
|
|
54
|
+
self._get()._set_(value)
|
|
55
|
+
else:
|
|
56
|
+
self._get()._copy_from_(value)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass_transform()
|
|
60
|
+
def streams[T](cls: type[T]) -> T:
|
|
61
|
+
"""Decorator to define streams and stream groups.
|
|
62
|
+
|
|
63
|
+
Streams and stream groups are declared by annotating class attributes with `Stream` or `StreamGroup`.
|
|
64
|
+
|
|
65
|
+
Other types are also supported in the form of data fields. They may be used to store additional data to export from
|
|
66
|
+
Play to Watch mode.
|
|
67
|
+
|
|
68
|
+
In either case, data is write-only in Play mode and read-only in Watch mode.
|
|
69
|
+
|
|
70
|
+
This should only be used once in most projects, as multiple decorated classes will overlap with each other and
|
|
71
|
+
interfere when both are used at the same time.
|
|
72
|
+
|
|
73
|
+
For backwards compatibility, new streams and stream groups should be added to the end of existing ones, and
|
|
74
|
+
lengths and element types of existing streams and stream groups should not be changed. Otherwise, old replays may
|
|
75
|
+
not work on new versions of the engine.
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
```python
|
|
79
|
+
@streams
|
|
80
|
+
class Streams:
|
|
81
|
+
stream_1: Stream[Num] # A stream of Num values
|
|
82
|
+
stream_2: Stream[Vec2] # A stream of Vec2 values
|
|
83
|
+
group_1: StreamGroup[Num, 10] # A group of 10 Num streams
|
|
84
|
+
group_2: StreamGroup[Vec2, 5] # A group of 5 Vec2 streams
|
|
85
|
+
|
|
86
|
+
data_field_1: Num # A data field of type Num
|
|
87
|
+
data_field_2: Vec2 # A data field of type Vec2
|
|
88
|
+
```
|
|
89
|
+
"""
|
|
90
|
+
if len(cls.__bases__) != 1:
|
|
91
|
+
raise ValueError("Options class must not inherit from any class (except object)")
|
|
92
|
+
instance = cls()
|
|
93
|
+
entries = []
|
|
94
|
+
# Offset 0 is unused so we can tell when a stream object is uninitialized since it'll have offset 0.
|
|
95
|
+
offset = 1
|
|
96
|
+
for name, annotation in get_field_specifiers(cls).items():
|
|
97
|
+
if issubclass(annotation, Stream | StreamGroup):
|
|
98
|
+
annotation = cast(type[Stream | StreamGroup], annotation)
|
|
99
|
+
if annotation is Stream or annotation is StreamGroup:
|
|
100
|
+
raise TypeError(f"Invalid annotation for streams: {annotation}. Must have type arguments.")
|
|
101
|
+
setattr(cls, name, _StreamField(offset, annotation))
|
|
102
|
+
# Streams store their data across several backing streams
|
|
103
|
+
entries.append((name, offset, annotation))
|
|
104
|
+
offset += annotation.backing_size()
|
|
105
|
+
elif issubclass(annotation, Value) and annotation._is_concrete_():
|
|
106
|
+
setattr(cls, name, _StreamDataField(offset, annotation))
|
|
107
|
+
# Data fields store their data in a single backing stream at different offsets in the same stream
|
|
108
|
+
entries.append((name, offset, annotation))
|
|
109
|
+
offset += 1
|
|
110
|
+
instance._streams_ = entries
|
|
111
|
+
instance._is_comptime_value_ = True
|
|
112
|
+
return instance
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@meta_fn
|
|
116
|
+
def _check_can_read_stream() -> None:
|
|
117
|
+
if not ctx() or ctx().global_state.mode != Mode.WATCH:
|
|
118
|
+
raise RuntimeError("Stream read operations are only allowed in watch mode.")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@meta_fn
|
|
122
|
+
def _check_can_write_stream() -> None:
|
|
123
|
+
if not ctx() or ctx().global_state.mode != Mode.PLAY:
|
|
124
|
+
raise RuntimeError("Stream write operations are only allowed in play mode.")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@meta_fn
|
|
128
|
+
def _check_can_read_or_write_stream() -> None:
|
|
129
|
+
if not ctx() or ctx().global_state.mode not in {Mode.PLAY, Mode.WATCH}:
|
|
130
|
+
raise RuntimeError("Stream operations are only allowed in play and watch modes.")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _StreamBacking(BackingValue):
|
|
134
|
+
id: Num
|
|
135
|
+
index: Num
|
|
136
|
+
|
|
137
|
+
def __init__(self, stream_id: int, index: Num):
|
|
138
|
+
super().__init__()
|
|
139
|
+
self.id = Num._accept_(stream_id)
|
|
140
|
+
self.index = Num._accept_(index)
|
|
141
|
+
|
|
142
|
+
def read(self) -> IRExpr:
|
|
143
|
+
"""Read the value from the stream."""
|
|
144
|
+
_check_can_read_stream()
|
|
145
|
+
return IRPureInstr(Op.StreamGetValue, [self.id.ir(), self.index.ir()])
|
|
146
|
+
|
|
147
|
+
def write(self, value: IRExpr) -> None:
|
|
148
|
+
"""Write the value to the stream."""
|
|
149
|
+
_check_can_write_stream()
|
|
150
|
+
ctx().add_statement(IRInstr(Op.StreamSet, [self.id.ir(), self.index.ir(), value]))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class _SparseStreamBacking(BackingValue):
|
|
154
|
+
id: Num
|
|
155
|
+
index: Num
|
|
156
|
+
|
|
157
|
+
def __init__(self, stream_id: int, index: Num):
|
|
158
|
+
super().__init__()
|
|
159
|
+
self.id = Num._accept_(stream_id)
|
|
160
|
+
self.index = Num._accept_(index)
|
|
161
|
+
|
|
162
|
+
def read(self) -> IRExpr:
|
|
163
|
+
"""Read the value from the stream."""
|
|
164
|
+
_check_can_read_stream()
|
|
165
|
+
return IRPureInstr(Op.StreamGetValue, [self.id.ir(), self.index.ir()])
|
|
166
|
+
|
|
167
|
+
def write(self, value: IRExpr) -> None:
|
|
168
|
+
"""Write the value to the stream."""
|
|
169
|
+
_check_can_write_stream()
|
|
170
|
+
ctx().add_statements(
|
|
171
|
+
IRInstr(Op.StreamSet, [self.id.ir(), self.index.ir(), value]),
|
|
172
|
+
IRInstr(Op.StreamSet, [self.id.ir(), (self.index - 0.5).ir(), IRConst(0)]),
|
|
173
|
+
IRInstr(Op.StreamSet, [self.id.ir(), (self.index + 0.5).ir(), IRConst(0)]),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class Stream[T](Record, BackingValue):
|
|
178
|
+
"""Represents a stream.
|
|
179
|
+
|
|
180
|
+
Most users should use `@stream` to declare streams and stream groups rather than using this class directly.
|
|
181
|
+
|
|
182
|
+
If used directly, it is important that streams do not overlap. No other streams should have an offset in
|
|
183
|
+
`range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
|
|
184
|
+
with each other.
|
|
185
|
+
|
|
186
|
+
Usage:
|
|
187
|
+
Declaring a stream:
|
|
188
|
+
```python
|
|
189
|
+
@streams
|
|
190
|
+
class Streams:
|
|
191
|
+
my_stream_1: Stream[Num] # A stream of Num values
|
|
192
|
+
my_stream_2: Stream[Vec2] # A stream of Vec2 values
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Directly creating a stream (advanced usage):
|
|
196
|
+
```python
|
|
197
|
+
stream = Stream[Num](offset=0)
|
|
198
|
+
```
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
offset: int
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def element_type(cls) -> type[T] | type[Value]:
|
|
205
|
+
"""Return the type of elements in this array type."""
|
|
206
|
+
return cls.type_var_value(T)
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
@meta_fn
|
|
210
|
+
def backing_size(cls) -> int:
|
|
211
|
+
"""Return the number of underlying single-value streams backing this stream."""
|
|
212
|
+
return max(1, sizeof(cls.element_type()))
|
|
213
|
+
|
|
214
|
+
def __contains__(self, item: int | float) -> bool:
|
|
215
|
+
"""Check if the stream contains the key."""
|
|
216
|
+
_check_can_read_stream()
|
|
217
|
+
return _stream_has(self.offset, item)
|
|
218
|
+
|
|
219
|
+
@meta_fn
|
|
220
|
+
def __getitem__(self, key: int | float) -> T:
|
|
221
|
+
"""Get the value corresponding to the key.
|
|
222
|
+
|
|
223
|
+
If the key is not in the stream, interpolates linearly between surrounding values.
|
|
224
|
+
If the stream is empty, returns the zero value of the element type.
|
|
225
|
+
"""
|
|
226
|
+
# This is allowed in Play mode since a stream value may be accessed just to write to it without reading.
|
|
227
|
+
_check_can_read_or_write_stream()
|
|
228
|
+
return self.element_type()._from_backing_source_(lambda offset: _StreamBacking(self.offset + Num(offset), key))
|
|
229
|
+
|
|
230
|
+
@meta_fn
|
|
231
|
+
def __setitem__(self, key: int | float, value: T) -> None:
|
|
232
|
+
"""Set the value corresponding to the key."""
|
|
233
|
+
_check_can_write_stream()
|
|
234
|
+
if not self.element_type()._accepts_(value):
|
|
235
|
+
raise TypeError(f"Cannot set value of type {type(value)} to stream of type {self.element_type()}.")
|
|
236
|
+
if self.element_type()._size_() == 0:
|
|
237
|
+
# We still need to store something to preserve the key, so this is a special case.
|
|
238
|
+
_stream_set(self.offset, key, 0)
|
|
239
|
+
else:
|
|
240
|
+
for i, v in enumerate(value._to_list_()):
|
|
241
|
+
_stream_set(self.offset + i, key, Num(v))
|
|
242
|
+
|
|
243
|
+
def next_key(self, key: int | float) -> int:
|
|
244
|
+
"""Get the next key, or the key unchanged if it is the last key or the stream is empty.
|
|
245
|
+
|
|
246
|
+
If the key is in the stream and there is a next key, returns the next key.
|
|
247
|
+
"""
|
|
248
|
+
_check_can_read_stream()
|
|
249
|
+
return _stream_get_next_key(self.offset, key)
|
|
250
|
+
|
|
251
|
+
def previous_key(self, key: int | float) -> int:
|
|
252
|
+
"""Get the previous key, or the key unchanged if it is the first key or the stream is empty.
|
|
253
|
+
|
|
254
|
+
If the key is in the stream and there is a previous key, returns the previous key.
|
|
255
|
+
"""
|
|
256
|
+
_check_can_read_stream()
|
|
257
|
+
return _stream_get_previous_key(self.offset, key)
|
|
258
|
+
|
|
259
|
+
def has_next_key(self, key: int | float) -> bool:
|
|
260
|
+
"""Check if there is a next key after the given key in the stream."""
|
|
261
|
+
_check_can_read_stream()
|
|
262
|
+
next_key = self.next_key(key)
|
|
263
|
+
return next_key > key
|
|
264
|
+
|
|
265
|
+
def has_previous_key(self, key: int | float) -> bool:
|
|
266
|
+
"""Check if there is a previous key before the given key in the stream."""
|
|
267
|
+
_check_can_read_stream()
|
|
268
|
+
previous_key = self.previous_key(key)
|
|
269
|
+
return previous_key < key
|
|
270
|
+
|
|
271
|
+
def next_key_inclusive(self, key: int | float) -> int:
|
|
272
|
+
"""Like `next_key`, but returns the key itself if it is in the stream."""
|
|
273
|
+
_check_can_read_stream()
|
|
274
|
+
return key if key in self else self.next_key(key)
|
|
275
|
+
|
|
276
|
+
def previous_key_inclusive(self, key: int | float) -> int:
|
|
277
|
+
"""Like `previous_key`, but returns the key itself if it is in the stream."""
|
|
278
|
+
_check_can_read_stream()
|
|
279
|
+
return key if key in self else self.previous_key(key)
|
|
280
|
+
|
|
281
|
+
def get_next(self, key: int | float) -> T:
|
|
282
|
+
"""Get the value corresponding to the next key.
|
|
283
|
+
|
|
284
|
+
If there is no next key, returns the value at the given key. Equivalent to `self[self.next_key(key)]`.
|
|
285
|
+
"""
|
|
286
|
+
_check_can_read_stream()
|
|
287
|
+
return self[self.next_key(key)]
|
|
288
|
+
|
|
289
|
+
def get_previous(self, key: int | float) -> T:
|
|
290
|
+
"""Get the value corresponding to the previous key.
|
|
291
|
+
|
|
292
|
+
If there is no previous key, returns the value at the given key. Equivalent to `self[self.previous_key(key)]`.
|
|
293
|
+
"""
|
|
294
|
+
_check_can_read_stream()
|
|
295
|
+
return self[self.previous_key(key)]
|
|
296
|
+
|
|
297
|
+
def get_next_inclusive(self, key: int | float) -> T:
|
|
298
|
+
"""Get the value corresponding to the next key, or the value at the given key if it is in the stream.
|
|
299
|
+
|
|
300
|
+
Equivalent to `self[self.next_key_inclusive(key)]`.
|
|
301
|
+
"""
|
|
302
|
+
_check_can_read_stream()
|
|
303
|
+
return self[self.next_key_inclusive(key)]
|
|
304
|
+
|
|
305
|
+
def iter_items_from(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
306
|
+
"""Iterate over the items in the stream in ascending order starting from the given key.
|
|
307
|
+
|
|
308
|
+
If the key is in the stream, it will be included in the iteration.
|
|
309
|
+
|
|
310
|
+
Usage:
|
|
311
|
+
```python
|
|
312
|
+
stream = ...
|
|
313
|
+
for key, value in stream.iter_items_from(0):
|
|
314
|
+
do_something(key, value)
|
|
315
|
+
```
|
|
316
|
+
"""
|
|
317
|
+
_check_can_read_stream()
|
|
318
|
+
return _StreamAscIterator(self, self.next_key_inclusive(start))
|
|
319
|
+
|
|
320
|
+
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
321
|
+
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
322
|
+
|
|
323
|
+
If the key is in the stream, it will be included in the iteration.
|
|
324
|
+
|
|
325
|
+
Usage:
|
|
326
|
+
```python
|
|
327
|
+
stream = ...
|
|
328
|
+
for key, value in stream.iter_items_from_desc(0):
|
|
329
|
+
do_something(key, value)
|
|
330
|
+
```
|
|
331
|
+
"""
|
|
332
|
+
_check_can_read_stream()
|
|
333
|
+
return _StreamDescIterator(self, self.previous_key_inclusive(start))
|
|
334
|
+
|
|
335
|
+
def iter_keys_from(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
336
|
+
"""Iterate over the keys in the stream in ascending order starting from the given key.
|
|
337
|
+
|
|
338
|
+
If the key is in the stream, it will be included in the iteration.
|
|
339
|
+
|
|
340
|
+
Usage:
|
|
341
|
+
```python
|
|
342
|
+
stream = ...
|
|
343
|
+
for key in stream.iter_keys_from(0):
|
|
344
|
+
do_something(key)
|
|
345
|
+
```
|
|
346
|
+
"""
|
|
347
|
+
_check_can_read_stream()
|
|
348
|
+
return _StreamAscKeyIterator(self, self.next_key_inclusive(start))
|
|
349
|
+
|
|
350
|
+
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
351
|
+
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
352
|
+
|
|
353
|
+
If the key is in the stream, it will be included in the iteration.
|
|
354
|
+
|
|
355
|
+
Usage:
|
|
356
|
+
```python
|
|
357
|
+
stream = ...
|
|
358
|
+
for key in stream.iter_keys_from_desc(0):
|
|
359
|
+
do_something(key)
|
|
360
|
+
```
|
|
361
|
+
"""
|
|
362
|
+
_check_can_read_stream()
|
|
363
|
+
return _StreamDescKeyIterator(self, self.previous_key_inclusive(start))
|
|
364
|
+
|
|
365
|
+
def iter_values_from(self, start: int | float, /) -> SonolusIterator[T]:
|
|
366
|
+
"""Iterate over the values in the stream in ascending order starting from the given key.
|
|
367
|
+
|
|
368
|
+
If the key is in the stream, it will be included in the iteration.
|
|
369
|
+
|
|
370
|
+
Usage:
|
|
371
|
+
```python
|
|
372
|
+
stream = ...
|
|
373
|
+
for value in stream.iter_values_from(0):
|
|
374
|
+
do_something(value)
|
|
375
|
+
```
|
|
376
|
+
"""
|
|
377
|
+
_check_can_read_stream()
|
|
378
|
+
return _StreamAscValueIterator(self, self.next_key_inclusive(start))
|
|
379
|
+
|
|
380
|
+
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
381
|
+
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
382
|
+
|
|
383
|
+
If the key is in the stream, it will be included in the iteration.
|
|
384
|
+
|
|
385
|
+
Usage:
|
|
386
|
+
```python
|
|
387
|
+
stream = ...
|
|
388
|
+
for value in stream.iter_values_from_desc(0):
|
|
389
|
+
do_something(value)
|
|
390
|
+
```
|
|
391
|
+
"""
|
|
392
|
+
_check_can_read_stream()
|
|
393
|
+
return _StreamDescValueIterator(self, self.previous_key_inclusive(start))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class StreamGroup[T, Size](Record):
|
|
397
|
+
"""Represents a group of streams.
|
|
398
|
+
|
|
399
|
+
Most users should use `@stream` to declare stream groups rather than using this class directly.
|
|
400
|
+
|
|
401
|
+
Usage:
|
|
402
|
+
Declaring a stream group:
|
|
403
|
+
```python
|
|
404
|
+
@streams
|
|
405
|
+
class Streams:
|
|
406
|
+
my_group_1: StreamGroup[Num, 10] # A group of 10 Num streams
|
|
407
|
+
my_group_2: StreamGroup[Vec2, 5] # A group of 5 Vec2 streams
|
|
408
|
+
```
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
offset: int
|
|
412
|
+
|
|
413
|
+
@classmethod
|
|
414
|
+
def size(cls) -> Size:
|
|
415
|
+
"""Return the size of the group."""
|
|
416
|
+
return cls.type_var_value(Size)
|
|
417
|
+
|
|
418
|
+
@classmethod
|
|
419
|
+
def element_type(cls) -> type[T] | type[Value]:
|
|
420
|
+
"""Return the type of elements in this group."""
|
|
421
|
+
return cls.type_var_value(T)
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
@meta_fn
|
|
425
|
+
def backing_size(cls) -> int:
|
|
426
|
+
"""Return the number of underlying single-value streams backing this stream."""
|
|
427
|
+
return max(1, sizeof(cls.element_type())) * cls.size()
|
|
428
|
+
|
|
429
|
+
def __contains__(self, item: int) -> bool:
|
|
430
|
+
"""Check if the group contains the stream with the given index."""
|
|
431
|
+
_check_can_read_or_write_stream()
|
|
432
|
+
return 0 <= item < self.size()
|
|
433
|
+
|
|
434
|
+
def __getitem__(self, index: int) -> Stream[T]:
|
|
435
|
+
"""Get the stream at the given index."""
|
|
436
|
+
_check_can_read_or_write_stream()
|
|
437
|
+
assert index in self
|
|
438
|
+
# Size 0 elements still need 1 stream to preserve the key.
|
|
439
|
+
return Stream[self.type_var_value(T)](max(1, sizeof(self.element_type())) * index + self.offset)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
443
|
+
stream: Stream[T]
|
|
444
|
+
current_key: int | float
|
|
445
|
+
|
|
446
|
+
def has_next(self) -> bool:
|
|
447
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
448
|
+
|
|
449
|
+
def get(self) -> tuple[int | float, T]:
|
|
450
|
+
return self.current_key, self.stream[self.current_key]
|
|
451
|
+
|
|
452
|
+
def advance(self):
|
|
453
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
457
|
+
stream: Stream[T]
|
|
458
|
+
current_key: int | float
|
|
459
|
+
|
|
460
|
+
def has_next(self) -> bool:
|
|
461
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
462
|
+
|
|
463
|
+
def get(self) -> tuple[int | float, T]:
|
|
464
|
+
return self.current_key, self.stream[self.current_key]
|
|
465
|
+
|
|
466
|
+
def advance(self):
|
|
467
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
471
|
+
stream: Stream[T]
|
|
472
|
+
current_key: int | float
|
|
473
|
+
|
|
474
|
+
def has_next(self) -> bool:
|
|
475
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
476
|
+
|
|
477
|
+
def get(self) -> int | float:
|
|
478
|
+
return self.current_key
|
|
479
|
+
|
|
480
|
+
def advance(self):
|
|
481
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
485
|
+
stream: Stream[T]
|
|
486
|
+
current_key: int | float
|
|
487
|
+
|
|
488
|
+
def has_next(self) -> bool:
|
|
489
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
490
|
+
|
|
491
|
+
def get(self) -> int | float:
|
|
492
|
+
return self.current_key
|
|
493
|
+
|
|
494
|
+
def advance(self):
|
|
495
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
499
|
+
stream: Stream[T]
|
|
500
|
+
current_key: int | float
|
|
501
|
+
|
|
502
|
+
def has_next(self) -> bool:
|
|
503
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
504
|
+
|
|
505
|
+
def get(self) -> T:
|
|
506
|
+
return self.stream[self.current_key]
|
|
507
|
+
|
|
508
|
+
def advance(self):
|
|
509
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
513
|
+
stream: Stream[T]
|
|
514
|
+
current_key: int | float
|
|
515
|
+
|
|
516
|
+
def has_next(self) -> bool:
|
|
517
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
518
|
+
|
|
519
|
+
def get(self) -> T:
|
|
520
|
+
return self.stream[self.current_key]
|
|
521
|
+
|
|
522
|
+
def advance(self):
|
|
523
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@native_function(Op.StreamGetNextKey)
|
|
527
|
+
def _stream_get_next_key(stream_id: int, key: int | float) -> int:
|
|
528
|
+
"""Get the next key in the stream, or the key unchanged if it is the last key or the stream is empty."""
|
|
529
|
+
raise NotImplementedError
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@native_function(Op.StreamGetPreviousKey)
|
|
533
|
+
def _stream_get_previous_key(stream_id: int, key: int | float) -> int:
|
|
534
|
+
"""Get the previous key in the stream, or the key unchanged if it is the first key or the stream is empty."""
|
|
535
|
+
raise NotImplementedError
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@native_function(Op.StreamGetValue)
|
|
539
|
+
def _stream_get_value(stream_id: int, key: int | float) -> float:
|
|
540
|
+
"""Get the value of the key in the stream."""
|
|
541
|
+
raise NotImplementedError
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@native_function(Op.StreamHas)
|
|
545
|
+
def _stream_has(stream_id: int, key: int | float) -> bool:
|
|
546
|
+
"""Check if the stream has the key."""
|
|
547
|
+
raise NotImplementedError
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@native_function(Op.StreamSet)
|
|
551
|
+
def _stream_set(stream_id: int, key: int | float, value: float) -> None:
|
|
552
|
+
"""Set the value of the key in the stream."""
|
|
553
|
+
raise NotImplementedError
|
sonolus/script/transform.py
CHANGED
|
@@ -10,7 +10,7 @@ class Transform2d(Record):
|
|
|
10
10
|
"""A transformation matrix for 2D points.
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
```
|
|
13
|
+
```python
|
|
14
14
|
Transform2d.new()
|
|
15
15
|
```
|
|
16
16
|
"""
|
|
@@ -368,7 +368,7 @@ class Transform2d(Record):
|
|
|
368
368
|
return other.compose(self)
|
|
369
369
|
|
|
370
370
|
def transform_vec(self, v: Vec2) -> Vec2:
|
|
371
|
-
"""Transform a Vec2 and return a new Vec2.
|
|
371
|
+
"""Transform a [`Vec2`][sonolus.script.vec.Vec2] and return a new [`Vec2`][sonolus.script.vec.Vec2].
|
|
372
372
|
|
|
373
373
|
Args:
|
|
374
374
|
v: The vector to transform.
|
|
@@ -382,7 +382,7 @@ class Transform2d(Record):
|
|
|
382
382
|
return Vec2(x / w, y / w)
|
|
383
383
|
|
|
384
384
|
def transform_quad(self, quad: QuadLike) -> Quad:
|
|
385
|
-
"""Transform a Quad and return a new Quad.
|
|
385
|
+
"""Transform a [`Quad`][sonolus.script.quad.Quad] and return a new [`Quad`][sonolus.script.quad.Quad].
|
|
386
386
|
|
|
387
387
|
Args:
|
|
388
388
|
quad: The quad to transform.
|
sonolus/script/values.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from sonolus.script.internal.context import ctx
|
|
2
2
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
3
3
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
4
|
+
from sonolus.script.num import Num
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
@meta_fn
|
|
@@ -37,3 +38,13 @@ def swap[T](a: T, b: T):
|
|
|
37
38
|
temp = copy(a)
|
|
38
39
|
a @= b
|
|
39
40
|
b @= temp
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@meta_fn
|
|
44
|
+
def sizeof(type_: type, /) -> int:
|
|
45
|
+
"""Return the size of the given type."""
|
|
46
|
+
type_ = validate_concrete_type(type_)
|
|
47
|
+
if ctx():
|
|
48
|
+
return Num(type_._size_())
|
|
49
|
+
else:
|
|
50
|
+
return type_._size_()
|
|
@@ -8,7 +8,7 @@ sonolus/backend/interpret.py,sha256=B0jqlLmEGoyO2mxpcvwRwV17Tq_gOE9wLNt26Q5QOfs,
|
|
|
8
8
|
sonolus/backend/ir.py,sha256=TCDLMvlX2S8emFDQwFVeD2OUC4fnhbrMObgYtoa_7PQ,2845
|
|
9
9
|
sonolus/backend/mode.py,sha256=NkcPZJm8dn83LX35uP24MtQOCnfRDFZ280dHeEEfauE,613
|
|
10
10
|
sonolus/backend/node.py,sha256=H8qgnNyIseR-DhfgtcbDX03SUmhAJSSrYAlUEJTkkUo,999
|
|
11
|
-
sonolus/backend/ops.py,sha256=
|
|
11
|
+
sonolus/backend/ops.py,sha256=7DERBPU6z9Bz3i6UxyLdFTXYcCpvRZVNWw4ZQ-Djwnk,10510
|
|
12
12
|
sonolus/backend/place.py,sha256=jABvLNNE-2pklTcb9WnyfHK8c-tYxn0ObsoLp5LYd5I,4703
|
|
13
13
|
sonolus/backend/utils.py,sha256=9-mmCUwGlNdjp51jKrNBN2dGxCAXVV2PdJn031kaXHM,1717
|
|
14
14
|
sonolus/backend/visitor.py,sha256=GvAEx8D3_YijTMUYBmir0IvDPAEpK1ObAuQOAcAMKm0,48157
|
|
@@ -34,10 +34,10 @@ sonolus/build/level.py,sha256=AjvK4725nqDcg7oGn5kWocBdG-AcirXpku74T7c2epA,673
|
|
|
34
34
|
sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
|
|
35
35
|
sonolus/build/project.py,sha256=DhNqgHnm73qKUOhrg1JPlWEL0Vg7VxcGUbNokpMWzVE,6315
|
|
36
36
|
sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
sonolus/script/archetype.py,sha256=
|
|
38
|
-
sonolus/script/array.py,sha256=
|
|
39
|
-
sonolus/script/array_like.py,sha256=
|
|
40
|
-
sonolus/script/bucket.py,sha256=
|
|
37
|
+
sonolus/script/archetype.py,sha256=s9ucFQKIihYkxDlN1eA4SIOWzzIxVrDQSTBeCbllKJ0,40120
|
|
38
|
+
sonolus/script/array.py,sha256=cZcDd_UqTdDFP3fD_r6qdOCtdKSZyVvsDQtcr0hTmMs,11483
|
|
39
|
+
sonolus/script/array_like.py,sha256=gnpjSUZVTxe13ILcSWHAm3tGVMl6tmNUEzJZg8kH_zo,8552
|
|
40
|
+
sonolus/script/bucket.py,sha256=YFSyKS4ZngxerBlKwFBSCRAVewgQdwZ1-NqfPKcPZxI,7519
|
|
41
41
|
sonolus/script/containers.py,sha256=QUFUk1lDwM2AzIPH6uHkZtZXT2D4_xT3P6pJIp6i7Ew,17640
|
|
42
42
|
sonolus/script/debug.py,sha256=aYyFm8DuoifK0L0pY4G7ZZEEeSqbig90Yogy6Z_M8Yg,3135
|
|
43
43
|
sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
|
|
@@ -49,32 +49,33 @@ sonolus/script/interval.py,sha256=IYNgJx0ngREnEVu_yMAu0MrA_Q7mZuToT8U3fbdb3Sc,91
|
|
|
49
49
|
sonolus/script/iterator.py,sha256=OHnIOKRchzVCMaN33Tubo6EzqV61ZYUxDWIJ2S5G6iQ,4590
|
|
50
50
|
sonolus/script/level.py,sha256=wR23xk-NOcW_JMRb3R12sqIXCLSZL-7cM3y7IpMF1J0,6333
|
|
51
51
|
sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
|
|
52
|
-
sonolus/script/num.py,sha256=
|
|
52
|
+
sonolus/script/num.py,sha256=Eniv447z8LIAkiZK9Cr_35ZXPnb7Dvy_3c9yq1INoYs,14768
|
|
53
53
|
sonolus/script/options.py,sha256=ZD9I898wMrpPQ8IyuDoYef804rZzhSvPoC_0bxovIec,8245
|
|
54
|
-
sonolus/script/particle.py,sha256=
|
|
54
|
+
sonolus/script/particle.py,sha256=oeVQF01xOeW2BEn04ZK1ZOP2HGvQzxBJCpITFjy9woQ,8353
|
|
55
55
|
sonolus/script/pointer.py,sha256=IH2_a0XE76uG_UyYM9jAYIf7qZ5LhUNc9ksXDIvAPZA,1511
|
|
56
56
|
sonolus/script/printing.py,sha256=mNYu9QWiacBBGZrnePZQMVwbbguoelUps9GiOK_aVRU,2096
|
|
57
57
|
sonolus/script/project.py,sha256=jLndgGJHdkqFYe-lDl_IzTjQ4gOSuy80en8WoSWXnB8,3340
|
|
58
|
-
sonolus/script/quad.py,sha256=
|
|
59
|
-
sonolus/script/record.py,sha256=
|
|
58
|
+
sonolus/script/quad.py,sha256=TwmQEsa7_UDSly4uXjN9RRQBVu7Qc_-rpDvhy6cy3KQ,10109
|
|
59
|
+
sonolus/script/record.py,sha256=ouS5f7HVjH60esC0ryVOJL4q7Lt48CHokspgdf2kiH4,11742
|
|
60
60
|
sonolus/script/runtime.py,sha256=MIGqDqNitlI6vFtZ2SP32D6LCLJ3RAuGKSxKKl-1tJw,20303
|
|
61
61
|
sonolus/script/sprite.py,sha256=CMcRAZ2hejXnaBmY2_n1_rj6hGOgPP5zEW-BpyaEVOY,16256
|
|
62
|
+
sonolus/script/stream.py,sha256=5LmQXRmNUiYln67krgocXhiS09dU5BXQuo29CqJk1TE,19989
|
|
62
63
|
sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
|
|
63
64
|
sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
|
|
64
|
-
sonolus/script/transform.py,sha256=
|
|
65
|
+
sonolus/script/transform.py,sha256=iFY51p6Q0_VlAjlGCDxB14Vj5NlvAB_9VNzDjHeaxDs,10884
|
|
65
66
|
sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
|
|
66
|
-
sonolus/script/values.py,sha256=
|
|
67
|
+
sonolus/script/values.py,sha256=kyjJ9ODkBi_eJE-t4WYt0wGkbSMNMRkTqpUPZzVVPOU,1289
|
|
67
68
|
sonolus/script/vec.py,sha256=4ntfJ96zxKR-nVZluUgHd-Ud4vNfButfiv7EsroZxfE,7002
|
|
68
69
|
sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
|
|
69
70
|
sonolus/script/internal/builtin_impls.py,sha256=w9QqxJQ2YR5pVy820qWmAy0utbsW4hcSzMvA_yNgsvg,8186
|
|
70
71
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
71
|
-
sonolus/script/internal/constant.py,sha256=
|
|
72
|
+
sonolus/script/internal/constant.py,sha256=k4kIBoy-c74UbfUR7GKzyTT6syhE-lSRJfw0nGuc8ZY,3904
|
|
72
73
|
sonolus/script/internal/context.py,sha256=xW7yFWr13yf77Uk4C_F7v9Kp4ZP1o30uZuBkvK1KyLA,14157
|
|
73
74
|
sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
|
|
74
75
|
sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
|
|
75
76
|
sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
|
|
76
|
-
sonolus/script/internal/generic.py,sha256=
|
|
77
|
-
sonolus/script/internal/impl.py,sha256=
|
|
77
|
+
sonolus/script/internal/generic.py,sha256=F0-cCiRNGTaUJvYlpmkiOsU3Xge_XjoBpBwBhH_qS_s,7577
|
|
78
|
+
sonolus/script/internal/impl.py,sha256=0uIZ9UtZYqKbsvp2XKo-aVIzkiA8MRbiPIOt0OMmxVo,3018
|
|
78
79
|
sonolus/script/internal/introspection.py,sha256=SL2zaYjid0kkcj6ZbFLIwhgh7WKZBaAHhlbSJGr6PWs,974
|
|
79
80
|
sonolus/script/internal/math_impls.py,sha256=Xk7tLMnV2npzPJWtHlspONQHt09Gh2YLdHhAjx4jkdE,2320
|
|
80
81
|
sonolus/script/internal/native.py,sha256=XKlNnWSJ-lxbwVGWhGj_CSSoWsVN18imqT5sAsDJT1w,1551
|
|
@@ -82,9 +83,9 @@ sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z
|
|
|
82
83
|
sonolus/script/internal/range.py,sha256=lrTanQFHU7RuQxSSPwDdoC30Y8FnHGxcP1Ahditu3zU,2297
|
|
83
84
|
sonolus/script/internal/transient.py,sha256=d6iYhM9f6DPUX5nkYQGm-x0b9XEfZUmB4AtUNnyhixo,1636
|
|
84
85
|
sonolus/script/internal/tuple_impl.py,sha256=vjXmScLVdeTkDn3t9fgIRqtW31iwngnaP2rmA6nlsLw,3431
|
|
85
|
-
sonolus/script/internal/value.py,sha256=
|
|
86
|
-
sonolus_py-0.
|
|
87
|
-
sonolus_py-0.
|
|
88
|
-
sonolus_py-0.
|
|
89
|
-
sonolus_py-0.
|
|
90
|
-
sonolus_py-0.
|
|
86
|
+
sonolus/script/internal/value.py,sha256=gQVNQD_xGpgrN4-UXFDmWRZCJCe8wPZ_wYv4QoPRJkM,5379
|
|
87
|
+
sonolus_py-0.3.1.dist-info/METADATA,sha256=txzVSPgkG_eIzWqlChZcQ5RTrh6l6Juk_m8-UmFgOG8,302
|
|
88
|
+
sonolus_py-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
89
|
+
sonolus_py-0.3.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
|
|
90
|
+
sonolus_py-0.3.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
|
|
91
|
+
sonolus_py-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|