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/internal/impl.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from types import FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
5
|
+
from types import EllipsisType, FunctionType, MethodType, ModuleType, NoneType, NotImplementedType, UnionType
|
|
6
6
|
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, get_origin, overload
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
@@ -42,7 +42,7 @@ def validate_value(value: Any) -> Value:
|
|
|
42
42
|
|
|
43
43
|
def try_validate_value(value: Any) -> Value | None:
|
|
44
44
|
from sonolus.script.globals import _GlobalPlaceholder
|
|
45
|
-
from sonolus.script.internal.constant import
|
|
45
|
+
from sonolus.script.internal.constant import BasicConstantValue
|
|
46
46
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
47
47
|
from sonolus.script.internal.generic import PartialGeneric
|
|
48
48
|
from sonolus.script.internal.tuple_impl import TupleImpl
|
|
@@ -65,8 +65,8 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
65
65
|
return value
|
|
66
66
|
case type():
|
|
67
67
|
if value in {int, float, bool}:
|
|
68
|
-
return
|
|
69
|
-
return
|
|
68
|
+
return BasicConstantValue.of(Num)
|
|
69
|
+
return BasicConstantValue.of(value)
|
|
70
70
|
case int() | float() | bool():
|
|
71
71
|
return Num._accept_(value)
|
|
72
72
|
case tuple():
|
|
@@ -78,17 +78,18 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
78
78
|
| TypeVar()
|
|
79
79
|
| FunctionType()
|
|
80
80
|
| MethodType()
|
|
81
|
-
| NotImplementedType()
|
|
82
81
|
| str()
|
|
83
|
-
| NoneType()
|
|
84
82
|
| ModuleType()
|
|
83
|
+
| NoneType()
|
|
84
|
+
| NotImplementedType()
|
|
85
|
+
| EllipsisType()
|
|
85
86
|
):
|
|
86
|
-
return
|
|
87
|
+
return BasicConstantValue.of(value)
|
|
87
88
|
case other_type if get_origin(value) in {Literal, Annotated, UnionType, tuple}:
|
|
88
|
-
return
|
|
89
|
+
return BasicConstantValue.of(other_type)
|
|
89
90
|
case _GlobalPlaceholder():
|
|
90
91
|
return value.get()
|
|
91
92
|
case comptime_value if getattr(comptime_value, "_is_comptime_value_", False):
|
|
92
|
-
return
|
|
93
|
+
return BasicConstantValue.of(comptime_value)
|
|
93
94
|
case _:
|
|
94
95
|
return None
|
|
@@ -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,36 @@
|
|
|
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
|
+
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
|
+
|
|
30
|
+
type DataValue = BlockPlace | BackingValue | float | int | bool
|
|
31
|
+
type BackingSource = Callable[[IRExpr], BackingValue]
|
|
32
|
+
|
|
33
|
+
|
|
8
34
|
class Value:
|
|
9
35
|
"""Base class for values."""
|
|
10
36
|
|
|
@@ -59,15 +85,20 @@ class Value:
|
|
|
59
85
|
"""
|
|
60
86
|
raise NotImplementedError
|
|
61
87
|
|
|
88
|
+
@classmethod
|
|
89
|
+
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
90
|
+
"""Creates a value from a backing source."""
|
|
91
|
+
return cls._from_list_(source(IRConst(i)) for i in range(cls._size_()))
|
|
92
|
+
|
|
62
93
|
@classmethod
|
|
63
94
|
@abstractmethod
|
|
64
|
-
def _from_list_(cls, values: Iterable[
|
|
65
|
-
"""Creates a value from a list of
|
|
95
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
96
|
+
"""Creates a value from a list of data values."""
|
|
66
97
|
raise NotImplementedError
|
|
67
98
|
|
|
68
99
|
@abstractmethod
|
|
69
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
70
|
-
"""Converts this value to a list of
|
|
100
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
101
|
+
"""Converts this value to a list of data values."""
|
|
71
102
|
raise NotImplementedError
|
|
72
103
|
|
|
73
104
|
@classmethod
|
|
@@ -76,9 +107,7 @@ class Value:
|
|
|
76
107
|
"""Returns the keys to a flat representation of this value."""
|
|
77
108
|
raise NotImplementedError
|
|
78
109
|
|
|
79
|
-
def _to_flat_dict_(
|
|
80
|
-
self, prefix: str, level_refs: dict[Any, str] | None = None
|
|
81
|
-
) -> dict[str, float | str | BlockPlace]:
|
|
110
|
+
def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, str] | None = None) -> dict[str, DataValue | str]:
|
|
82
111
|
"""Converts this value to a flat dictionary."""
|
|
83
112
|
return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
|
|
84
113
|
|
|
@@ -96,12 +125,12 @@ class Value:
|
|
|
96
125
|
v: Num
|
|
97
126
|
|
|
98
127
|
a = 1
|
|
99
|
-
b = X(a) # (1)
|
|
100
|
-
c = b.v # (2)
|
|
128
|
+
b = X(a) # (1) _get_() is called on a
|
|
129
|
+
c = b.v # (2) _get_() is called on the value for v
|
|
101
130
|
|
|
102
131
|
# (1) prevents this from changing the value of a
|
|
103
132
|
# (2) prevents this from changing the value of c
|
|
104
|
-
# Thus, both calls to
|
|
133
|
+
# Thus, both calls to _get_() are necessary to ensure values behave immutably.
|
|
105
134
|
b.v = 2
|
|
106
135
|
```
|
|
107
136
|
"""
|
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
|
@@ -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
|
-
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, ExprBackingValue, Value
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _NumMeta(type):
|
|
@@ -30,15 +30,19 @@ 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 | 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")
|
|
44
|
+
if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
|
|
45
|
+
raise TypeError(f"Cannot create a Num from {type(data)}")
|
|
42
46
|
self.data = data
|
|
43
47
|
|
|
44
48
|
def __str__(self) -> str:
|
|
@@ -78,7 +82,7 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
78
82
|
return cls(value)
|
|
79
83
|
|
|
80
84
|
def _is_py_(self) -> bool:
|
|
81
|
-
return
|
|
85
|
+
return isinstance(self.data, float | int | bool)
|
|
82
86
|
|
|
83
87
|
def _as_py_(self) -> Any:
|
|
84
88
|
if not self._is_py_():
|
|
@@ -88,11 +92,11 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
88
92
|
return self.data
|
|
89
93
|
|
|
90
94
|
@classmethod
|
|
91
|
-
def _from_list_(cls, values: Iterable[
|
|
95
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
92
96
|
value = next(iter(values))
|
|
93
97
|
return Num(value)
|
|
94
98
|
|
|
95
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
99
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue]:
|
|
96
100
|
return [self.data]
|
|
97
101
|
|
|
98
102
|
@classmethod
|
|
@@ -111,10 +115,14 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
111
115
|
|
|
112
116
|
def _set_(self, value: Self):
|
|
113
117
|
if ctx():
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
match self.data:
|
|
119
|
+
case BackingValue():
|
|
120
|
+
ctx().add_statements(self.data.write(value))
|
|
121
|
+
case BlockPlace():
|
|
122
|
+
ctx().check_writable(self.data)
|
|
123
|
+
ctx().add_statements(IRSet(self.data, value.ir()))
|
|
124
|
+
case _:
|
|
125
|
+
raise ValueError("Cannot set a read-only value")
|
|
118
126
|
else:
|
|
119
127
|
self.data = value.data
|
|
120
128
|
|
|
@@ -144,12 +152,17 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
144
152
|
return cls(0)
|
|
145
153
|
|
|
146
154
|
def ir(self):
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
match self.data:
|
|
156
|
+
case BlockPlace():
|
|
157
|
+
return IRGet(self.data)
|
|
158
|
+
case BackingValue():
|
|
159
|
+
return self.data.read()
|
|
160
|
+
case _:
|
|
161
|
+
return IRConst(self.data)
|
|
151
162
|
|
|
152
163
|
def index(self) -> int | BlockPlace:
|
|
164
|
+
if isinstance(self.data, BlockPlace):
|
|
165
|
+
return self._get_().data
|
|
153
166
|
return self.data
|
|
154
167
|
|
|
155
168
|
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/particle.py
CHANGED
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
|
|
|
@@ -10,7 +11,7 @@ class Quad(Record):
|
|
|
10
11
|
"""A quad defined by its four corners.
|
|
11
12
|
|
|
12
13
|
Usage:
|
|
13
|
-
```
|
|
14
|
+
```python
|
|
14
15
|
Quad(bl: Vec2, tl: Vec2, tr: Vec2, br: Vec2)
|
|
15
16
|
```
|
|
16
17
|
"""
|
|
@@ -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
|
|
|
@@ -111,7 +160,7 @@ class Rect(Record):
|
|
|
111
160
|
"""A rectangle defined by its top, right, bottom, and left edges.
|
|
112
161
|
|
|
113
162
|
Usage:
|
|
114
|
-
```
|
|
163
|
+
```python
|
|
115
164
|
Rect(t: float, r: float, b: float, l: float)
|
|
116
165
|
```
|
|
117
166
|
"""
|
|
@@ -174,7 +223,7 @@ class Rect(Record):
|
|
|
174
223
|
return Vec2((self.l + self.r) / 2, (self.t + self.b) / 2)
|
|
175
224
|
|
|
176
225
|
def as_quad(self) -> Quad:
|
|
177
|
-
"""Convert the rectangle to a quad."""
|
|
226
|
+
"""Convert the rectangle to a [`Quad`][sonolus.script.quad.Quad]."""
|
|
178
227
|
return Quad(
|
|
179
228
|
bl=self.bl,
|
|
180
229
|
tl=self.tl,
|
|
@@ -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 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)
|
|
@@ -182,11 +193,11 @@ class Record(GenericValue):
|
|
|
182
193
|
return self
|
|
183
194
|
|
|
184
195
|
@classmethod
|
|
185
|
-
def _from_list_(cls, values: Iterable[
|
|
196
|
+
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
186
197
|
iterator = iter(values)
|
|
187
198
|
return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
|
|
188
199
|
|
|
189
|
-
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[
|
|
200
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
190
201
|
result = []
|
|
191
202
|
for field in self._fields:
|
|
192
203
|
result.extend(self._value[field.name]._to_list_(level_refs))
|
|
@@ -203,7 +214,7 @@ class Record(GenericValue):
|
|
|
203
214
|
return self
|
|
204
215
|
|
|
205
216
|
def _set_(self, value: Self):
|
|
206
|
-
raise TypeError("Record does not support
|
|
217
|
+
raise TypeError("Record does not support _set_")
|
|
207
218
|
|
|
208
219
|
def _copy_from_(self, value: Self):
|
|
209
220
|
value = self._accept_(value)
|
sonolus/script/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
|