sonolus.py 0.2.0__py3-none-any.whl → 0.3.0__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.
- sonolus/backend/ops.py +6 -0
- sonolus/script/archetype.py +45 -7
- sonolus/script/array.py +58 -29
- sonolus/script/array_like.py +7 -2
- sonolus/script/bucket.py +5 -3
- sonolus/script/containers.py +166 -0
- sonolus/script/debug.py +20 -2
- sonolus/script/engine.py +2 -2
- sonolus/script/internal/constant.py +6 -4
- sonolus/script/internal/generic.py +7 -0
- sonolus/script/internal/impl.py +10 -9
- sonolus/script/internal/transient.py +3 -3
- sonolus/script/internal/value.py +40 -11
- sonolus/script/interval.py +3 -14
- sonolus/script/num.py +29 -16
- sonolus/script/options.py +18 -3
- sonolus/script/particle.py +1 -0
- sonolus/script/pointer.py +9 -1
- sonolus/script/quad.py +58 -4
- sonolus/script/record.py +15 -4
- sonolus/script/runtime.py +17 -6
- sonolus/script/stream.py +529 -0
- sonolus/script/text.py +9 -0
- sonolus/script/transform.py +3 -3
- sonolus/script/ui.py +13 -4
- sonolus/script/values.py +12 -1
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.3.0.dist-info}/METADATA +2 -2
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.3.0.dist-info}/RECORD +31 -30
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.3.0.dist-info}/WHEEL +0 -0
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.3.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.3.0.dist-info}/licenses/LICENSE +0 -0
sonolus/script/stream.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import cast, dataclass_transform
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.ir import IRExpr, IRInstr, IRPureInstr
|
|
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: _StreamBacking(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 Stream[T](Record, BackingValue):
|
|
154
|
+
"""Represents a stream.
|
|
155
|
+
|
|
156
|
+
Most users should use `@stream` to declare streams and stream groups rather than using this class directly.
|
|
157
|
+
|
|
158
|
+
If used directly, it is important that streams do not overlap. No other streams should have an offset in
|
|
159
|
+
`range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
|
|
160
|
+
with each other.
|
|
161
|
+
|
|
162
|
+
Usage:
|
|
163
|
+
Declaring a stream:
|
|
164
|
+
```python
|
|
165
|
+
@streams
|
|
166
|
+
class Streams:
|
|
167
|
+
my_stream_1: Stream[Num] # A stream of Num values
|
|
168
|
+
my_stream_2: Stream[Vec2] # A stream of Vec2 values
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Directly creating a stream (advanced usage):
|
|
172
|
+
```python
|
|
173
|
+
stream = Stream[Num](offset=0)
|
|
174
|
+
```
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
offset: int
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def element_type(cls) -> type[T] | type[Value]:
|
|
181
|
+
"""Return the type of elements in this array type."""
|
|
182
|
+
return cls.type_var_value(T)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
@meta_fn
|
|
186
|
+
def backing_size(cls) -> int:
|
|
187
|
+
"""Return the number of underlying single-value streams backing this stream."""
|
|
188
|
+
return max(1, sizeof(cls.element_type()))
|
|
189
|
+
|
|
190
|
+
def __contains__(self, item: int | float) -> bool:
|
|
191
|
+
"""Check if the stream contains the key."""
|
|
192
|
+
_check_can_read_stream()
|
|
193
|
+
return _stream_has(self.offset, item)
|
|
194
|
+
|
|
195
|
+
@meta_fn
|
|
196
|
+
def __getitem__(self, key: int | float) -> T:
|
|
197
|
+
"""Get the value corresponding to the key.
|
|
198
|
+
|
|
199
|
+
If the key is not in the stream, interpolates linearly between surrounding values.
|
|
200
|
+
If the stream is empty, returns the zero value of the element type.
|
|
201
|
+
"""
|
|
202
|
+
# This is allowed in Play mode since a stream value may be accessed just to write to it without reading.
|
|
203
|
+
_check_can_read_or_write_stream()
|
|
204
|
+
return self.element_type()._from_backing_source_(lambda offset: _StreamBacking(self.offset + Num(offset), key))
|
|
205
|
+
|
|
206
|
+
@meta_fn
|
|
207
|
+
def __setitem__(self, key: int | float, value: T) -> None:
|
|
208
|
+
"""Set the value corresponding to the key."""
|
|
209
|
+
_check_can_write_stream()
|
|
210
|
+
if not self.element_type()._accepts_(value):
|
|
211
|
+
raise TypeError(f"Cannot set value of type {type(value)} to stream of type {self.element_type()}.")
|
|
212
|
+
if self.element_type()._size_() == 0:
|
|
213
|
+
# We still need to store something to preserve the key, so this is a special case.
|
|
214
|
+
_stream_set(self.offset, key, 0)
|
|
215
|
+
else:
|
|
216
|
+
for i, v in enumerate(value._to_list_()):
|
|
217
|
+
_stream_set(self.offset + i, key, Num(v))
|
|
218
|
+
|
|
219
|
+
def next_key(self, key: int | float) -> int:
|
|
220
|
+
"""Get the next key, or the key unchanged if it is the last key or the stream is empty.
|
|
221
|
+
|
|
222
|
+
If the key is in the stream and there is a next key, returns the next key.
|
|
223
|
+
"""
|
|
224
|
+
_check_can_read_stream()
|
|
225
|
+
return _stream_get_next_key(self.offset, key)
|
|
226
|
+
|
|
227
|
+
def previous_key(self, key: int | float) -> int:
|
|
228
|
+
"""Get the previous key, or the key unchanged if it is the first key or the stream is empty.
|
|
229
|
+
|
|
230
|
+
If the key is in the stream and there is a previous key, returns the previous key.
|
|
231
|
+
"""
|
|
232
|
+
_check_can_read_stream()
|
|
233
|
+
return _stream_get_previous_key(self.offset, key)
|
|
234
|
+
|
|
235
|
+
def has_next_key(self, key: int | float) -> bool:
|
|
236
|
+
"""Check if there is a next key after the given key in the stream."""
|
|
237
|
+
_check_can_read_stream()
|
|
238
|
+
next_key = self.next_key(key)
|
|
239
|
+
return next_key > key
|
|
240
|
+
|
|
241
|
+
def has_previous_key(self, key: int | float) -> bool:
|
|
242
|
+
"""Check if there is a previous key before the given key in the stream."""
|
|
243
|
+
_check_can_read_stream()
|
|
244
|
+
previous_key = self.previous_key(key)
|
|
245
|
+
return previous_key < key
|
|
246
|
+
|
|
247
|
+
def next_key_inclusive(self, key: int | float) -> int:
|
|
248
|
+
"""Like `next_key`, but returns the key itself if it is in the stream."""
|
|
249
|
+
_check_can_read_stream()
|
|
250
|
+
return key if key in self else self.next_key(key)
|
|
251
|
+
|
|
252
|
+
def previous_key_inclusive(self, key: int | float) -> int:
|
|
253
|
+
"""Like `previous_key`, but returns the key itself if it is in the stream."""
|
|
254
|
+
_check_can_read_stream()
|
|
255
|
+
return key if key in self else self.previous_key(key)
|
|
256
|
+
|
|
257
|
+
def get_next(self, key: int | float) -> T:
|
|
258
|
+
"""Get the value corresponding to the next key.
|
|
259
|
+
|
|
260
|
+
If there is no next key, returns the value at the given key. Equivalent to `self[self.next_key(key)]`.
|
|
261
|
+
"""
|
|
262
|
+
_check_can_read_stream()
|
|
263
|
+
return self[self.next_key(key)]
|
|
264
|
+
|
|
265
|
+
def get_previous(self, key: int | float) -> T:
|
|
266
|
+
"""Get the value corresponding to the previous key.
|
|
267
|
+
|
|
268
|
+
If there is no previous key, returns the value at the given key. Equivalent to `self[self.previous_key(key)]`.
|
|
269
|
+
"""
|
|
270
|
+
_check_can_read_stream()
|
|
271
|
+
return self[self.previous_key(key)]
|
|
272
|
+
|
|
273
|
+
def get_next_inclusive(self, key: int | float) -> T:
|
|
274
|
+
"""Get the value corresponding to the next key, or the value at the given key if it is in the stream.
|
|
275
|
+
|
|
276
|
+
Equivalent to `self[self.next_key_inclusive(key)]`.
|
|
277
|
+
"""
|
|
278
|
+
_check_can_read_stream()
|
|
279
|
+
return self[self.next_key_inclusive(key)]
|
|
280
|
+
|
|
281
|
+
def iter_items_from(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
282
|
+
"""Iterate over the items in the stream in ascending order starting from the given key.
|
|
283
|
+
|
|
284
|
+
If the key is in the stream, it will be included in the iteration.
|
|
285
|
+
|
|
286
|
+
Usage:
|
|
287
|
+
```python
|
|
288
|
+
stream = ...
|
|
289
|
+
for key, value in stream.iter_items_from(0):
|
|
290
|
+
do_something(key, value)
|
|
291
|
+
```
|
|
292
|
+
"""
|
|
293
|
+
_check_can_read_stream()
|
|
294
|
+
return _StreamAscIterator(self, self.next_key_inclusive(start))
|
|
295
|
+
|
|
296
|
+
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
297
|
+
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
298
|
+
|
|
299
|
+
If the key is in the stream, it will be included in the iteration.
|
|
300
|
+
|
|
301
|
+
Usage:
|
|
302
|
+
```python
|
|
303
|
+
stream = ...
|
|
304
|
+
for key, value in stream.iter_items_from_desc(0):
|
|
305
|
+
do_something(key, value)
|
|
306
|
+
```
|
|
307
|
+
"""
|
|
308
|
+
_check_can_read_stream()
|
|
309
|
+
return _StreamDescIterator(self, self.previous_key_inclusive(start))
|
|
310
|
+
|
|
311
|
+
def iter_keys_from(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
312
|
+
"""Iterate over the keys in the stream in ascending order starting from the given key.
|
|
313
|
+
|
|
314
|
+
If the key is in the stream, it will be included in the iteration.
|
|
315
|
+
|
|
316
|
+
Usage:
|
|
317
|
+
```python
|
|
318
|
+
stream = ...
|
|
319
|
+
for key in stream.iter_keys_from(0):
|
|
320
|
+
do_something(key)
|
|
321
|
+
```
|
|
322
|
+
"""
|
|
323
|
+
_check_can_read_stream()
|
|
324
|
+
return _StreamAscKeyIterator(self, self.next_key_inclusive(start))
|
|
325
|
+
|
|
326
|
+
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
327
|
+
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
328
|
+
|
|
329
|
+
If the key is in the stream, it will be included in the iteration.
|
|
330
|
+
|
|
331
|
+
Usage:
|
|
332
|
+
```python
|
|
333
|
+
stream = ...
|
|
334
|
+
for key in stream.iter_keys_from_desc(0):
|
|
335
|
+
do_something(key)
|
|
336
|
+
```
|
|
337
|
+
"""
|
|
338
|
+
_check_can_read_stream()
|
|
339
|
+
return _StreamDescKeyIterator(self, self.previous_key_inclusive(start))
|
|
340
|
+
|
|
341
|
+
def iter_values_from(self, start: int | float, /) -> SonolusIterator[T]:
|
|
342
|
+
"""Iterate over the values in the stream in ascending order starting from the given key.
|
|
343
|
+
|
|
344
|
+
If the key is in the stream, it will be included in the iteration.
|
|
345
|
+
|
|
346
|
+
Usage:
|
|
347
|
+
```python
|
|
348
|
+
stream = ...
|
|
349
|
+
for value in stream.iter_values_from(0):
|
|
350
|
+
do_something(value)
|
|
351
|
+
```
|
|
352
|
+
"""
|
|
353
|
+
_check_can_read_stream()
|
|
354
|
+
return _StreamAscValueIterator(self, self.next_key_inclusive(start))
|
|
355
|
+
|
|
356
|
+
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
357
|
+
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
358
|
+
|
|
359
|
+
If the key is in the stream, it will be included in the iteration.
|
|
360
|
+
|
|
361
|
+
Usage:
|
|
362
|
+
```python
|
|
363
|
+
stream = ...
|
|
364
|
+
for value in stream.iter_values_from_desc(0):
|
|
365
|
+
do_something(value)
|
|
366
|
+
```
|
|
367
|
+
"""
|
|
368
|
+
_check_can_read_stream()
|
|
369
|
+
return _StreamDescValueIterator(self, self.previous_key_inclusive(start))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class StreamGroup[T, Size](Record):
|
|
373
|
+
"""Represents a group of streams.
|
|
374
|
+
|
|
375
|
+
Most users should use `@stream` to declare stream groups rather than using this class directly.
|
|
376
|
+
|
|
377
|
+
Usage:
|
|
378
|
+
Declaring a stream group:
|
|
379
|
+
```python
|
|
380
|
+
@streams
|
|
381
|
+
class Streams:
|
|
382
|
+
my_group_1: StreamGroup[Num, 10] # A group of 10 Num streams
|
|
383
|
+
my_group_2: StreamGroup[Vec2, 5] # A group of 5 Vec2 streams
|
|
384
|
+
```
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
offset: int
|
|
388
|
+
|
|
389
|
+
@classmethod
|
|
390
|
+
def size(cls) -> Size:
|
|
391
|
+
"""Return the size of the group."""
|
|
392
|
+
return cls.type_var_value(Size)
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def element_type(cls) -> type[T] | type[Value]:
|
|
396
|
+
"""Return the type of elements in this group."""
|
|
397
|
+
return cls.type_var_value(T)
|
|
398
|
+
|
|
399
|
+
@classmethod
|
|
400
|
+
@meta_fn
|
|
401
|
+
def backing_size(cls) -> int:
|
|
402
|
+
"""Return the number of underlying single-value streams backing this stream."""
|
|
403
|
+
return max(1, sizeof(cls.element_type())) * cls.size()
|
|
404
|
+
|
|
405
|
+
def __contains__(self, item: int) -> bool:
|
|
406
|
+
"""Check if the group contains the stream with the given index."""
|
|
407
|
+
_check_can_read_or_write_stream()
|
|
408
|
+
return 0 <= item < self.size()
|
|
409
|
+
|
|
410
|
+
def __getitem__(self, index: int) -> Stream[T]:
|
|
411
|
+
"""Get the stream at the given index."""
|
|
412
|
+
_check_can_read_or_write_stream()
|
|
413
|
+
assert index in self
|
|
414
|
+
# Size 0 elements still need 1 stream to preserve the key.
|
|
415
|
+
return Stream[self.type_var_value(T)](max(1, sizeof(self.element_type())) * index + self.offset)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
419
|
+
stream: Stream[T]
|
|
420
|
+
current_key: int | float
|
|
421
|
+
|
|
422
|
+
def has_next(self) -> bool:
|
|
423
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
424
|
+
|
|
425
|
+
def get(self) -> tuple[int | float, T]:
|
|
426
|
+
return self.current_key, self.stream[self.current_key]
|
|
427
|
+
|
|
428
|
+
def advance(self):
|
|
429
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
433
|
+
stream: Stream[T]
|
|
434
|
+
current_key: int | float
|
|
435
|
+
|
|
436
|
+
def has_next(self) -> bool:
|
|
437
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
438
|
+
|
|
439
|
+
def get(self) -> tuple[int | float, T]:
|
|
440
|
+
return self.current_key, self.stream[self.current_key]
|
|
441
|
+
|
|
442
|
+
def advance(self):
|
|
443
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
447
|
+
stream: Stream[T]
|
|
448
|
+
current_key: int | float
|
|
449
|
+
|
|
450
|
+
def has_next(self) -> bool:
|
|
451
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
452
|
+
|
|
453
|
+
def get(self) -> int | float:
|
|
454
|
+
return self.current_key
|
|
455
|
+
|
|
456
|
+
def advance(self):
|
|
457
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
461
|
+
stream: Stream[T]
|
|
462
|
+
current_key: int | float
|
|
463
|
+
|
|
464
|
+
def has_next(self) -> bool:
|
|
465
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
466
|
+
|
|
467
|
+
def get(self) -> int | float:
|
|
468
|
+
return self.current_key
|
|
469
|
+
|
|
470
|
+
def advance(self):
|
|
471
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
475
|
+
stream: Stream[T]
|
|
476
|
+
current_key: int | float
|
|
477
|
+
|
|
478
|
+
def has_next(self) -> bool:
|
|
479
|
+
return self.stream.next_key(self.current_key) > self.current_key
|
|
480
|
+
|
|
481
|
+
def get(self) -> T:
|
|
482
|
+
return self.stream[self.current_key]
|
|
483
|
+
|
|
484
|
+
def advance(self):
|
|
485
|
+
self.current_key = self.stream.next_key(self.current_key)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
489
|
+
stream: Stream[T]
|
|
490
|
+
current_key: int | float
|
|
491
|
+
|
|
492
|
+
def has_next(self) -> bool:
|
|
493
|
+
return self.stream.previous_key(self.current_key) < self.current_key
|
|
494
|
+
|
|
495
|
+
def get(self) -> T:
|
|
496
|
+
return self.stream[self.current_key]
|
|
497
|
+
|
|
498
|
+
def advance(self):
|
|
499
|
+
self.current_key = self.stream.previous_key(self.current_key)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@native_function(Op.StreamGetNextKey)
|
|
503
|
+
def _stream_get_next_key(stream_id: int, key: int | float) -> int:
|
|
504
|
+
"""Get the next key in the stream, or the key unchanged if it is the last key or the stream is empty."""
|
|
505
|
+
raise NotImplementedError
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@native_function(Op.StreamGetPreviousKey)
|
|
509
|
+
def _stream_get_previous_key(stream_id: int, key: int | float) -> int:
|
|
510
|
+
"""Get the previous key in the stream, or the key unchanged if it is the first key or the stream is empty."""
|
|
511
|
+
raise NotImplementedError
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
@native_function(Op.StreamGetValue)
|
|
515
|
+
def _stream_get_value(stream_id: int, key: int | float) -> float:
|
|
516
|
+
"""Get the value of the key in the stream."""
|
|
517
|
+
raise NotImplementedError
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@native_function(Op.StreamHas)
|
|
521
|
+
def _stream_has(stream_id: int, key: int | float) -> bool:
|
|
522
|
+
"""Check if the stream has the key."""
|
|
523
|
+
raise NotImplementedError
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@native_function(Op.StreamSet)
|
|
527
|
+
def _stream_set(stream_id: int, key: int | float, value: float) -> None:
|
|
528
|
+
"""Set the value of the key in the stream."""
|
|
529
|
+
raise NotImplementedError
|
sonolus/script/text.py
CHANGED
|
@@ -157,6 +157,15 @@ class StandardText(StrEnum):
|
|
|
157
157
|
SIMLINE_COLOR = "#SIMLINE_COLOR"
|
|
158
158
|
SIMLINE_ALPHA = "#SIMLINE_ALPHA"
|
|
159
159
|
SIMLINE_ANIMATION = "#SIMLINE_ANIMATION"
|
|
160
|
+
PREVIEW_SCALE_VERTICAL = "#PREVIEW_SCALE_VERTICAL"
|
|
161
|
+
PREVIEW_SCALE_HORIZONTAL = "#PREVIEW_SCALE_HORIZONTAL"
|
|
162
|
+
PREVIEW_TIME = "#PREVIEW_TIME"
|
|
163
|
+
PREVIEW_SCORE = "#PREVIEW_SCORE"
|
|
164
|
+
PREVIEW_BPM = "#PREVIEW_BPM"
|
|
165
|
+
PREVIEW_TIMESCALE = "#PREVIEW_TIMESCALE"
|
|
166
|
+
PREVIEW_BEAT = "#PREVIEW_BEAT"
|
|
167
|
+
PREVIEW_MEASURE = "#PREVIEW_MEASURE"
|
|
168
|
+
PREVIEW_COMBO = "#PREVIEW_COMBO"
|
|
160
169
|
NONE = "#NONE"
|
|
161
170
|
ANY = "#ANY"
|
|
162
171
|
ALL = "#ALL"
|
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/ui.py
CHANGED
|
@@ -20,9 +20,14 @@ class UiMetric(StrEnum):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class UiJudgmentErrorStyle(StrEnum):
|
|
23
|
-
"""The style of the judgment error.
|
|
23
|
+
"""The style of the judgment error.
|
|
24
|
+
|
|
25
|
+
The name of each member refers to what's used for positive (late) judgment errors.
|
|
26
|
+
"""
|
|
24
27
|
|
|
25
28
|
NONE = "none"
|
|
29
|
+
LATE = "late"
|
|
30
|
+
EARLY = "early" # Not really useful
|
|
26
31
|
PLUS = "plus"
|
|
27
32
|
MINUS = "minus"
|
|
28
33
|
ARROW_UP = "arrowUp"
|
|
@@ -38,9 +43,13 @@ class UiJudgmentErrorStyle(StrEnum):
|
|
|
38
43
|
class UiJudgmentErrorPlacement(StrEnum):
|
|
39
44
|
"""The placement of the judgment error."""
|
|
40
45
|
|
|
41
|
-
BOTH = "both"
|
|
42
46
|
LEFT = "left"
|
|
43
47
|
RIGHT = "right"
|
|
48
|
+
LEFT_RIGHT = "leftRight"
|
|
49
|
+
TOP = "top"
|
|
50
|
+
BOTTOM = "bottom"
|
|
51
|
+
TOP_BOTTOM = "topBottom"
|
|
52
|
+
CENTER = "center"
|
|
44
53
|
|
|
45
54
|
|
|
46
55
|
class EaseType(StrEnum):
|
|
@@ -193,8 +202,8 @@ class UiConfig:
|
|
|
193
202
|
scale=UiAnimationTween(1.2, 1, 0.2, EaseType.IN_CUBIC), alpha=UiAnimationTween(1, 1, 0, EaseType.NONE)
|
|
194
203
|
)
|
|
195
204
|
)
|
|
196
|
-
judgment_error_style: UiJudgmentErrorStyle = UiJudgmentErrorStyle.
|
|
197
|
-
judgment_error_placement: UiJudgmentErrorPlacement = UiJudgmentErrorPlacement.
|
|
205
|
+
judgment_error_style: UiJudgmentErrorStyle = UiJudgmentErrorStyle.LATE
|
|
206
|
+
judgment_error_placement: UiJudgmentErrorPlacement = UiJudgmentErrorPlacement.TOP
|
|
198
207
|
judgment_error_min: float = 0.0
|
|
199
208
|
|
|
200
209
|
def to_dict(self):
|
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
|
|
@@ -33,7 +34,17 @@ def copy[T](value: T) -> T:
|
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def swap[T](a: T, b: T):
|
|
36
|
-
"""Swap the values of the given
|
|
37
|
+
"""Swap the values of the two given arguments."""
|
|
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,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sonolus.py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Sonolus engine development in Python
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
|
|
9
9
|
# Sonolus.py
|
|
10
|
-
Sonolus engine development in Python.
|
|
10
|
+
Sonolus engine development in Python. See [docs](https://sonolus.py.qwewqa.xyz) for more information.
|