sonolus.py 0.2.0__py3-none-any.whl → 0.2.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/script/archetype.py +44 -6
- sonolus/script/array.py +3 -3
- sonolus/script/array_like.py +6 -2
- sonolus/script/containers.py +166 -0
- sonolus/script/debug.py +20 -2
- sonolus/script/engine.py +2 -2
- sonolus/script/internal/constant.py +3 -3
- sonolus/script/internal/transient.py +3 -3
- sonolus/script/internal/value.py +27 -11
- sonolus/script/interval.py +3 -14
- sonolus/script/num.py +26 -15
- sonolus/script/options.py +18 -3
- sonolus/script/pointer.py +9 -1
- sonolus/script/quad.py +55 -1
- sonolus/script/record.py +3 -3
- sonolus/script/runtime.py +17 -6
- sonolus/script/text.py +9 -0
- sonolus/script/ui.py +13 -4
- sonolus/script/values.py +1 -1
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.2.1.dist-info}/METADATA +2 -2
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.2.1.dist-info}/RECORD +24 -24
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.2.1.dist-info}/WHEEL +0 -0
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.2.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.2.0.dist-info → sonolus_py-0.2.1.dist-info}/licenses/LICENSE +0 -0
sonolus/script/archetype.py
CHANGED
|
@@ -8,10 +8,9 @@ from enum import Enum, StrEnum
|
|
|
8
8
|
from types import FunctionType
|
|
9
9
|
from typing import Annotated, Any, ClassVar, Self, TypedDict, get_origin
|
|
10
10
|
|
|
11
|
-
from sonolus.backend.ir import IRConst, IRInstr
|
|
11
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr, IRStmt
|
|
12
12
|
from sonolus.backend.mode import Mode
|
|
13
13
|
from sonolus.backend.ops import Op
|
|
14
|
-
from sonolus.backend.place import BlockPlace
|
|
15
14
|
from sonolus.script.bucket import Bucket, Judgment
|
|
16
15
|
from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
|
|
17
16
|
from sonolus.script.internal.context import ctx
|
|
@@ -20,9 +19,9 @@ from sonolus.script.internal.generic import validate_concrete_type
|
|
|
20
19
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
21
20
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
22
21
|
from sonolus.script.internal.native import native_call
|
|
23
|
-
from sonolus.script.internal.value import Value
|
|
22
|
+
from sonolus.script.internal.value import BackingValue, DataValue, Value
|
|
24
23
|
from sonolus.script.num import Num
|
|
25
|
-
from sonolus.script.pointer import _deref
|
|
24
|
+
from sonolus.script.pointer import _backing_deref, _deref
|
|
26
25
|
from sonolus.script.record import Record
|
|
27
26
|
from sonolus.script.values import zeros
|
|
28
27
|
|
|
@@ -44,6 +43,17 @@ class _ArchetypeFieldInfo:
|
|
|
44
43
|
storage: _StorageType
|
|
45
44
|
|
|
46
45
|
|
|
46
|
+
class _ExportBackingValue(BackingValue):
|
|
47
|
+
def __init__(self, index: IRExpr):
|
|
48
|
+
self.index = index
|
|
49
|
+
|
|
50
|
+
def read(self) -> IRExpr:
|
|
51
|
+
raise NotImplementedError("Exported fields are write-only")
|
|
52
|
+
|
|
53
|
+
def write(self, value: IRExpr) -> IRStmt:
|
|
54
|
+
return IRInstr(Op.ExportValue, [self.index, value])
|
|
55
|
+
|
|
56
|
+
|
|
47
57
|
class _ArchetypeField(SonolusDescriptor):
|
|
48
58
|
def __init__(self, name: str, data_name: str, storage: _StorageType, offset: int, type_: type[Value]):
|
|
49
59
|
self.name = name
|
|
@@ -70,7 +80,20 @@ class _ArchetypeField(SonolusDescriptor):
|
|
|
70
80
|
case _ArchetypeLevelData(values=values):
|
|
71
81
|
result = values[self.name]
|
|
72
82
|
case _StorageType.EXPORTED:
|
|
73
|
-
|
|
83
|
+
match instance._data_:
|
|
84
|
+
case _ArchetypeSelfData():
|
|
85
|
+
|
|
86
|
+
def backing_source(i: IRExpr):
|
|
87
|
+
return _ExportBackingValue(IRPureInstr(Op.Add, [i, IRConst(self.offset)]))
|
|
88
|
+
|
|
89
|
+
result = _backing_deref(
|
|
90
|
+
backing_source,
|
|
91
|
+
self.type,
|
|
92
|
+
)
|
|
93
|
+
case _ArchetypeReferenceData():
|
|
94
|
+
raise RuntimeError("Exported fields of other entities are not accessible")
|
|
95
|
+
case _ArchetypeLevelData():
|
|
96
|
+
raise RuntimeError("Exported fields are not available in level data")
|
|
74
97
|
case _StorageType.MEMORY:
|
|
75
98
|
match instance._data_:
|
|
76
99
|
case _ArchetypeSelfData():
|
|
@@ -584,6 +607,7 @@ class _BaseArchetype:
|
|
|
584
607
|
cls._spawn_signature_ = inspect.Signature(
|
|
585
608
|
[inspect.Parameter(name, inspect.Parameter.POSITIONAL_OR_KEYWORD) for name in cls._memory_fields_]
|
|
586
609
|
)
|
|
610
|
+
cls._post_init_fields()
|
|
587
611
|
|
|
588
612
|
@property
|
|
589
613
|
@abstractmethod
|
|
@@ -609,6 +633,10 @@ class _BaseArchetype:
|
|
|
609
633
|
case _:
|
|
610
634
|
raise RuntimeError("Invalid entity data")
|
|
611
635
|
|
|
636
|
+
@classmethod
|
|
637
|
+
def _post_init_fields(cls):
|
|
638
|
+
pass
|
|
639
|
+
|
|
612
640
|
|
|
613
641
|
class PlayArchetype(_BaseArchetype):
|
|
614
642
|
"""Base class for play mode archetypes.
|
|
@@ -885,6 +913,11 @@ class WatchArchetype(_BaseArchetype):
|
|
|
885
913
|
case _:
|
|
886
914
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
887
915
|
|
|
916
|
+
@classmethod
|
|
917
|
+
def _post_init_fields(cls):
|
|
918
|
+
if cls._exported_fields_:
|
|
919
|
+
raise RuntimeError("Watch archetypes cannot have exported fields")
|
|
920
|
+
|
|
888
921
|
|
|
889
922
|
class PreviewArchetype(_BaseArchetype):
|
|
890
923
|
"""Base class for preview mode archetypes.
|
|
@@ -934,6 +967,11 @@ class PreviewArchetype(_BaseArchetype):
|
|
|
934
967
|
"""The index of this entity."""
|
|
935
968
|
return self._info.index
|
|
936
969
|
|
|
970
|
+
@classmethod
|
|
971
|
+
def _post_init_fields(cls):
|
|
972
|
+
if cls._exported_fields_:
|
|
973
|
+
raise RuntimeError("Preview archetypes cannot have exported fields")
|
|
974
|
+
|
|
937
975
|
|
|
938
976
|
@meta_fn
|
|
939
977
|
def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
|
|
@@ -1066,7 +1104,7 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1066
1104
|
"""Check if entity at the index is precisely of the archetype."""
|
|
1067
1105
|
return self.index >= 0 and self.archetype().is_at(self.index)
|
|
1068
1106
|
|
|
1069
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
1107
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
1070
1108
|
ref = getattr(self, "_ref_", None)
|
|
1071
1109
|
if ref is None:
|
|
1072
1110
|
return [self.index]
|
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 Value
|
|
15
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
16
16
|
from sonolus.script.num import Num
|
|
17
17
|
|
|
18
18
|
|
|
@@ -108,11 +108,11 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
108
108
|
return self
|
|
109
109
|
|
|
110
110
|
@classmethod
|
|
111
|
-
def _from_list_(cls, values: Iterable[
|
|
111
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
112
112
|
iterator = iter(values)
|
|
113
113
|
return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
|
|
114
114
|
|
|
115
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
115
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
116
116
|
match self._value:
|
|
117
117
|
case list():
|
|
118
118
|
return [entry for value in self._value for entry in value._to_list_(level_refs)]
|
sonolus/script/array_like.py
CHANGED
|
@@ -158,10 +158,14 @@ class ArrayLike[T](Sequence, ABC):
|
|
|
158
158
|
return min_index
|
|
159
159
|
|
|
160
160
|
def _max_(self, key: Callable[T, Any] | None = None) -> T:
|
|
161
|
-
|
|
161
|
+
index = self.index_of_max(key=key)
|
|
162
|
+
assert index != -1
|
|
163
|
+
return self[index]
|
|
162
164
|
|
|
163
165
|
def _min_(self, key: Callable[T, Any] | None = None) -> T:
|
|
164
|
-
|
|
166
|
+
index = self.index_of_min(key=key)
|
|
167
|
+
assert index != -1
|
|
168
|
+
return self[index]
|
|
165
169
|
|
|
166
170
|
def swap(self, i: Num, j: Num, /):
|
|
167
171
|
"""Swap the values at the given indices.
|
sonolus/script/containers.py
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from sonolus.backend.visitor import compile_and_call
|
|
3
4
|
from sonolus.script.array import Array
|
|
4
5
|
from sonolus.script.array_like import ArrayLike
|
|
5
6
|
from sonolus.script.debug import error
|
|
7
|
+
from sonolus.script.internal.context import ctx
|
|
8
|
+
from sonolus.script.internal.impl import meta_fn
|
|
6
9
|
from sonolus.script.iterator import SonolusIterator
|
|
10
|
+
from sonolus.script.num import Num
|
|
11
|
+
from sonolus.script.pointer import _deref
|
|
7
12
|
from sonolus.script.record import Record
|
|
8
13
|
from sonolus.script.values import alloc, copy
|
|
9
14
|
|
|
10
15
|
|
|
16
|
+
class Box[T](Record):
|
|
17
|
+
"""A box that contains a value.
|
|
18
|
+
|
|
19
|
+
This can be helpful for generic code that can handle both Num and non-Num types.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
```python
|
|
23
|
+
Box[T](value: T)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
```python
|
|
28
|
+
box = Box(1)
|
|
29
|
+
box = Box[int](2)
|
|
30
|
+
|
|
31
|
+
x: T = ...
|
|
32
|
+
y: T = ...
|
|
33
|
+
box = Box(x)
|
|
34
|
+
box.value = y # Works regardless of whether x is a Num or not
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
value: T
|
|
39
|
+
"""The value contained in the box."""
|
|
40
|
+
|
|
41
|
+
|
|
11
42
|
class Pair[T, U](Record):
|
|
12
43
|
"""A generic pair of values.
|
|
13
44
|
|
|
@@ -129,6 +160,17 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
129
160
|
self._array[self._size] = value
|
|
130
161
|
self._size += 1
|
|
131
162
|
|
|
163
|
+
def append_unchecked(self, value: T):
|
|
164
|
+
"""Append the given value to the end of the array without checking the capacity.
|
|
165
|
+
|
|
166
|
+
Use with caution as this may cause hard to debug issues if the array is full.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
value: The value to append.
|
|
170
|
+
"""
|
|
171
|
+
self._array[self._size] = value
|
|
172
|
+
self._size += 1
|
|
173
|
+
|
|
132
174
|
def extend(self, values: ArrayLike[T]):
|
|
133
175
|
"""Appends copies of the values in the given array to the end of the array.
|
|
134
176
|
|
|
@@ -258,6 +300,130 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
258
300
|
raise TypeError("unhashable type: 'VarArray'")
|
|
259
301
|
|
|
260
302
|
|
|
303
|
+
class ArrayPointer[T](Record, ArrayLike[T]):
|
|
304
|
+
"""An array defined by a size and pointer to the first element.
|
|
305
|
+
|
|
306
|
+
This is intended to be created internally and improper use may result in hard to debug issues.
|
|
307
|
+
|
|
308
|
+
Usage:
|
|
309
|
+
```python
|
|
310
|
+
ArrayPointer[T](size: int, block: int, offset: int)
|
|
311
|
+
```
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
size: int
|
|
315
|
+
block: int
|
|
316
|
+
offset: int
|
|
317
|
+
|
|
318
|
+
def __len__(self) -> int:
|
|
319
|
+
"""Return the number of elements in the array."""
|
|
320
|
+
return self.size
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def element_type(cls) -> type[T]:
|
|
324
|
+
"""Return the type of the elements in the array."""
|
|
325
|
+
return cls.type_var_value(T)
|
|
326
|
+
|
|
327
|
+
def _check_index(self, index: int):
|
|
328
|
+
assert 0 <= index < self.size
|
|
329
|
+
|
|
330
|
+
@meta_fn
|
|
331
|
+
def _get_item(self, item: int) -> T:
|
|
332
|
+
if not ctx():
|
|
333
|
+
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
334
|
+
return _deref(
|
|
335
|
+
self.block,
|
|
336
|
+
self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
|
|
337
|
+
self.element_type(),
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@meta_fn
|
|
341
|
+
def __getitem__(self, item: int) -> T:
|
|
342
|
+
compile_and_call(self._check_index, item)
|
|
343
|
+
return self._get_item(item)._get_()
|
|
344
|
+
|
|
345
|
+
@meta_fn
|
|
346
|
+
def __setitem__(self, key: int, value: T):
|
|
347
|
+
compile_and_call(self._check_index, key)
|
|
348
|
+
dst = self._get_item(key)
|
|
349
|
+
if self.element_type()._is_value_type_():
|
|
350
|
+
dst._set_(value)
|
|
351
|
+
else:
|
|
352
|
+
dst._copy_from__(value)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class ArraySet[T, Capacity](Record):
|
|
356
|
+
"""A set implemented as an array with a fixed maximum capacity.
|
|
357
|
+
|
|
358
|
+
Usage:
|
|
359
|
+
```python
|
|
360
|
+
ArraySet[T, Capacity].new() # Create a new empty set
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
```python
|
|
365
|
+
s = ArraySet[int, 10].new()
|
|
366
|
+
s.add(1)
|
|
367
|
+
s.add(2)
|
|
368
|
+
assert 1 in s
|
|
369
|
+
assert 3 not in s
|
|
370
|
+
s.remove(1)
|
|
371
|
+
assert 1 not in s
|
|
372
|
+
```
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
_values: VarArray[T, Capacity]
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def new(cls):
|
|
379
|
+
"""Create a new empty set."""
|
|
380
|
+
element_type = cls.type_var_value(T)
|
|
381
|
+
capacity = cls.type_var_value(Capacity)
|
|
382
|
+
return cls(VarArray[element_type, capacity].new())
|
|
383
|
+
|
|
384
|
+
def __len__(self):
|
|
385
|
+
"""Return the number of elements in the set."""
|
|
386
|
+
return len(self._values)
|
|
387
|
+
|
|
388
|
+
def __contains__(self, value):
|
|
389
|
+
"""Return whether the given value is present in the set."""
|
|
390
|
+
return value in self._values
|
|
391
|
+
|
|
392
|
+
def __iter__(self):
|
|
393
|
+
"""Return an iterator over the values in the set."""
|
|
394
|
+
return self._values.__iter__()
|
|
395
|
+
|
|
396
|
+
def add(self, value: T) -> bool:
|
|
397
|
+
"""Add a copy of the given value to the set.
|
|
398
|
+
|
|
399
|
+
This has no effect and returns False if the value is already present or if the set is full.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
value: The value to add.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
True if the value was added, False otherwise.
|
|
406
|
+
"""
|
|
407
|
+
return self._values.set_add(value)
|
|
408
|
+
|
|
409
|
+
def remove(self, value: T) -> bool:
|
|
410
|
+
"""Remove the given value from the set.
|
|
411
|
+
|
|
412
|
+
This has no effect and returns False if the value is not present.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
value: The value to remove.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
True if the value was removed, False otherwise.
|
|
419
|
+
"""
|
|
420
|
+
return self._values.set_remove(value)
|
|
421
|
+
|
|
422
|
+
def clear(self):
|
|
423
|
+
"""Clear the set, removing all elements."""
|
|
424
|
+
self._values.clear()
|
|
425
|
+
|
|
426
|
+
|
|
261
427
|
class _ArrayMapEntry[K, V](Record):
|
|
262
428
|
key: K
|
|
263
429
|
value: V
|
sonolus/script/debug.py
CHANGED
|
@@ -16,8 +16,13 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@meta_fn
|
|
19
|
-
def error(message: str | None = None) ->
|
|
20
|
-
|
|
19
|
+
def error(message: str | None = None) -> Never:
|
|
20
|
+
"""Raise an error.
|
|
21
|
+
|
|
22
|
+
This function is used to raise an error during runtime.
|
|
23
|
+
When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
|
|
24
|
+
"""
|
|
25
|
+
message = validate_value(message)._as_py_() if message is not None else "Error"
|
|
21
26
|
if not isinstance(message, str):
|
|
22
27
|
raise ValueError("Expected a string")
|
|
23
28
|
if ctx():
|
|
@@ -28,6 +33,19 @@ def error(message: str | None = None) -> None:
|
|
|
28
33
|
raise RuntimeError(message)
|
|
29
34
|
|
|
30
35
|
|
|
36
|
+
@meta_fn
|
|
37
|
+
def static_error(message: str | None = None) -> Never:
|
|
38
|
+
"""Raise a static error.
|
|
39
|
+
|
|
40
|
+
This function is used to raise an error during compile-time if the compiler cannot guarantee that
|
|
41
|
+
this function will not be called during runtime.
|
|
42
|
+
"""
|
|
43
|
+
message = validate_value(message)._as_py_() if message is not None else "Error"
|
|
44
|
+
if not isinstance(message, str):
|
|
45
|
+
raise ValueError("Expected a string")
|
|
46
|
+
raise RuntimeError(message)
|
|
47
|
+
|
|
48
|
+
|
|
31
49
|
@meta_fn
|
|
32
50
|
def debug_log(value: Num):
|
|
33
51
|
"""Log a value in debug mode."""
|
sonolus/script/engine.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from os import PathLike
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Literal
|
|
8
8
|
|
|
9
9
|
from sonolus.build.collection import Asset, load_asset
|
|
10
10
|
from sonolus.script.archetype import PlayArchetype, PreviewArchetype, WatchArchetype, _BaseArchetype
|
|
@@ -81,7 +81,7 @@ class Engine:
|
|
|
81
81
|
meta: Additional metadata of the engine.
|
|
82
82
|
"""
|
|
83
83
|
|
|
84
|
-
version =
|
|
84
|
+
version: Literal[13] = 13
|
|
85
85
|
|
|
86
86
|
def __init__(
|
|
87
87
|
self,
|
|
@@ -3,7 +3,7 @@ from typing import Any, ClassVar, Self
|
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.place import BlockPlace
|
|
5
5
|
from sonolus.script.internal.impl import meta_fn
|
|
6
|
-
from sonolus.script.internal.value import Value
|
|
6
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class _Missing:
|
|
@@ -94,10 +94,10 @@ class ConstantValue(Value):
|
|
|
94
94
|
return self.value()
|
|
95
95
|
|
|
96
96
|
@classmethod
|
|
97
|
-
def _from_list_(cls, values: Iterable[
|
|
97
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
98
98
|
return cls()
|
|
99
99
|
|
|
100
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
100
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
101
101
|
return []
|
|
102
102
|
|
|
103
103
|
@classmethod
|
|
@@ -2,7 +2,7 @@ from collections.abc import Iterable
|
|
|
2
2
|
from typing import Any, Self
|
|
3
3
|
|
|
4
4
|
from sonolus.backend.place import BlockPlace
|
|
5
|
-
from sonolus.script.internal.value import Value
|
|
5
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TransientValue(Value):
|
|
@@ -23,10 +23,10 @@ class TransientValue(Value):
|
|
|
23
23
|
raise TypeError(f"{cls.__name__} cannot be dereferenced")
|
|
24
24
|
|
|
25
25
|
@classmethod
|
|
26
|
-
def _from_list_(cls, values: Iterable[
|
|
26
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
27
27
|
raise TypeError(f"{cls.__name__} cannot be constructed from list")
|
|
28
28
|
|
|
29
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
29
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
30
30
|
raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
|
|
31
31
|
|
|
32
32
|
@classmethod
|
sonolus/script/internal/value.py
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from collections.abc import Iterable
|
|
2
|
+
from collections.abc import Callable, Iterable
|
|
3
3
|
from typing import Any, Self
|
|
4
4
|
|
|
5
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRStmt
|
|
5
6
|
from sonolus.backend.place import BlockPlace
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class BackingValue:
|
|
10
|
+
def read(self) -> IRExpr:
|
|
11
|
+
raise NotImplementedError()
|
|
12
|
+
|
|
13
|
+
def write(self, value: IRExpr) -> IRStmt:
|
|
14
|
+
raise NotImplementedError()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
type DataValue = BlockPlace | BackingValue | float | int | bool
|
|
18
|
+
type BackingSource = Callable[[IRExpr], BackingValue]
|
|
19
|
+
|
|
20
|
+
|
|
8
21
|
class Value:
|
|
9
22
|
"""Base class for values."""
|
|
10
23
|
|
|
@@ -59,15 +72,20 @@ class Value:
|
|
|
59
72
|
"""
|
|
60
73
|
raise NotImplementedError
|
|
61
74
|
|
|
75
|
+
@classmethod
|
|
76
|
+
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
77
|
+
"""Creates a value from a backing source."""
|
|
78
|
+
return cls._from_list_(source(IRConst(i)) for i in range(cls._size_()))
|
|
79
|
+
|
|
62
80
|
@classmethod
|
|
63
81
|
@abstractmethod
|
|
64
|
-
def _from_list_(cls, values: Iterable[
|
|
65
|
-
"""Creates a value from a list of
|
|
82
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
83
|
+
"""Creates a value from a list of data values."""
|
|
66
84
|
raise NotImplementedError
|
|
67
85
|
|
|
68
86
|
@abstractmethod
|
|
69
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
70
|
-
"""Converts this value to a list of
|
|
87
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
88
|
+
"""Converts this value to a list of data values."""
|
|
71
89
|
raise NotImplementedError
|
|
72
90
|
|
|
73
91
|
@classmethod
|
|
@@ -76,9 +94,7 @@ class Value:
|
|
|
76
94
|
"""Returns the keys to a flat representation of this value."""
|
|
77
95
|
raise NotImplementedError
|
|
78
96
|
|
|
79
|
-
def _to_flat_dict_(
|
|
80
|
-
self, prefix: str, level_refs: dict[Any, str] | None = None
|
|
81
|
-
) -> dict[str, float | str | BlockPlace]:
|
|
97
|
+
def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, str] | None = None) -> dict[str, DataValue | str]:
|
|
82
98
|
"""Converts this value to a flat dictionary."""
|
|
83
99
|
return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
|
|
84
100
|
|
|
@@ -96,12 +112,12 @@ class Value:
|
|
|
96
112
|
v: Num
|
|
97
113
|
|
|
98
114
|
a = 1
|
|
99
|
-
b = X(a) # (1)
|
|
100
|
-
c = b.v # (2)
|
|
115
|
+
b = X(a) # (1) _get_() is called on a
|
|
116
|
+
c = b.v # (2) _get_() is called on the value for v
|
|
101
117
|
|
|
102
118
|
# (1) prevents this from changing the value of a
|
|
103
119
|
# (2) prevents this from changing the value of c
|
|
104
|
-
# Thus, both calls to
|
|
120
|
+
# Thus, both calls to _get_() are necessary to ensure values behave immutably.
|
|
105
121
|
b.v = 2
|
|
106
122
|
```
|
|
107
123
|
"""
|
sonolus/script/interval.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
|
-
from sonolus.script.debug import
|
|
4
|
+
from sonolus.script.debug import static_error
|
|
5
5
|
from sonolus.script.internal.native import native_function
|
|
6
6
|
from sonolus.script.num import Num
|
|
7
7
|
from sonolus.script.record import Record
|
|
@@ -24,17 +24,6 @@ class Interval(Record):
|
|
|
24
24
|
"""Get an empty interval."""
|
|
25
25
|
return cls(0, 0)
|
|
26
26
|
|
|
27
|
-
def then(self, length: float) -> Self:
|
|
28
|
-
"""Get the interval after this one with a given length.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
length: The length of the interval.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
An interval that has the end of this interval as the start and has the given length.
|
|
35
|
-
"""
|
|
36
|
-
return Interval(self.end, self.end + length)
|
|
37
|
-
|
|
38
27
|
@property
|
|
39
28
|
def length(self) -> float:
|
|
40
29
|
"""The length of the interval.
|
|
@@ -45,7 +34,7 @@ class Interval(Record):
|
|
|
45
34
|
|
|
46
35
|
@property
|
|
47
36
|
def is_empty(self) -> bool:
|
|
48
|
-
"""Whether the
|
|
37
|
+
"""Whether the has a start greater than its end."""
|
|
49
38
|
return self.start > self.end
|
|
50
39
|
|
|
51
40
|
@property
|
|
@@ -73,7 +62,7 @@ class Interval(Record):
|
|
|
73
62
|
case Num(value):
|
|
74
63
|
return self.start <= value <= self.end
|
|
75
64
|
case _:
|
|
76
|
-
|
|
65
|
+
static_error("Invalid type for interval check")
|
|
77
66
|
|
|
78
67
|
def __add__(self, other: float | int) -> Self:
|
|
79
68
|
"""Add a value to both ends of the interval.
|
sonolus/script/num.py
CHANGED
|
@@ -7,11 +7,11 @@ from typing import TYPE_CHECKING, Any, Self, TypeGuard, final, runtime_checkable
|
|
|
7
7
|
|
|
8
8
|
from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
|
|
9
9
|
from sonolus.backend.ops import Op
|
|
10
|
-
from sonolus.backend.place import BlockPlace
|
|
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 Value
|
|
14
|
+
from sonolus.script.internal.value import BackingValue, DataValue, Value
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _NumMeta(type):
|
|
@@ -30,15 +30,17 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
30
30
|
# Since we don't support complex numbers, real is equal to the original number
|
|
31
31
|
__match_args__ = ("real",)
|
|
32
32
|
|
|
33
|
-
data:
|
|
33
|
+
data: DataValue
|
|
34
34
|
|
|
35
|
-
def __init__(self, data:
|
|
35
|
+
def __init__(self, data: DataValue):
|
|
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
40
|
if _is_num(data):
|
|
41
41
|
raise InternalError("Cannot create a Num from a Num")
|
|
42
|
+
if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
|
|
43
|
+
raise TypeError(f"Cannot create a Num from {type(data)}")
|
|
42
44
|
self.data = data
|
|
43
45
|
|
|
44
46
|
def __str__(self) -> str:
|
|
@@ -78,7 +80,7 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
78
80
|
return cls(value)
|
|
79
81
|
|
|
80
82
|
def _is_py_(self) -> bool:
|
|
81
|
-
return
|
|
83
|
+
return isinstance(self.data, float | int | bool)
|
|
82
84
|
|
|
83
85
|
def _as_py_(self) -> Any:
|
|
84
86
|
if not self._is_py_():
|
|
@@ -88,11 +90,11 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
88
90
|
return self.data
|
|
89
91
|
|
|
90
92
|
@classmethod
|
|
91
|
-
def _from_list_(cls, values: Iterable[
|
|
93
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
92
94
|
value = next(iter(values))
|
|
93
95
|
return Num(value)
|
|
94
96
|
|
|
95
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
97
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue]:
|
|
96
98
|
return [self.data]
|
|
97
99
|
|
|
98
100
|
@classmethod
|
|
@@ -111,10 +113,14 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
111
113
|
|
|
112
114
|
def _set_(self, value: Self):
|
|
113
115
|
if ctx():
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
match self.data:
|
|
117
|
+
case BackingValue():
|
|
118
|
+
ctx().add_statements(self.data.write(value))
|
|
119
|
+
case BlockPlace():
|
|
120
|
+
ctx().check_writable(self.data)
|
|
121
|
+
ctx().add_statements(IRSet(self.data, value.ir()))
|
|
122
|
+
case _:
|
|
123
|
+
raise ValueError("Cannot set a read-only value")
|
|
118
124
|
else:
|
|
119
125
|
self.data = value.data
|
|
120
126
|
|
|
@@ -144,12 +150,17 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
144
150
|
return cls(0)
|
|
145
151
|
|
|
146
152
|
def ir(self):
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
match self.data:
|
|
154
|
+
case BlockPlace():
|
|
155
|
+
return IRGet(self.data)
|
|
156
|
+
case BackingValue():
|
|
157
|
+
return self.data.read()
|
|
158
|
+
case _:
|
|
159
|
+
return IRConst(self.data)
|
|
151
160
|
|
|
152
161
|
def index(self) -> int | BlockPlace:
|
|
162
|
+
if isinstance(self.data, BlockPlace):
|
|
163
|
+
return self._get_().data
|
|
153
164
|
return self.data
|
|
154
165
|
|
|
155
166
|
def _bin_op(self, other: Self, const_fn: Callable[[Self, Self], Self | None], ir_op: Op) -> Self:
|
sonolus/script/options.py
CHANGED
|
@@ -15,6 +15,7 @@ from sonolus.script.num import Num
|
|
|
15
15
|
@dataclass
|
|
16
16
|
class _SliderOption:
|
|
17
17
|
name: str | None
|
|
18
|
+
description: str | None
|
|
18
19
|
standard: bool
|
|
19
20
|
advanced: bool
|
|
20
21
|
scope: str | None
|
|
@@ -35,6 +36,8 @@ class _SliderOption:
|
|
|
35
36
|
"max": self.max,
|
|
36
37
|
"step": self.step,
|
|
37
38
|
}
|
|
39
|
+
if self.description is not None:
|
|
40
|
+
result["description"] = self.description
|
|
38
41
|
if self.scope is not None:
|
|
39
42
|
result["scope"] = self.scope
|
|
40
43
|
if self.unit is not None:
|
|
@@ -45,6 +48,7 @@ class _SliderOption:
|
|
|
45
48
|
@dataclass
|
|
46
49
|
class _ToggleOption:
|
|
47
50
|
name: str | None
|
|
51
|
+
description: str | None
|
|
48
52
|
standard: bool
|
|
49
53
|
advanced: bool
|
|
50
54
|
scope: str | None
|
|
@@ -58,6 +62,8 @@ class _ToggleOption:
|
|
|
58
62
|
"advanced": self.advanced,
|
|
59
63
|
"def": int(self.default),
|
|
60
64
|
}
|
|
65
|
+
if self.description is not None:
|
|
66
|
+
result["description"] = self.description
|
|
61
67
|
if self.scope is not None:
|
|
62
68
|
result["scope"] = self.scope
|
|
63
69
|
return result
|
|
@@ -66,6 +72,7 @@ class _ToggleOption:
|
|
|
66
72
|
@dataclass
|
|
67
73
|
class _SelectOption:
|
|
68
74
|
name: str | None
|
|
75
|
+
description: str | None
|
|
69
76
|
standard: bool
|
|
70
77
|
advanced: bool
|
|
71
78
|
scope: str | None
|
|
@@ -81,6 +88,8 @@ class _SelectOption:
|
|
|
81
88
|
"def": self.default,
|
|
82
89
|
"values": self.values,
|
|
83
90
|
}
|
|
91
|
+
if self.description is not None:
|
|
92
|
+
result["description"] = self.description
|
|
84
93
|
if self.scope is not None:
|
|
85
94
|
result["scope"] = self.scope
|
|
86
95
|
return result
|
|
@@ -89,6 +98,7 @@ class _SelectOption:
|
|
|
89
98
|
def slider_option(
|
|
90
99
|
*,
|
|
91
100
|
name: str | None = None,
|
|
101
|
+
description: str | None = None,
|
|
92
102
|
standard: bool = False,
|
|
93
103
|
advanced: bool = False,
|
|
94
104
|
default: float,
|
|
@@ -102,6 +112,7 @@ def slider_option(
|
|
|
102
112
|
|
|
103
113
|
Args:
|
|
104
114
|
name: The name of the option.
|
|
115
|
+
description: The description of the option.
|
|
105
116
|
standard: Whether the option is standard.
|
|
106
117
|
advanced: Whether the option is advanced.
|
|
107
118
|
default: The default value of the option.
|
|
@@ -111,12 +122,13 @@ def slider_option(
|
|
|
111
122
|
unit: The unit of the option.
|
|
112
123
|
scope: The scope of the option.
|
|
113
124
|
"""
|
|
114
|
-
return _SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
|
|
125
|
+
return _SliderOption(name, description, standard, advanced, scope, default, min, max, step, unit)
|
|
115
126
|
|
|
116
127
|
|
|
117
128
|
def toggle_option(
|
|
118
129
|
*,
|
|
119
130
|
name: str | None = None,
|
|
131
|
+
description: str | None = None,
|
|
120
132
|
standard: bool = False,
|
|
121
133
|
advanced: bool = False,
|
|
122
134
|
default: bool,
|
|
@@ -126,17 +138,19 @@ def toggle_option(
|
|
|
126
138
|
|
|
127
139
|
Args:
|
|
128
140
|
name: The name of the option.
|
|
141
|
+
description: The description of the option.
|
|
129
142
|
standard: Whether the option is standard.
|
|
130
143
|
advanced: Whether the option is advanced.
|
|
131
144
|
default: The default value of the option.
|
|
132
145
|
scope: The scope of the option.
|
|
133
146
|
"""
|
|
134
|
-
return _ToggleOption(name, standard, advanced, scope, default)
|
|
147
|
+
return _ToggleOption(name, description, standard, advanced, scope, default)
|
|
135
148
|
|
|
136
149
|
|
|
137
150
|
def select_option(
|
|
138
151
|
*,
|
|
139
152
|
name: str | None = None,
|
|
153
|
+
description: str | None = None,
|
|
140
154
|
standard: bool = False,
|
|
141
155
|
advanced: bool = False,
|
|
142
156
|
default: str | int,
|
|
@@ -147,6 +161,7 @@ def select_option(
|
|
|
147
161
|
|
|
148
162
|
Args:
|
|
149
163
|
name: The name of the option.
|
|
164
|
+
description: The description of the option.
|
|
150
165
|
standard: Whether the option is standard.
|
|
151
166
|
advanced: Whether the option is advanced.
|
|
152
167
|
default: The default value of the option.
|
|
@@ -155,7 +170,7 @@ def select_option(
|
|
|
155
170
|
"""
|
|
156
171
|
if isinstance(default, str):
|
|
157
172
|
default = values.index(default)
|
|
158
|
-
return _SelectOption(name, standard, advanced, scope, default, values)
|
|
173
|
+
return _SelectOption(name, description, standard, advanced, scope, default, values)
|
|
159
174
|
|
|
160
175
|
|
|
161
176
|
type Options = NewType("Options", Any)
|
sonolus/script/pointer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from sonolus.backend.place import BlockPlace
|
|
2
2
|
from sonolus.script.internal.context import ctx
|
|
3
3
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
4
|
-
from sonolus.script.internal.value import Value
|
|
4
|
+
from sonolus.script.internal.value import BackingSource, Value
|
|
5
5
|
from sonolus.script.num import Num, _is_num
|
|
6
6
|
|
|
7
7
|
|
|
@@ -30,3 +30,11 @@ def _deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
|
|
|
30
30
|
if not (isinstance(type_, type) and issubclass(type_, Value)):
|
|
31
31
|
raise TypeError("type_ must be a Value")
|
|
32
32
|
return type_._from_place_(BlockPlace(block, offset))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@meta_fn
|
|
36
|
+
def _backing_deref[T: Value](source: BackingSource, type_: type[T]) -> T:
|
|
37
|
+
type_ = validate_value(type_)._as_py_()
|
|
38
|
+
if not isinstance(type_, type) or not issubclass(type_, Value):
|
|
39
|
+
raise TypeError("type_ must be a Value")
|
|
40
|
+
return type_._from_backing_source_(source)
|
sonolus/script/quad.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Protocol, Self
|
|
4
4
|
|
|
5
5
|
from sonolus.script.record import Record
|
|
6
|
+
from sonolus.script.values import zeros
|
|
6
7
|
from sonolus.script.vec import Vec2, pnpoly
|
|
7
8
|
|
|
8
9
|
|
|
@@ -27,6 +28,16 @@ class Quad(Record):
|
|
|
27
28
|
br: Vec2
|
|
28
29
|
"""The bottom-right corner of the quad."""
|
|
29
30
|
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_quad(cls, value: QuadLike, /) -> Quad:
|
|
33
|
+
"""Create a quad from a quad-like value."""
|
|
34
|
+
return cls(
|
|
35
|
+
bl=value.bl,
|
|
36
|
+
tl=value.tl,
|
|
37
|
+
tr=value.tr,
|
|
38
|
+
br=value.br,
|
|
39
|
+
)
|
|
40
|
+
|
|
30
41
|
@property
|
|
31
42
|
def center(self) -> Vec2:
|
|
32
43
|
"""The center of the quad."""
|
|
@@ -95,6 +106,44 @@ class Quad(Record):
|
|
|
95
106
|
"""Rotate the quad by the given angle about its center and return a new quad."""
|
|
96
107
|
return self.rotate_about(angle, self.center)
|
|
97
108
|
|
|
109
|
+
def permute(self, count: int = 1, /) -> Self:
|
|
110
|
+
"""Perform a cyclic permutation of the quad's vertices and return a new quad.
|
|
111
|
+
|
|
112
|
+
On a square, this operation is equivalent to rotating the square counterclockwise 90 degrees `count` times.
|
|
113
|
+
|
|
114
|
+
Negative values of `count` are allowed and will rotate the quad clockwise.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
count: The number of vertices to shift. Defaults to 1.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The permuted quad.
|
|
121
|
+
"""
|
|
122
|
+
count = int(count % 4)
|
|
123
|
+
result = zeros(Quad)
|
|
124
|
+
match count:
|
|
125
|
+
case 0:
|
|
126
|
+
result.bl @= self.bl
|
|
127
|
+
result.tl @= self.tl
|
|
128
|
+
result.tr @= self.tr
|
|
129
|
+
result.br @= self.br
|
|
130
|
+
case 1:
|
|
131
|
+
result.bl @= self.br
|
|
132
|
+
result.tl @= self.bl
|
|
133
|
+
result.tr @= self.tl
|
|
134
|
+
result.br @= self.tr
|
|
135
|
+
case 2:
|
|
136
|
+
result.bl @= self.tr
|
|
137
|
+
result.tl @= self.br
|
|
138
|
+
result.tr @= self.bl
|
|
139
|
+
result.br @= self.tl
|
|
140
|
+
case 3:
|
|
141
|
+
result.bl @= self.tl
|
|
142
|
+
result.tl @= self.tr
|
|
143
|
+
result.tr @= self.br
|
|
144
|
+
result.br @= self.bl
|
|
145
|
+
return result
|
|
146
|
+
|
|
98
147
|
def contains_point(self, point: Vec2, /) -> bool:
|
|
99
148
|
"""Check if the quad contains the given point.
|
|
100
149
|
|
|
@@ -248,7 +297,7 @@ class Rect(Record):
|
|
|
248
297
|
return self.l <= point.x <= self.r and self.b <= point.y <= self.t
|
|
249
298
|
|
|
250
299
|
|
|
251
|
-
class
|
|
300
|
+
class _QuadLike(Protocol):
|
|
252
301
|
"""A protocol for types that can be used as quads."""
|
|
253
302
|
|
|
254
303
|
@property
|
|
@@ -268,6 +317,11 @@ class QuadLike(Protocol):
|
|
|
268
317
|
"""The bottom-right corner of the quad."""
|
|
269
318
|
|
|
270
319
|
|
|
320
|
+
# PyCharm doesn't recognize attributes as satisfying the protocol.
|
|
321
|
+
type QuadLike = _QuadLike | Quad
|
|
322
|
+
"""A type that can be used as a quad."""
|
|
323
|
+
|
|
324
|
+
|
|
271
325
|
def flatten_quad(quad: QuadLike) -> tuple[float, float, float, float, float, float, float, float]:
|
|
272
326
|
bl = quad.bl
|
|
273
327
|
tl = quad.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 Value
|
|
19
|
+
from sonolus.script.internal.value import DataValue, Value
|
|
20
20
|
from sonolus.script.num import Num
|
|
21
21
|
|
|
22
22
|
|
|
@@ -182,11 +182,11 @@ class Record(GenericValue):
|
|
|
182
182
|
return self
|
|
183
183
|
|
|
184
184
|
@classmethod
|
|
185
|
-
def _from_list_(cls, values: Iterable[
|
|
185
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
186
186
|
iterator = iter(values)
|
|
187
187
|
return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
|
|
188
188
|
|
|
189
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
189
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
190
190
|
result = []
|
|
191
191
|
for field in self._fields:
|
|
192
192
|
result.extend(self._value[field.name]._to_list_(level_refs))
|
sonolus/script/runtime.py
CHANGED
|
@@ -2,7 +2,8 @@ from enum import IntEnum
|
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.mode import Mode
|
|
4
4
|
from sonolus.script.array import Array
|
|
5
|
-
from sonolus.script.
|
|
5
|
+
from sonolus.script.array_like import ArrayLike
|
|
6
|
+
from sonolus.script.containers import ArrayPointer
|
|
6
7
|
from sonolus.script.globals import (
|
|
7
8
|
_level_life,
|
|
8
9
|
_level_score,
|
|
@@ -315,7 +316,8 @@ class Touch(Record):
|
|
|
315
316
|
|
|
316
317
|
@_runtime_touch_array
|
|
317
318
|
class _TouchArray:
|
|
318
|
-
|
|
319
|
+
# Handled specially, see touches()
|
|
320
|
+
pass
|
|
319
321
|
|
|
320
322
|
|
|
321
323
|
@_runtime_skin_transform
|
|
@@ -500,6 +502,15 @@ def is_tutorial() -> bool:
|
|
|
500
502
|
return ctx() and ctx().global_state.mode == Mode.TUTORIAL
|
|
501
503
|
|
|
502
504
|
|
|
505
|
+
@meta_fn
|
|
506
|
+
def is_preprocessing() -> bool:
|
|
507
|
+
"""Check if the game is in the preprocessing stage.
|
|
508
|
+
|
|
509
|
+
Returns True if the current callback is one of preprocess, spawn_order, spawn_time, or despawn_time.
|
|
510
|
+
"""
|
|
511
|
+
return ctx() and ctx().callback in {"preprocess", "spawnOrder", "spawnTime", "despawnTime"}
|
|
512
|
+
|
|
513
|
+
|
|
503
514
|
@meta_fn
|
|
504
515
|
def aspect_ratio() -> float:
|
|
505
516
|
"""Get the aspect ratio of the game."""
|
|
@@ -640,15 +651,15 @@ def scaled_time() -> float:
|
|
|
640
651
|
|
|
641
652
|
|
|
642
653
|
@meta_fn
|
|
643
|
-
def touches() ->
|
|
654
|
+
def touches() -> ArrayLike[Touch]:
|
|
644
655
|
"""Get the current touches of the game."""
|
|
645
656
|
if not ctx():
|
|
646
|
-
return
|
|
657
|
+
return Array[Touch, 0]()
|
|
647
658
|
match ctx().global_state.mode:
|
|
648
659
|
case Mode.PLAY:
|
|
649
|
-
return
|
|
660
|
+
return ArrayPointer[Touch](_PlayRuntimeUpdate.touch_count, ctx().blocks.RuntimeTouchArray, 0)
|
|
650
661
|
case _:
|
|
651
|
-
return
|
|
662
|
+
return Array[Touch, 0]()
|
|
652
663
|
|
|
653
664
|
|
|
654
665
|
@meta_fn
|
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/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,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sonolus.py
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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.
|
|
@@ -34,41 +34,41 @@ 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=
|
|
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
40
|
sonolus/script/bucket.py,sha256=oaaB7OXtBWzr8sLzPOpm8-GMhs7FzEhYrI1uVcM0DHM,7453
|
|
41
|
-
sonolus/script/containers.py,sha256=
|
|
42
|
-
sonolus/script/debug.py,sha256
|
|
41
|
+
sonolus/script/containers.py,sha256=QUFUk1lDwM2AzIPH6uHkZtZXT2D4_xT3P6pJIp6i7Ew,17640
|
|
42
|
+
sonolus/script/debug.py,sha256=aYyFm8DuoifK0L0pY4G7ZZEEeSqbig90Yogy6Z_M8Yg,3135
|
|
43
43
|
sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
|
|
44
44
|
sonolus/script/effect.py,sha256=V9bJvMzs1O4C1PjTOKgsAXov-l4AnDb2h38-DzmeWpI,5838
|
|
45
|
-
sonolus/script/engine.py,sha256=
|
|
45
|
+
sonolus/script/engine.py,sha256=BhhQTrHuGAAAD6JPQ3R0jvHdimwW83PPghEIdAdtGMA,10683
|
|
46
46
|
sonolus/script/globals.py,sha256=Z8RfLkgDuXPIKiq-aOqblP0s__ALGmvcKdlyWZH21EM,9166
|
|
47
47
|
sonolus/script/instruction.py,sha256=PNfxC1dhT_hB0BxhDV3KXMn_kKxfI0t1iZmg8m6ddMU,6725
|
|
48
|
-
sonolus/script/interval.py,sha256=
|
|
48
|
+
sonolus/script/interval.py,sha256=IYNgJx0ngREnEVu_yMAu0MrA_Q7mZuToT8U3fbdb3Sc,9122
|
|
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=
|
|
53
|
-
sonolus/script/options.py,sha256=
|
|
52
|
+
sonolus/script/num.py,sha256=FpMi-w_rdkozZSFxraNQ4IkOzQpTr4YAH3hbqMIFYy8,14612
|
|
53
|
+
sonolus/script/options.py,sha256=ZD9I898wMrpPQ8IyuDoYef804rZzhSvPoC_0bxovIec,8245
|
|
54
54
|
sonolus/script/particle.py,sha256=K06ArT9tstRNdbuGIviDmDDWcK3-ieA53LHg0Xvizow,8304
|
|
55
|
-
sonolus/script/pointer.py,sha256=
|
|
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=
|
|
60
|
-
sonolus/script/runtime.py,sha256=
|
|
58
|
+
sonolus/script/quad.py,sha256=7uhBwRSvA4tZ_JFjc3Y9n8C9AwQirX5E8MAA0QaHCak,10067
|
|
59
|
+
sonolus/script/record.py,sha256=EV4wywagBl3RU40Bqo9DRdx7Ta8xBgwKtgKlDF02X0o,11332
|
|
60
|
+
sonolus/script/runtime.py,sha256=MIGqDqNitlI6vFtZ2SP32D6LCLJ3RAuGKSxKKl-1tJw,20303
|
|
61
61
|
sonolus/script/sprite.py,sha256=CMcRAZ2hejXnaBmY2_n1_rj6hGOgPP5zEW-BpyaEVOY,16256
|
|
62
|
-
sonolus/script/text.py,sha256=
|
|
62
|
+
sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
|
|
63
63
|
sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
|
|
64
64
|
sonolus/script/transform.py,sha256=hH6KSRQC8vV-Z10CRCrGewMYqQwUMH3mQIEmniuC2Zw,10760
|
|
65
|
-
sonolus/script/ui.py,sha256=
|
|
66
|
-
sonolus/script/values.py,sha256=
|
|
65
|
+
sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
|
|
66
|
+
sonolus/script/values.py,sha256=bwh_9ikxuKyrO_m3_lTKIvZDze4j88Fz-_HT_jB657g,1032
|
|
67
67
|
sonolus/script/vec.py,sha256=4ntfJ96zxKR-nVZluUgHd-Ud4vNfButfiv7EsroZxfE,7002
|
|
68
68
|
sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
|
|
69
69
|
sonolus/script/internal/builtin_impls.py,sha256=w9QqxJQ2YR5pVy820qWmAy0utbsW4hcSzMvA_yNgsvg,8186
|
|
70
70
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
71
|
-
sonolus/script/internal/constant.py,sha256=
|
|
71
|
+
sonolus/script/internal/constant.py,sha256=XljC1bXpjmQsekFFDNbaCLoHLPXzDeE17QDHf-rY70Q,3835
|
|
72
72
|
sonolus/script/internal/context.py,sha256=xW7yFWr13yf77Uk4C_F7v9Kp4ZP1o30uZuBkvK1KyLA,14157
|
|
73
73
|
sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
|
|
74
74
|
sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
|
|
@@ -80,11 +80,11 @@ sonolus/script/internal/math_impls.py,sha256=Xk7tLMnV2npzPJWtHlspONQHt09Gh2YLdHh
|
|
|
80
80
|
sonolus/script/internal/native.py,sha256=XKlNnWSJ-lxbwVGWhGj_CSSoWsVN18imqT5sAsDJT1w,1551
|
|
81
81
|
sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
|
|
82
82
|
sonolus/script/internal/range.py,sha256=lrTanQFHU7RuQxSSPwDdoC30Y8FnHGxcP1Ahditu3zU,2297
|
|
83
|
-
sonolus/script/internal/transient.py,sha256=
|
|
83
|
+
sonolus/script/internal/transient.py,sha256=d6iYhM9f6DPUX5nkYQGm-x0b9XEfZUmB4AtUNnyhixo,1636
|
|
84
84
|
sonolus/script/internal/tuple_impl.py,sha256=vjXmScLVdeTkDn3t9fgIRqtW31iwngnaP2rmA6nlsLw,3431
|
|
85
|
-
sonolus/script/internal/value.py,sha256=
|
|
86
|
-
sonolus_py-0.2.
|
|
87
|
-
sonolus_py-0.2.
|
|
88
|
-
sonolus_py-0.2.
|
|
89
|
-
sonolus_py-0.2.
|
|
90
|
-
sonolus_py-0.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|