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 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)
@@ -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 when this entity is first in the spawn queue.
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 set_")
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
- else:
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
- base = ctx().rom[tuple(self._to_list_())] if isinstance(self._value, list) else self._value
199
- place = BlockPlace(
200
- block=base.block,
201
- index=(Num(base.index) + index * self.element_type()._size_()).index(),
202
- offset=base.offset,
203
- )
204
- return self.element_type()._from_place_(place)._get_()
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
- base = self._value
214
- place = (
215
- base.add_offset(int(index._as_py_()) * self.element_type()._size_())
216
- if index._is_py_()
217
- else BlockPlace(
218
- block=base.block,
219
- index=(Num(base.index) + index * self.element_type()._size_()).index(),
220
- offset=base.offset,
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
- dst = self.element_type()._from_place_(place)
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
- return f"{type(self).__name__}({", ".join(str(self[i]) for i in range(self.size()))})"
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
- return f"{type(self).__name__}({", ".join(repr(self[i]) for i in range(self.size()))})"
297
+ else:
298
+ return f"{type(self).__name__}({", ".join(repr(self[i]) for i in range(self.size()))})"
@@ -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 judgment of the hit.
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
- ```Bucket(id: int)```
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 MiscConstantValue(ConstantValue):
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
 
@@ -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 MiscConstantValue
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 MiscConstantValue.of(Num)
69
- return MiscConstantValue.of(value)
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 MiscConstantValue.of(value)
87
+ return BasicConstantValue.of(value)
87
88
  case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
88
- return MiscConstantValue.of(other_type)
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 MiscConstantValue.of(comptime_value)
93
+ return BasicConstantValue.of(comptime_value)
93
94
  case _:
94
95
  return None
@@ -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
- ctx().add_statements(self.data.write(value))
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()))
@@ -91,6 +91,7 @@ class _ParticleInfo:
91
91
 
92
92
 
93
93
  def particle(name: str) -> Any:
94
+ """Define a particle with the given name."""
94
95
  return _ParticleInfo(name)
95
96
 
96
97
 
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 set_")
217
+ raise TypeError("Record does not support _set_")
207
218
 
208
219
  def _copy_from_(self, value: Self):
209
220
  value = self._accept_(value)
@@ -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
@@ -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_()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Sonolus engine development in Python
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -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=ekkHSdgRubIYLSYFk0wTUuBvyf3TKdApM4AyR_koTQ8,10122
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=I-K9uT4l6S5w4toDplzG8v01w0KTM4TsZZPgiZgwOrA,40098
38
- sonolus/script/array.py,sha256=l-KdtHvxF4NpCSZAw4t7MG4N5aZ_gOTGkemDobC6mQY,9791
39
- sonolus/script/array_like.py,sha256=nCIt_TxV7Ck6ovbSXyViqUkxsO9BksFOjUhpwF0uuxA,8444
40
- sonolus/script/bucket.py,sha256=oaaB7OXtBWzr8sLzPOpm8-GMhs7FzEhYrI1uVcM0DHM,7453
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=FpMi-w_rdkozZSFxraNQ4IkOzQpTr4YAH3hbqMIFYy8,14612
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=K06ArT9tstRNdbuGIviDmDDWcK3-ieA53LHg0Xvizow,8304
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=7uhBwRSvA4tZ_JFjc3Y9n8C9AwQirX5E8MAA0QaHCak,10067
59
- sonolus/script/record.py,sha256=EV4wywagBl3RU40Bqo9DRdx7Ta8xBgwKtgKlDF02X0o,11332
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=hH6KSRQC8vV-Z10CRCrGewMYqQwUMH3mQIEmniuC2Zw,10760
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=bwh_9ikxuKyrO_m3_lTKIvZDze4j88Fz-_HT_jB657g,1032
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=XljC1bXpjmQsekFFDNbaCLoHLPXzDeE17QDHf-rY70Q,3835
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=YU1hUJoBcGc0OSrFStK5JI6CikOwSmd_IR20pCuT82k,7310
77
- sonolus/script/internal/impl.py,sha256=HKQVoHknw5t43Wmj_G1vFjGSmnFpOj1FUYnhbUM3rHc,2969
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=St7OqBX-Tad_qBfu6aWMFU4lDXRyFnvzzkb-yFmOzg0,5042
86
- sonolus_py-0.2.1.dist-info/METADATA,sha256=OZkz271-5AHDS0iRKllUD5f3Wb5FL9s7BQghhIhI6Nw,302
87
- sonolus_py-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
- sonolus_py-0.2.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
89
- sonolus_py-0.2.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
90
- sonolus_py-0.2.1.dist-info/RECORD,,
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,,