sonolus.py 0.3.3__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/excepthook.py +30 -0
- sonolus/backend/finalize.py +15 -1
- sonolus/backend/ops.py +4 -0
- sonolus/backend/optimize/allocate.py +5 -5
- sonolus/backend/optimize/constant_evaluation.py +124 -19
- sonolus/backend/optimize/copy_coalesce.py +15 -12
- sonolus/backend/optimize/dead_code.py +7 -6
- sonolus/backend/optimize/dominance.py +2 -2
- sonolus/backend/optimize/flow.py +54 -8
- sonolus/backend/optimize/inlining.py +137 -30
- sonolus/backend/optimize/liveness.py +2 -2
- sonolus/backend/optimize/optimize.py +15 -1
- sonolus/backend/optimize/passes.py +11 -3
- sonolus/backend/optimize/simplify.py +137 -8
- sonolus/backend/optimize/ssa.py +47 -13
- sonolus/backend/place.py +5 -4
- sonolus/backend/utils.py +24 -0
- sonolus/backend/visitor.py +260 -17
- sonolus/build/cli.py +47 -19
- sonolus/build/compile.py +12 -5
- sonolus/build/engine.py +70 -1
- sonolus/build/level.py +3 -3
- sonolus/build/project.py +2 -2
- sonolus/script/archetype.py +27 -24
- sonolus/script/array.py +25 -19
- sonolus/script/array_like.py +46 -49
- sonolus/script/bucket.py +1 -1
- sonolus/script/containers.py +22 -26
- sonolus/script/debug.py +24 -47
- sonolus/script/effect.py +1 -1
- sonolus/script/engine.py +2 -2
- sonolus/script/globals.py +3 -3
- sonolus/script/instruction.py +3 -3
- sonolus/script/internal/builtin_impls.py +155 -28
- sonolus/script/internal/constant.py +13 -3
- sonolus/script/internal/context.py +46 -15
- sonolus/script/internal/impl.py +9 -3
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/math_impls.py +17 -0
- sonolus/script/internal/native.py +5 -5
- sonolus/script/internal/range.py +14 -17
- sonolus/script/internal/simulation_context.py +1 -1
- sonolus/script/internal/transient.py +2 -2
- sonolus/script/internal/value.py +42 -4
- sonolus/script/interval.py +15 -15
- sonolus/script/iterator.py +38 -107
- sonolus/script/maybe.py +139 -0
- sonolus/script/num.py +30 -15
- sonolus/script/options.py +1 -1
- sonolus/script/particle.py +1 -1
- sonolus/script/pointer.py +1 -1
- sonolus/script/project.py +24 -5
- sonolus/script/quad.py +15 -15
- sonolus/script/record.py +21 -12
- sonolus/script/runtime.py +22 -18
- sonolus/script/sprite.py +1 -1
- sonolus/script/stream.py +69 -85
- sonolus/script/transform.py +35 -34
- sonolus/script/values.py +10 -10
- sonolus/script/vec.py +23 -20
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/METADATA +1 -1
- sonolus_py-0.4.0.dist-info/RECORD +93 -0
- sonolus_py-0.3.3.dist-info/RECORD +0 -92
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.3.dist-info → sonolus_py-0.4.0.dist-info}/licenses/LICENSE +0 -0
sonolus/script/iterator.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from abc import abstractmethod
|
|
4
3
|
from typing import Any
|
|
5
4
|
|
|
6
5
|
from sonolus.script.internal.impl import meta_fn
|
|
6
|
+
from sonolus.script.maybe import Maybe, Nothing, Some
|
|
7
7
|
from sonolus.script.record import Record
|
|
8
8
|
|
|
9
9
|
|
|
@@ -12,57 +12,28 @@ class SonolusIterator[T]:
|
|
|
12
12
|
|
|
13
13
|
This class is used to define custom iterators that can be used in Sonolus.py.
|
|
14
14
|
|
|
15
|
-
Inheritors must implement the `
|
|
16
|
-
The `__next__` and `__iter__` methods are implemented by default.
|
|
15
|
+
Inheritors must implement the `next` method, which should return a `Maybe[T]`.
|
|
17
16
|
|
|
18
17
|
Usage:
|
|
19
18
|
```python
|
|
20
19
|
class MyIterator(Record, SonolusIterator):
|
|
21
|
-
def
|
|
22
|
-
...
|
|
23
|
-
|
|
24
|
-
def get(self) -> Any:
|
|
25
|
-
...
|
|
26
|
-
|
|
27
|
-
def advance(self):
|
|
20
|
+
def next(self) -> Maybe[T]:
|
|
28
21
|
...
|
|
29
22
|
```
|
|
30
23
|
"""
|
|
31
24
|
|
|
32
25
|
_allow_instance_check_ = True
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
self.advance()
|
|
37
|
-
return result
|
|
38
|
-
|
|
39
|
-
@abstractmethod
|
|
40
|
-
def has_next(self) -> bool:
|
|
41
|
-
"""Return whether the iterator has more elements."""
|
|
42
|
-
raise NotImplementedError
|
|
43
|
-
|
|
44
|
-
@abstractmethod
|
|
45
|
-
def get(self) -> T:
|
|
46
|
-
"""Return the next element of the iterator.
|
|
47
|
-
|
|
48
|
-
May be called multiple times before calling `advance`.
|
|
49
|
-
|
|
50
|
-
Must not be called if `has_next` returns `False`.
|
|
51
|
-
"""
|
|
52
|
-
raise NotImplementedError
|
|
53
|
-
|
|
54
|
-
@abstractmethod
|
|
55
|
-
def advance(self):
|
|
56
|
-
"""Advance the iterator to the next element.
|
|
57
|
-
|
|
58
|
-
Must not be called if `has_next` returns `False`.
|
|
59
|
-
"""
|
|
27
|
+
@meta_fn
|
|
28
|
+
def next(self) -> Maybe[T]:
|
|
60
29
|
raise NotImplementedError
|
|
61
30
|
|
|
62
31
|
def __next__(self) -> T:
|
|
63
|
-
|
|
32
|
+
result = self.next()
|
|
33
|
+
if result.is_some:
|
|
34
|
+
return result.get_unsafe()
|
|
35
|
+
else:
|
|
64
36
|
raise StopIteration
|
|
65
|
-
return self.next()
|
|
66
37
|
|
|
67
38
|
def __iter__(self) -> SonolusIterator[T]:
|
|
68
39
|
return self
|
|
@@ -73,15 +44,13 @@ class _Enumerator[V: SonolusIterator](Record, SonolusIterator):
|
|
|
73
44
|
offset: int
|
|
74
45
|
iterator: V
|
|
75
46
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def advance(self):
|
|
47
|
+
def next(self) -> Maybe[tuple[int, Any]]:
|
|
48
|
+
value = self.iterator.next()
|
|
49
|
+
if value.is_nothing:
|
|
50
|
+
return Nothing
|
|
51
|
+
result = (self.i + self.offset, value.get_unsafe())
|
|
83
52
|
self.i += 1
|
|
84
|
-
|
|
53
|
+
return Some(result)
|
|
85
54
|
|
|
86
55
|
|
|
87
56
|
class _Zipper[T](Record, SonolusIterator):
|
|
@@ -89,11 +58,6 @@ class _Zipper[T](Record, SonolusIterator):
|
|
|
89
58
|
iterators: T
|
|
90
59
|
|
|
91
60
|
@meta_fn
|
|
92
|
-
def has_next(self) -> bool:
|
|
93
|
-
from sonolus.backend.visitor import compile_and_call
|
|
94
|
-
|
|
95
|
-
return compile_and_call(self._has_next, self._get_iterators())
|
|
96
|
-
|
|
97
61
|
def _get_iterators(self) -> tuple[SonolusIterator, ...]:
|
|
98
62
|
from sonolus.script.containers import Pair
|
|
99
63
|
|
|
@@ -105,81 +69,48 @@ class _Zipper[T](Record, SonolusIterator):
|
|
|
105
69
|
iterators.append(v)
|
|
106
70
|
return tuple(iterators)
|
|
107
71
|
|
|
108
|
-
def _has_next(self, iterators: tuple[SonolusIterator, ...]) -> bool:
|
|
109
|
-
for iterator in iterators: # noqa: SIM110
|
|
110
|
-
if not iterator.has_next():
|
|
111
|
-
return False
|
|
112
|
-
return True
|
|
113
|
-
|
|
114
72
|
@meta_fn
|
|
115
|
-
def
|
|
73
|
+
def _get_next_values(self) -> tuple[Any, ...]:
|
|
116
74
|
from sonolus.backend.visitor import compile_and_call
|
|
117
75
|
|
|
118
|
-
return tuple(compile_and_call(iterator.
|
|
76
|
+
return tuple(compile_and_call(iterator.next) for iterator in self._get_iterators())
|
|
119
77
|
|
|
120
78
|
@meta_fn
|
|
121
|
-
def
|
|
79
|
+
def _values_to_tuple(self, values: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
122
80
|
from sonolus.backend.visitor import compile_and_call
|
|
123
81
|
|
|
124
|
-
for
|
|
125
|
-
compile_and_call(iterator.advance)
|
|
126
|
-
|
|
82
|
+
return tuple(compile_and_call(value.get_unsafe) for value in values)
|
|
127
83
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
84
|
+
def next(self) -> Maybe[tuple[Any, ...]]:
|
|
85
|
+
values = self._get_next_values()
|
|
86
|
+
for value in values:
|
|
87
|
+
if value.is_nothing:
|
|
88
|
+
return Nothing
|
|
89
|
+
return Some(self._values_to_tuple(values))
|
|
131
90
|
|
|
132
|
-
def get(self) -> Any:
|
|
133
|
-
return None
|
|
134
91
|
|
|
135
|
-
|
|
136
|
-
|
|
92
|
+
class _EmptyIterator(Record, SonolusIterator):
|
|
93
|
+
def next(self) -> Maybe[Any]:
|
|
94
|
+
return Nothing
|
|
137
95
|
|
|
138
96
|
|
|
139
97
|
class _MappingIterator[T, Fn](Record, SonolusIterator):
|
|
140
98
|
fn: Fn
|
|
141
99
|
iterator: T
|
|
142
100
|
|
|
143
|
-
def
|
|
144
|
-
return self.iterator.
|
|
145
|
-
|
|
146
|
-
def get(self) -> Any:
|
|
147
|
-
return self.fn(self.iterator.get())
|
|
148
|
-
|
|
149
|
-
def advance(self):
|
|
150
|
-
self.iterator.advance()
|
|
101
|
+
def next(self) -> Maybe[Any]:
|
|
102
|
+
return self.iterator.next().map(self.fn)
|
|
151
103
|
|
|
152
104
|
|
|
153
105
|
class _FilteringIterator[T, Fn](Record, SonolusIterator):
|
|
154
106
|
fn: Fn
|
|
155
107
|
iterator: T
|
|
156
108
|
|
|
157
|
-
def
|
|
158
|
-
while
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return self.iterator.get()
|
|
166
|
-
|
|
167
|
-
def advance(self):
|
|
168
|
-
self.iterator.advance()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class _ChainingIterator[T](Record, SonolusIterator):
|
|
172
|
-
iterator: T
|
|
173
|
-
|
|
174
|
-
def has_next(self) -> bool:
|
|
175
|
-
return self.iterator.has_next()
|
|
176
|
-
|
|
177
|
-
def get(self) -> Any:
|
|
178
|
-
return self.iterator.get().get()
|
|
179
|
-
|
|
180
|
-
def advance(self):
|
|
181
|
-
self.iterator.get().advance()
|
|
182
|
-
while not self.iterator.get().has_next():
|
|
183
|
-
self.iterator.advance()
|
|
184
|
-
if not self.iterator.has_next():
|
|
185
|
-
break
|
|
109
|
+
def next(self) -> Maybe[T]:
|
|
110
|
+
while True:
|
|
111
|
+
value = self.iterator.next()
|
|
112
|
+
if value.is_nothing:
|
|
113
|
+
return Nothing
|
|
114
|
+
inside = value.get_unsafe()
|
|
115
|
+
if self.fn(inside):
|
|
116
|
+
return Some(inside)
|
sonolus/script/maybe.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from sonolus.script.internal.context import ctx
|
|
7
|
+
from sonolus.script.internal.impl import meta_fn
|
|
8
|
+
from sonolus.script.internal.transient import TransientValue
|
|
9
|
+
from sonolus.script.internal.value import Value
|
|
10
|
+
from sonolus.script.num import Num
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Maybe[T](TransientValue):
|
|
14
|
+
"""A type that either has a value or is empty.
|
|
15
|
+
|
|
16
|
+
Maybe has special behavior when returned from a function: it may be returned from multiple places
|
|
17
|
+
in a function, provided that all but one return statement returns the literal `Nothing`.
|
|
18
|
+
|
|
19
|
+
This type is not intended for other uses, such as being stored in a Record, Array, or Archetype.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
```python
|
|
23
|
+
def fn(a, b):
|
|
24
|
+
if a:
|
|
25
|
+
return Some(b)
|
|
26
|
+
else:
|
|
27
|
+
return Nothing
|
|
28
|
+
|
|
29
|
+
result = fn(..., ...)
|
|
30
|
+
if result.is_some:
|
|
31
|
+
value = result.get()
|
|
32
|
+
...
|
|
33
|
+
```
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
_present: Num
|
|
37
|
+
_value: T
|
|
38
|
+
|
|
39
|
+
def __init__(self, *, present: bool, value: T):
|
|
40
|
+
self._present = Num._accept_(present)
|
|
41
|
+
self._value = value
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
@meta_fn
|
|
45
|
+
def is_some(self) -> bool:
|
|
46
|
+
"""Check if the value is present."""
|
|
47
|
+
if ctx():
|
|
48
|
+
return self._present._get_readonly_()
|
|
49
|
+
else:
|
|
50
|
+
return self._present._as_py_()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def is_nothing(self) -> bool:
|
|
54
|
+
"""Check if the value is empty."""
|
|
55
|
+
return not self.is_some
|
|
56
|
+
|
|
57
|
+
def get(self) -> T:
|
|
58
|
+
"""Get the value if present, otherwise raise an error."""
|
|
59
|
+
assert self.is_some
|
|
60
|
+
return self.get_unsafe()
|
|
61
|
+
|
|
62
|
+
@meta_fn
|
|
63
|
+
def get_unsafe(self) -> T:
|
|
64
|
+
return self._value
|
|
65
|
+
|
|
66
|
+
def map[R](self, fn: Callable[[T], R], /) -> Maybe[R]:
|
|
67
|
+
if self.is_some:
|
|
68
|
+
return Some(fn(self.get_unsafe()))
|
|
69
|
+
return Nothing
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def _accepts_(cls, value: Any) -> bool:
|
|
73
|
+
return isinstance(value, cls)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _accept_(cls, value: Any) -> Maybe[T]:
|
|
77
|
+
if not cls._accepts_(value):
|
|
78
|
+
raise TypeError(f"Cannot accept value of type {type(value).__name__} as {cls.__name__}.")
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def _is_py_(self) -> bool:
|
|
82
|
+
return not self._present or not isinstance(self._value, Value) or self._value._is_py_()
|
|
83
|
+
|
|
84
|
+
def _as_py_(self) -> Any:
|
|
85
|
+
if not self._is_py_():
|
|
86
|
+
raise ValueError("Not a python value")
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def _copy_from_(self, value: Any):
|
|
90
|
+
raise TypeError("Maybe does not support mutation.")
|
|
91
|
+
|
|
92
|
+
def _copy_(self) -> Maybe[T]:
|
|
93
|
+
raise TypeError("Maybe does not support copying.")
|
|
94
|
+
|
|
95
|
+
def _set_(self, value: Any):
|
|
96
|
+
if not self._accepts_(value):
|
|
97
|
+
raise TypeError(f"Cannot set value of type {type(value).__name__} to {self.__class__.__name__}.")
|
|
98
|
+
if value is not Nothing and self._value is not value._value:
|
|
99
|
+
raise TypeError(f"Cannot set value of type {type(value._value).__name__} to {self.__class__.__name__}.")
|
|
100
|
+
self._present._set_(value._present)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def _get_merge_target_(cls, values: list[Any]) -> Any:
|
|
104
|
+
if not all(isinstance(v, cls) for v in values):
|
|
105
|
+
return NotImplemented
|
|
106
|
+
distinct = []
|
|
107
|
+
seen_ids = set()
|
|
108
|
+
for v in values:
|
|
109
|
+
if v is Nothing:
|
|
110
|
+
continue
|
|
111
|
+
if id(v._value) not in seen_ids:
|
|
112
|
+
distinct.append(v)
|
|
113
|
+
seen_ids.add(id(v._value))
|
|
114
|
+
match distinct:
|
|
115
|
+
case []:
|
|
116
|
+
return Nothing
|
|
117
|
+
case [v]:
|
|
118
|
+
return Maybe(present=Num._alloc_(), value=v._value)
|
|
119
|
+
case _:
|
|
120
|
+
return NotImplemented
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def Some[T](value: T) -> Maybe[T]: # noqa: N802
|
|
124
|
+
"""Create a `Maybe` instance with a value.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
value: The contained value.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
A `Maybe` instance that contains the provided value.
|
|
131
|
+
"""
|
|
132
|
+
return Maybe(present=True, value=value)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Nothing: Maybe[Any] = Maybe(present=False, value=None) # type: ignore
|
|
136
|
+
|
|
137
|
+
# Note: has to come after the definition to hide the definition in the docs.
|
|
138
|
+
Nothing: Maybe[Any]
|
|
139
|
+
"""The empty `Maybe` instance."""
|
sonolus/script/num.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# type: ignore
|
|
1
2
|
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
import operator
|
|
4
5
|
from collections.abc import Callable, Iterable
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Self, TypeGuard, final, runtime_checkable
|
|
6
7
|
|
|
8
|
+
from sonolus.backend.blocks import BlockData
|
|
7
9
|
from sonolus.backend.ir import IRConst, IRExpr, IRGet, IRPureInstr, IRSet
|
|
8
10
|
from sonolus.backend.ops import Op
|
|
9
11
|
from sonolus.backend.place import BlockPlace
|
|
@@ -114,15 +116,28 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
114
116
|
|
|
115
117
|
def _get_(self) -> Self:
|
|
116
118
|
if ctx():
|
|
119
|
+
if isinstance(self.data, BlockPlace):
|
|
120
|
+
ctx().check_readable(self.data)
|
|
117
121
|
place = ctx().alloc(size=1)
|
|
122
|
+
ctx().add_statements(IRSet(place, self.ir()))
|
|
123
|
+
return Num(place)
|
|
124
|
+
else:
|
|
125
|
+
return Num(self.data)
|
|
126
|
+
|
|
127
|
+
def _get_readonly_(self) -> Self:
|
|
128
|
+
if ctx():
|
|
118
129
|
if isinstance(self.data, BlockPlace):
|
|
119
130
|
ctx().check_readable(self.data)
|
|
131
|
+
if isinstance(self.data.block, BlockData) and not ctx().is_writable(self.data):
|
|
132
|
+
# This block is immutable in the current callback, so no need to copy it in case it changes.
|
|
133
|
+
return Num(self.data)
|
|
134
|
+
place = ctx().alloc(size=1)
|
|
120
135
|
ctx().add_statements(IRSet(place, self.ir()))
|
|
121
136
|
return Num(place)
|
|
122
137
|
else:
|
|
123
138
|
return Num(self.data)
|
|
124
139
|
|
|
125
|
-
def _set_(self, value:
|
|
140
|
+
def _set_(self, value: Any):
|
|
126
141
|
value = Num._accept_(value)
|
|
127
142
|
if ctx():
|
|
128
143
|
match self.data:
|
|
@@ -136,7 +151,7 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
136
151
|
else:
|
|
137
152
|
self.data = value.data
|
|
138
153
|
|
|
139
|
-
def _copy_from_(self, value:
|
|
154
|
+
def _copy_from_(self, value: Any):
|
|
140
155
|
raise ValueError("Cannot assign to a number")
|
|
141
156
|
|
|
142
157
|
def _copy_(self) -> Self:
|
|
@@ -439,17 +454,17 @@ if TYPE_CHECKING:
|
|
|
439
454
|
|
|
440
455
|
@runtime_checkable
|
|
441
456
|
class Num[T](Protocol, int, bool, float):
|
|
442
|
-
def __add__(self, other: T, /) -> Num: ...
|
|
443
|
-
def __sub__(self, other: T, /) -> Num: ...
|
|
444
|
-
def __mul__(self, other: T, /) -> Num: ...
|
|
445
|
-
def __truediv__(self, other: T, /) -> Num: ...
|
|
446
|
-
def __floordiv__(self, other: T, /) -> Num: ...
|
|
447
|
-
def __mod__(self, other: T, /) -> Num: ...
|
|
448
|
-
def __pow__(self, other: T, /) -> Num: ...
|
|
449
|
-
|
|
450
|
-
def __neg__(self, /) -> Num: ...
|
|
451
|
-
def __pos__(self, /) -> Num: ...
|
|
452
|
-
def __abs__(self, /) -> Num: ...
|
|
457
|
+
def __add__(self, other: T, /) -> Num | int | bool | float: ...
|
|
458
|
+
def __sub__(self, other: T, /) -> Num | int | bool | float: ...
|
|
459
|
+
def __mul__(self, other: T, /) -> Num | int | bool | float: ...
|
|
460
|
+
def __truediv__(self, other: T, /) -> Num | int | bool | float: ...
|
|
461
|
+
def __floordiv__(self, other: T, /) -> Num | int | bool | float: ...
|
|
462
|
+
def __mod__(self, other: T, /) -> Num | int | bool | float: ...
|
|
463
|
+
def __pow__(self, other: T, /) -> Num | int | bool | float: ...
|
|
464
|
+
|
|
465
|
+
def __neg__(self, /) -> Num | int | bool | float: ...
|
|
466
|
+
def __pos__(self, /) -> Num | int | bool | float: ...
|
|
467
|
+
def __abs__(self, /) -> Num | int | bool | float: ...
|
|
453
468
|
|
|
454
469
|
def __eq__(self, other: Any, /) -> bool: ...
|
|
455
470
|
def __ne__(self, other: Any, /) -> bool: ...
|
|
@@ -461,10 +476,10 @@ if TYPE_CHECKING:
|
|
|
461
476
|
def __hash__(self, /) -> int: ...
|
|
462
477
|
|
|
463
478
|
@property
|
|
464
|
-
def real(self) -> Num: ...
|
|
479
|
+
def real(self) -> Num | int | bool | float: ...
|
|
465
480
|
|
|
466
481
|
@property
|
|
467
|
-
def imag(self) -> Num: ...
|
|
482
|
+
def imag(self) -> Num | int | bool | float: ...
|
|
468
483
|
else:
|
|
469
484
|
# Need to do this to satisfy type checkers (especially Pycharm)
|
|
470
485
|
_Num.__name__ = "Num"
|
sonolus/script/options.py
CHANGED
|
@@ -175,7 +175,7 @@ def select_option(
|
|
|
175
175
|
return _SelectOption(name, description, standard, advanced, scope, default, values)
|
|
176
176
|
|
|
177
177
|
|
|
178
|
-
type Options = NewType("Options", Any)
|
|
178
|
+
type Options = NewType("Options", Any) # type: ignore
|
|
179
179
|
type _OptionInfo = _SliderOption | _ToggleOption | _SelectOption
|
|
180
180
|
|
|
181
181
|
|
sonolus/script/particle.py
CHANGED
sonolus/script/pointer.py
CHANGED
|
@@ -6,7 +6,7 @@ from sonolus.script.num import Num, _is_num
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@meta_fn
|
|
9
|
-
def _deref[T: Value](block:
|
|
9
|
+
def _deref[T: Value](block: int, offset: int, type_: type[T]) -> T:
|
|
10
10
|
block = Num._accept_(block)
|
|
11
11
|
offset = Num._accept_(offset)
|
|
12
12
|
type_ = validate_value(type_)._as_py_()
|
sonolus/script/project.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Sequence
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from os import PathLike
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import ClassVar,
|
|
7
|
+
from typing import ClassVar, TypedDict
|
|
8
8
|
|
|
9
9
|
from sonolus.backend.optimize import optimize
|
|
10
10
|
from sonolus.backend.optimize.passes import CompilerPass
|
|
@@ -25,14 +25,23 @@ class Project:
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
27
|
engine: Engine,
|
|
28
|
-
levels:
|
|
28
|
+
levels: Iterable[Level] | Callable[[], Iterable[Level]] | None = None,
|
|
29
29
|
resources: PathLike | None = None,
|
|
30
30
|
):
|
|
31
31
|
self.engine = engine
|
|
32
|
-
|
|
32
|
+
match levels:
|
|
33
|
+
case Callable():
|
|
34
|
+
self._level_source = lazy_loader(levels)
|
|
35
|
+
case Iterable():
|
|
36
|
+
self._level_source = levels
|
|
37
|
+
case None:
|
|
38
|
+
self._level_source = []
|
|
39
|
+
case _:
|
|
40
|
+
raise TypeError(f"Invalid type for levels: {type(levels)}. Expected Iterable or Callable.")
|
|
41
|
+
self._levels = None
|
|
33
42
|
self.resources = Path(resources or "resources")
|
|
34
43
|
|
|
35
|
-
def with_levels(self, levels: list[Level]) ->
|
|
44
|
+
def with_levels(self, levels: list[Level]) -> Project:
|
|
36
45
|
"""Create a new project with the specified levels.
|
|
37
46
|
|
|
38
47
|
Args:
|
|
@@ -78,6 +87,16 @@ class Project:
|
|
|
78
87
|
|
|
79
88
|
return get_project_schema(self)
|
|
80
89
|
|
|
90
|
+
@property
|
|
91
|
+
def levels(self) -> list[Level]:
|
|
92
|
+
if self._levels is None:
|
|
93
|
+
self._levels = list(self._level_source)
|
|
94
|
+
return self._levels
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def lazy_loader(fn):
|
|
98
|
+
yield from fn()
|
|
99
|
+
|
|
81
100
|
|
|
82
101
|
class ProjectSchema(TypedDict):
|
|
83
102
|
archetypes: list[ArchetypeSchema]
|
sonolus/script/quad.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Protocol
|
|
3
|
+
from typing import Protocol
|
|
4
4
|
|
|
5
5
|
from sonolus.script.record import Record
|
|
6
6
|
from sonolus.script.values import zeros
|
|
@@ -57,7 +57,7 @@ class Quad(Record):
|
|
|
57
57
|
"""The center of the quad."""
|
|
58
58
|
return (self.bl + self.tr + self.tl + self.br) / 4
|
|
59
59
|
|
|
60
|
-
def translate(self, translation: Vec2, /) ->
|
|
60
|
+
def translate(self, translation: Vec2, /) -> Quad:
|
|
61
61
|
"""Translate the quad by the given translation and return a new quad."""
|
|
62
62
|
return Quad(
|
|
63
63
|
bl=self.bl + translation,
|
|
@@ -66,7 +66,7 @@ class Quad(Record):
|
|
|
66
66
|
br=self.br + translation,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
def scale(self, factor: Vec2, /) ->
|
|
69
|
+
def scale(self, factor: Vec2, /) -> Quad:
|
|
70
70
|
"""Scale the quad by the given factor about the origin and return a new quad."""
|
|
71
71
|
return Quad(
|
|
72
72
|
bl=self.bl * factor,
|
|
@@ -75,7 +75,7 @@ class Quad(Record):
|
|
|
75
75
|
br=self.br * factor,
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
def scale_about(self, factor: Vec2, /, pivot: Vec2) ->
|
|
78
|
+
def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Quad:
|
|
79
79
|
"""Scale the quad by the given factor about the given pivot and return a new quad."""
|
|
80
80
|
return Quad(
|
|
81
81
|
bl=(self.bl - pivot) * factor + pivot,
|
|
@@ -84,7 +84,7 @@ class Quad(Record):
|
|
|
84
84
|
br=(self.br - pivot) * factor + pivot,
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
-
def scale_centered(self, factor: Vec2, /) ->
|
|
87
|
+
def scale_centered(self, factor: Vec2, /) -> Quad:
|
|
88
88
|
"""Scale the quad by the given factor about its center and return a new quad."""
|
|
89
89
|
return Quad(
|
|
90
90
|
bl=self.bl * factor,
|
|
@@ -93,7 +93,7 @@ class Quad(Record):
|
|
|
93
93
|
br=self.br * factor,
|
|
94
94
|
).translate(self.center * (Vec2(1, 1) - factor))
|
|
95
95
|
|
|
96
|
-
def rotate(self, angle: float, /) ->
|
|
96
|
+
def rotate(self, angle: float, /) -> Quad:
|
|
97
97
|
"""Rotate the quad by the given angle about the origin and return a new quad.
|
|
98
98
|
|
|
99
99
|
Args:
|
|
@@ -114,7 +114,7 @@ class Quad(Record):
|
|
|
114
114
|
angle: float,
|
|
115
115
|
/,
|
|
116
116
|
pivot: Vec2,
|
|
117
|
-
) ->
|
|
117
|
+
) -> Quad:
|
|
118
118
|
"""Rotate the quad by the given angle about the given pivot and return a new quad.
|
|
119
119
|
|
|
120
120
|
Args:
|
|
@@ -131,7 +131,7 @@ class Quad(Record):
|
|
|
131
131
|
br=self.br.rotate_about(angle, pivot),
|
|
132
132
|
)
|
|
133
133
|
|
|
134
|
-
def rotate_centered(self, angle: float, /) ->
|
|
134
|
+
def rotate_centered(self, angle: float, /) -> Quad:
|
|
135
135
|
"""Rotate the quad by the given angle about its center and return a new quad.
|
|
136
136
|
|
|
137
137
|
Args:
|
|
@@ -142,7 +142,7 @@ class Quad(Record):
|
|
|
142
142
|
"""
|
|
143
143
|
return self.rotate_about(angle, self.center)
|
|
144
144
|
|
|
145
|
-
def permute(self, count: int = 1, /) ->
|
|
145
|
+
def permute(self, count: int = 1, /) -> Quad:
|
|
146
146
|
"""Perform a cyclic permutation of the quad's vertices and return a new quad.
|
|
147
147
|
|
|
148
148
|
On a square, this operation is equivalent to rotating the square counterclockwise 90 degrees `count` times.
|
|
@@ -269,7 +269,7 @@ class Rect(Record):
|
|
|
269
269
|
br=self.br,
|
|
270
270
|
)
|
|
271
271
|
|
|
272
|
-
def translate(self, translation: Vec2, /) ->
|
|
272
|
+
def translate(self, translation: Vec2, /) -> Rect:
|
|
273
273
|
"""Translate the rectangle by the given translation and return a new rectangle."""
|
|
274
274
|
return Rect(
|
|
275
275
|
t=self.t + translation.y,
|
|
@@ -278,7 +278,7 @@ class Rect(Record):
|
|
|
278
278
|
l=self.l + translation.x,
|
|
279
279
|
)
|
|
280
280
|
|
|
281
|
-
def scale(self, factor: Vec2, /) ->
|
|
281
|
+
def scale(self, factor: Vec2, /) -> Rect:
|
|
282
282
|
"""Scale the rectangle by the given factor about the origin and return a new rectangle."""
|
|
283
283
|
return Rect(
|
|
284
284
|
t=self.t * factor.y,
|
|
@@ -287,7 +287,7 @@ class Rect(Record):
|
|
|
287
287
|
l=self.l * factor.x,
|
|
288
288
|
)
|
|
289
289
|
|
|
290
|
-
def scale_about(self, factor: Vec2, /, pivot: Vec2) ->
|
|
290
|
+
def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Rect:
|
|
291
291
|
"""Scale the rectangle by the given factor about the given pivot and return a new rectangle."""
|
|
292
292
|
return Rect(
|
|
293
293
|
t=(self.t - pivot.y) * factor.y + pivot.y,
|
|
@@ -296,7 +296,7 @@ class Rect(Record):
|
|
|
296
296
|
l=(self.l - pivot.x) * factor.x + pivot.x,
|
|
297
297
|
)
|
|
298
298
|
|
|
299
|
-
def scale_centered(self, factor: Vec2, /) ->
|
|
299
|
+
def scale_centered(self, factor: Vec2, /) -> Rect:
|
|
300
300
|
"""Scale the rectangle by the given factor about its center and return a new rectangle."""
|
|
301
301
|
return Rect(
|
|
302
302
|
t=self.t * factor.y,
|
|
@@ -305,7 +305,7 @@ class Rect(Record):
|
|
|
305
305
|
l=self.l * factor.x,
|
|
306
306
|
).translate(self.center * (Vec2(1, 1) - factor))
|
|
307
307
|
|
|
308
|
-
def expand(self, expansion: Vec2, /) ->
|
|
308
|
+
def expand(self, expansion: Vec2, /) -> Rect:
|
|
309
309
|
"""Expand the rectangle by the given amount and return a new rectangle."""
|
|
310
310
|
return Rect(
|
|
311
311
|
t=self.t + expansion.y,
|
|
@@ -314,7 +314,7 @@ class Rect(Record):
|
|
|
314
314
|
l=self.l - expansion.x,
|
|
315
315
|
)
|
|
316
316
|
|
|
317
|
-
def shrink(self, shrinkage: Vec2, /) ->
|
|
317
|
+
def shrink(self, shrinkage: Vec2, /) -> Rect:
|
|
318
318
|
"""Shrink the rectangle by the given amount and return a new rectangle."""
|
|
319
319
|
return Rect(
|
|
320
320
|
t=self.t - shrinkage.y,
|