sonolus.py 0.9.3__py3-none-any.whl → 0.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/optimize/constant_evaluation.py +4 -2
- sonolus/backend/optimize/optimize.py +2 -0
- sonolus/backend/optimize/simplify.py +15 -0
- sonolus/backend/visitor.py +46 -21
- sonolus/build/cli.py +10 -8
- sonolus/build/compile.py +12 -11
- sonolus/build/dev_server.py +143 -32
- sonolus/build/engine.py +26 -24
- sonolus/build/project.py +8 -3
- sonolus/script/archetype.py +8 -8
- sonolus/script/array.py +33 -4
- sonolus/script/array_like.py +141 -26
- sonolus/script/bucket.py +1 -1
- sonolus/script/containers.py +40 -38
- sonolus/script/debug.py +56 -8
- sonolus/script/effect.py +5 -2
- sonolus/script/internal/builtin_impls.py +4 -4
- sonolus/script/internal/context.py +58 -27
- sonolus/script/internal/math_impls.py +19 -19
- sonolus/script/internal/native.py +7 -1
- sonolus/script/internal/range.py +3 -0
- sonolus/script/interval.py +6 -4
- sonolus/script/options.py +2 -2
- sonolus/script/particle.py +5 -2
- sonolus/script/project.py +2 -5
- sonolus/script/runtime.py +37 -37
- sonolus/script/sprite.py +6 -2
- sonolus/script/stream.py +3 -3
- sonolus/script/transform.py +1 -0
- sonolus/script/vec.py +1 -0
- {sonolus_py-0.9.3.dist-info → sonolus_py-0.10.1.dist-info}/METADATA +1 -1
- {sonolus_py-0.9.3.dist-info → sonolus_py-0.10.1.dist-info}/RECORD +35 -35
- {sonolus_py-0.9.3.dist-info → sonolus_py-0.10.1.dist-info}/WHEEL +0 -0
- {sonolus_py-0.9.3.dist-info → sonolus_py-0.10.1.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.9.3.dist-info → sonolus_py-0.10.1.dist-info}/licenses/LICENSE +0 -0
sonolus/script/bucket.py
CHANGED
|
@@ -156,7 +156,7 @@ class Bucket(Record):
|
|
|
156
156
|
"""The judgment window of the bucket."""
|
|
157
157
|
if not ctx():
|
|
158
158
|
raise RuntimeError("Bucket window access outside of compilation")
|
|
159
|
-
match ctx().
|
|
159
|
+
match ctx().mode_state.mode:
|
|
160
160
|
case Mode.PLAY:
|
|
161
161
|
return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
|
|
162
162
|
case Mode.WATCH:
|
sonolus/script/containers.py
CHANGED
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Self
|
|
4
4
|
|
|
5
|
-
from sonolus.backend.visitor import compile_and_call
|
|
6
5
|
from sonolus.script.array import Array
|
|
7
6
|
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
8
7
|
from sonolus.script.debug import error
|
|
@@ -144,11 +143,11 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
144
143
|
assert p == Pair(5, 6) # The value of p has changed
|
|
145
144
|
```
|
|
146
145
|
"""
|
|
147
|
-
return self._array
|
|
146
|
+
return self._array.get_unchecked(get_positive_index(item, self._size))
|
|
148
147
|
|
|
149
148
|
def __setitem__(self, key: int, value: T):
|
|
150
149
|
"""Update the element at the given index."""
|
|
151
|
-
self._array
|
|
150
|
+
self._array.set_unchecked(get_positive_index(key, self._size), value)
|
|
152
151
|
|
|
153
152
|
def __delitem__(self, key: int):
|
|
154
153
|
"""Remove the element at the given index."""
|
|
@@ -160,8 +159,8 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
160
159
|
Args:
|
|
161
160
|
value: The value to append.
|
|
162
161
|
"""
|
|
163
|
-
assert self._size < len(self._array)
|
|
164
|
-
self._array
|
|
162
|
+
assert self._size < len(self._array), "Array is full"
|
|
163
|
+
self._array.set_unchecked(self._size, value)
|
|
165
164
|
self._size += 1
|
|
166
165
|
|
|
167
166
|
def append_unchecked(self, value: T):
|
|
@@ -172,7 +171,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
172
171
|
Args:
|
|
173
172
|
value: The value to append.
|
|
174
173
|
"""
|
|
175
|
-
self._array
|
|
174
|
+
self._array.set_unchecked(self._size, value)
|
|
176
175
|
self._size += 1
|
|
177
176
|
|
|
178
177
|
def extend(self, values: ArrayLike[T]):
|
|
@@ -181,8 +180,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
181
180
|
Args:
|
|
182
181
|
values: The values to append.
|
|
183
182
|
"""
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
assert self._size + len(values) <= len(self._array), "Array is full"
|
|
184
|
+
i = 0
|
|
185
|
+
while i < len(values):
|
|
186
|
+
self._array.set_unchecked(self._size + i, values[i])
|
|
187
|
+
i += 1
|
|
188
|
+
self._size += len(values)
|
|
186
189
|
|
|
187
190
|
def pop(self, index: int | None = None) -> T:
|
|
188
191
|
"""Remove and return a copy of the value at the given index.
|
|
@@ -194,13 +197,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
194
197
|
"""
|
|
195
198
|
if index is None:
|
|
196
199
|
index = self._size - 1
|
|
197
|
-
index = get_positive_index(index,
|
|
198
|
-
|
|
199
|
-
value = copy(self._array[index])
|
|
200
|
+
index = get_positive_index(index, self._size)
|
|
201
|
+
value = copy(self._array.get_unchecked(index))
|
|
200
202
|
self._size -= 1
|
|
201
203
|
if index < self._size:
|
|
202
204
|
for i in range(index, self._size):
|
|
203
|
-
self._array
|
|
205
|
+
self._array.set_unchecked(i, self._array.get_unchecked(i + 1))
|
|
204
206
|
return value
|
|
205
207
|
|
|
206
208
|
def insert(self, index: int, value: T):
|
|
@@ -212,12 +214,12 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
212
214
|
index: The index at which to insert the value. Must be in the range [0, size].
|
|
213
215
|
value: The value to insert.
|
|
214
216
|
"""
|
|
215
|
-
index = clamp(get_positive_index(index,
|
|
216
|
-
assert self._size < len(self._array)
|
|
217
|
+
index = clamp(get_positive_index(index, self._size, include_end=True), 0, self._size)
|
|
218
|
+
assert self._size < len(self._array), "Array is full"
|
|
217
219
|
self._size += 1
|
|
218
220
|
for i in range(self._size - 1, index, -1):
|
|
219
|
-
self._array
|
|
220
|
-
self._array
|
|
221
|
+
self._array.set_unchecked(i, self._array.get_unchecked(i - 1))
|
|
222
|
+
self._array.set_unchecked(index, value)
|
|
221
223
|
|
|
222
224
|
def remove(self, value: T) -> bool:
|
|
223
225
|
"""Remove the first occurrence of the given value, returning whether the value was removed.
|
|
@@ -277,7 +279,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
277
279
|
if index < 0:
|
|
278
280
|
return False
|
|
279
281
|
if index < self._size - 1:
|
|
280
|
-
self._array
|
|
282
|
+
self._array.set_unchecked(index, self._array.get_unchecked(self._size - 1))
|
|
281
283
|
self._size -= 1
|
|
282
284
|
return True
|
|
283
285
|
|
|
@@ -293,7 +295,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
293
295
|
return False
|
|
294
296
|
i = 0
|
|
295
297
|
while i < len(self):
|
|
296
|
-
if self
|
|
298
|
+
if self.get_unchecked(i) != other.get_unchecked(i):
|
|
297
299
|
return False
|
|
298
300
|
i += 1
|
|
299
301
|
return True
|
|
@@ -329,12 +331,8 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
329
331
|
"""Return the type of the elements in the array."""
|
|
330
332
|
return cls.type_var_value(T)
|
|
331
333
|
|
|
332
|
-
def _check_index(self, index: int):
|
|
333
|
-
assert 0 <= index < self.size
|
|
334
|
-
|
|
335
334
|
@meta_fn
|
|
336
335
|
def _get_item(self, item: int) -> T:
|
|
337
|
-
item = get_positive_index(item, self.size)
|
|
338
336
|
if not ctx():
|
|
339
337
|
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
340
338
|
return _deref(
|
|
@@ -344,14 +342,18 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
344
342
|
self.element_type(),
|
|
345
343
|
)
|
|
346
344
|
|
|
345
|
+
def __getitem__(self, item) -> T:
|
|
346
|
+
return self.get_unchecked(get_positive_index(item, self.size))
|
|
347
|
+
|
|
348
|
+
def __setitem__(self, key: int, value: T):
|
|
349
|
+
self.set_unchecked(get_positive_index(key, self.size), value)
|
|
350
|
+
|
|
347
351
|
@meta_fn
|
|
348
|
-
def
|
|
349
|
-
compile_and_call(self._check_index, item)
|
|
352
|
+
def get_unchecked(self, item: int) -> T:
|
|
350
353
|
return self._get_item(item)._get_()
|
|
351
354
|
|
|
352
355
|
@meta_fn
|
|
353
|
-
def
|
|
354
|
-
compile_and_call(self._check_index, key)
|
|
356
|
+
def set_unchecked(self, key: int, value: T):
|
|
355
357
|
dst = self._get_item(key)
|
|
356
358
|
if self.element_type()._is_value_type_():
|
|
357
359
|
dst._set_(value)
|
|
@@ -519,7 +521,7 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
519
521
|
```
|
|
520
522
|
"""
|
|
521
523
|
for i in range(self._size):
|
|
522
|
-
entry = self._array
|
|
524
|
+
entry = self._array.get_unchecked(i)
|
|
523
525
|
if entry.key == key:
|
|
524
526
|
return entry.value
|
|
525
527
|
error()
|
|
@@ -535,12 +537,12 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
535
537
|
value: The value to associate with the key
|
|
536
538
|
"""
|
|
537
539
|
for i in range(self._size):
|
|
538
|
-
entry = self._array
|
|
540
|
+
entry = self._array.get_unchecked(i)
|
|
539
541
|
if entry.key == key:
|
|
540
542
|
entry.value = value
|
|
541
543
|
return
|
|
542
|
-
assert self._size < self.capacity()
|
|
543
|
-
self._array
|
|
544
|
+
assert self._size < self.capacity(), "Map is full"
|
|
545
|
+
self._array.set_unchecked(self._size, _ArrayMapEntry(key, value))
|
|
544
546
|
self._size += 1
|
|
545
547
|
|
|
546
548
|
def __delitem__(self, key: K):
|
|
@@ -552,11 +554,11 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
552
554
|
key: The key to remove
|
|
553
555
|
"""
|
|
554
556
|
for i in range(self._size):
|
|
555
|
-
entry = self._array
|
|
557
|
+
entry = self._array.get_unchecked(i)
|
|
556
558
|
if entry.key == key:
|
|
557
559
|
self._size -= 1
|
|
558
560
|
if i < self._size:
|
|
559
|
-
self._array
|
|
561
|
+
self._array.set_unchecked(i, self._array.get_unchecked(self._size))
|
|
560
562
|
return
|
|
561
563
|
error()
|
|
562
564
|
|
|
@@ -570,7 +572,7 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
570
572
|
True if the key is present, False otherwise.
|
|
571
573
|
"""
|
|
572
574
|
for i in range(self._size): # noqa: SIM110
|
|
573
|
-
if self._array
|
|
575
|
+
if self._array.get_unchecked(i).key == key:
|
|
574
576
|
return True
|
|
575
577
|
return False
|
|
576
578
|
|
|
@@ -586,12 +588,12 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
586
588
|
The value associated with the key
|
|
587
589
|
"""
|
|
588
590
|
for i in range(self._size):
|
|
589
|
-
entry = self._array
|
|
591
|
+
entry = self._array.get_unchecked(i)
|
|
590
592
|
if entry.key == key:
|
|
591
593
|
value = copy(entry.value)
|
|
592
594
|
self._size -= 1
|
|
593
595
|
if i < self._size:
|
|
594
|
-
self._array
|
|
596
|
+
self._array.set_unchecked(i, self._array.get_unchecked(self._size))
|
|
595
597
|
return value
|
|
596
598
|
error()
|
|
597
599
|
|
|
@@ -606,7 +608,7 @@ class _ArrayMapKeyIterator[K, V, Capacity](Record, SonolusIterator):
|
|
|
606
608
|
|
|
607
609
|
def next(self) -> Maybe[K]:
|
|
608
610
|
if self._index < len(self._map):
|
|
609
|
-
key = self._map._array
|
|
611
|
+
key = self._map._array.get_unchecked(self._index).key
|
|
610
612
|
self._index += 1
|
|
611
613
|
return Some(key)
|
|
612
614
|
return Nothing
|
|
@@ -618,7 +620,7 @@ class _ArrayMapValueIterator[K, V, Capacity](Record, SonolusIterator):
|
|
|
618
620
|
|
|
619
621
|
def next(self) -> Maybe[V]:
|
|
620
622
|
if self._index < len(self._map):
|
|
621
|
-
value = self._map._array
|
|
623
|
+
value = self._map._array.get_unchecked(self._index).value
|
|
622
624
|
self._index += 1
|
|
623
625
|
return Some(value)
|
|
624
626
|
return Nothing
|
|
@@ -630,7 +632,7 @@ class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
|
|
|
630
632
|
|
|
631
633
|
def next(self) -> Maybe[tuple[K, V]]:
|
|
632
634
|
if self._index < len(self._map):
|
|
633
|
-
entry = self._map._array
|
|
635
|
+
entry = self._map._array.get_unchecked(self._index)
|
|
634
636
|
result = (entry.key, entry.value)
|
|
635
637
|
self._index += 1
|
|
636
638
|
return Some(result)
|
sonolus/script/debug.py
CHANGED
|
@@ -7,7 +7,7 @@ from sonolus.backend.ops import Op
|
|
|
7
7
|
from sonolus.backend.optimize.flow import cfg_to_mermaid
|
|
8
8
|
from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig, run_passes
|
|
9
9
|
from sonolus.backend.optimize.simplify import RenumberVars
|
|
10
|
-
from sonolus.script.internal.context import
|
|
10
|
+
from sonolus.script.internal.context import ModeContextState, ProjectContextState, ctx, set_ctx
|
|
11
11
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
12
12
|
from sonolus.script.internal.native import native_function
|
|
13
13
|
from sonolus.script.internal.simulation_context import SimulationContext
|
|
@@ -18,17 +18,23 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
|
|
|
18
18
|
|
|
19
19
|
@meta_fn
|
|
20
20
|
def error(message: str | None = None) -> Never: # type: ignore
|
|
21
|
-
"""Raise an error.
|
|
21
|
+
"""Raise an error, and if in a dev build, log a message and pause the game.
|
|
22
22
|
|
|
23
23
|
This function is used to raise an error during runtime.
|
|
24
24
|
When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
|
|
25
|
+
|
|
26
|
+
In non-dev builds, this function will terminate the current callback silently.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
message: The message to log.
|
|
25
30
|
"""
|
|
26
31
|
message = validate_value(message)._as_py_() or "Error" # type: ignore
|
|
27
32
|
if not isinstance(message, str):
|
|
28
33
|
raise ValueError("Expected a string")
|
|
29
34
|
if ctx():
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
if ctx().project_state.dev:
|
|
36
|
+
debug_log(ctx().map_debug_message(message))
|
|
37
|
+
debug_pause()
|
|
32
38
|
terminate()
|
|
33
39
|
else:
|
|
34
40
|
raise RuntimeError(message)
|
|
@@ -40,6 +46,9 @@ def static_error(message: str | None = None) -> Never:
|
|
|
40
46
|
|
|
41
47
|
This function is used to raise an error during compile-time if the compiler cannot guarantee that
|
|
42
48
|
this function will not be called during runtime.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: The message to log.
|
|
43
52
|
"""
|
|
44
53
|
message = validate_value(message)._as_py_() or "Error" # type: ignore
|
|
45
54
|
if not isinstance(message, str):
|
|
@@ -69,7 +78,39 @@ def debug_pause():
|
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
@meta_fn
|
|
72
|
-
def
|
|
81
|
+
def notify(message: str):
|
|
82
|
+
"""Log a code that can be decoded by the dev server and pause the game if in debug mode and in a dev build.
|
|
83
|
+
|
|
84
|
+
Does nothing if not a dev build.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
message: The message to log.
|
|
88
|
+
"""
|
|
89
|
+
message = validate_value(message)._as_py_() # type: ignore
|
|
90
|
+
if not isinstance(message, str):
|
|
91
|
+
raise ValueError("Expected a string")
|
|
92
|
+
if ctx():
|
|
93
|
+
if ctx().project_state.dev:
|
|
94
|
+
debug_log(ctx().map_debug_message(message))
|
|
95
|
+
debug_pause()
|
|
96
|
+
else:
|
|
97
|
+
print(f"[NOTIFY] {message}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@meta_fn
|
|
101
|
+
def require(value: int | float | bool, message: str | None = None):
|
|
102
|
+
"""Require a condition to be true, or raise an error.
|
|
103
|
+
|
|
104
|
+
Similar to assert, but does not get stripped in non-dev builds.
|
|
105
|
+
|
|
106
|
+
If in a dev build, this function will log a message and pause the game if the condition is false.
|
|
107
|
+
|
|
108
|
+
In non-dev builds, this function will terminate the current callback silently if the condition is false.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
value: The condition to check.
|
|
112
|
+
message: The message to log if the condition is false.
|
|
113
|
+
"""
|
|
73
114
|
if not ctx():
|
|
74
115
|
if not value:
|
|
75
116
|
raise AssertionError(message if message is not None else "Assertion failed")
|
|
@@ -91,6 +132,13 @@ def assert_true(value: int | float | bool, message: str | None = None):
|
|
|
91
132
|
set_ctx(t_branch)
|
|
92
133
|
|
|
93
134
|
|
|
135
|
+
@meta_fn
|
|
136
|
+
def assert_true(value: int | float | bool, message: str | None = None):
|
|
137
|
+
if ctx() and not ctx().project_state.dev:
|
|
138
|
+
return
|
|
139
|
+
require(value, message)
|
|
140
|
+
|
|
141
|
+
|
|
94
142
|
def assert_false(value: int | float | bool, message: str | None = None):
|
|
95
143
|
assert_true(not value, message)
|
|
96
144
|
|
|
@@ -156,13 +204,13 @@ def visualize_cfg(
|
|
|
156
204
|
RenumberVars(),
|
|
157
205
|
]
|
|
158
206
|
|
|
159
|
-
|
|
207
|
+
project_state = ProjectContextState()
|
|
208
|
+
mode_state = ModeContextState(
|
|
160
209
|
mode,
|
|
161
210
|
{a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
|
|
162
|
-
ReadOnlyMemory(),
|
|
163
211
|
)
|
|
164
212
|
|
|
165
|
-
cfg = callback_to_cfg(
|
|
213
|
+
cfg = callback_to_cfg(project_state, mode_state, fn, callback, archetype=archetype) # type: ignore
|
|
166
214
|
cfg = run_passes(cfg, passes, OptimizerConfig(mode=mode))
|
|
167
215
|
return cfg_to_mermaid(cfg)
|
|
168
216
|
|
sonolus/script/effect.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
6
6
|
|
|
7
7
|
from sonolus.backend.ops import Op
|
|
8
|
-
from sonolus.script.array_like import ArrayLike
|
|
8
|
+
from sonolus.script.array_like import ArrayLike, check_positive_index
|
|
9
9
|
from sonolus.script.debug import static_error
|
|
10
10
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
11
11
|
from sonolus.script.internal.native import native_function
|
|
@@ -111,7 +111,10 @@ class EffectGroup(Record, ArrayLike[Effect]):
|
|
|
111
111
|
return self.size
|
|
112
112
|
|
|
113
113
|
def __getitem__(self, index: int) -> Effect:
|
|
114
|
-
|
|
114
|
+
check_positive_index(index, self.size)
|
|
115
|
+
return Effect(self.start_id + index)
|
|
116
|
+
|
|
117
|
+
def get_unchecked(self, index: int) -> Effect:
|
|
115
118
|
return Effect(self.start_id + index)
|
|
116
119
|
|
|
117
120
|
def __setitem__(self, index: int, value: Effect) -> None:
|
|
@@ -3,7 +3,7 @@ from typing import Never, assert_never
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
4
|
from sonolus.script.array import Array
|
|
5
5
|
from sonolus.script.array_like import ArrayLike
|
|
6
|
-
from sonolus.script.debug import error
|
|
6
|
+
from sonolus.script.debug import error, require
|
|
7
7
|
from sonolus.script.internal.context import ctx
|
|
8
8
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
9
9
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
@@ -192,7 +192,7 @@ def _max_num_iterator(iterable, default, key):
|
|
|
192
192
|
iterator = iterable.__iter__() # noqa: PLC2801
|
|
193
193
|
initial = iterator.next()
|
|
194
194
|
if initial.is_nothing:
|
|
195
|
-
|
|
195
|
+
require(default is not None, "default must be provided if the iterator is empty")
|
|
196
196
|
return default
|
|
197
197
|
if key is not None:
|
|
198
198
|
result = initial.get_unsafe()
|
|
@@ -285,7 +285,7 @@ def _min_num_iterator(iterable, default, key):
|
|
|
285
285
|
iterator = iterable.__iter__() # noqa: PLC2801
|
|
286
286
|
initial = iterator.next()
|
|
287
287
|
if initial.is_nothing:
|
|
288
|
-
|
|
288
|
+
require(default is not None, "default must be provided if the iterator is empty")
|
|
289
289
|
return default
|
|
290
290
|
if key is not None:
|
|
291
291
|
result = initial.get_unsafe()
|
|
@@ -371,7 +371,7 @@ def _sum(iterable, /, start=0):
|
|
|
371
371
|
|
|
372
372
|
|
|
373
373
|
def _next(iterator):
|
|
374
|
-
|
|
374
|
+
require(isinstance(iterator, SonolusIterator), "Only subclasses of SonolusIterator are supported as iterators")
|
|
375
375
|
value = iterator.next()
|
|
376
376
|
if value.is_some:
|
|
377
377
|
return value.get_unsafe()
|
|
@@ -39,19 +39,38 @@ _disabled_debug_config = DebugConfig(
|
|
|
39
39
|
debug_var = ContextVar("debug_var", default=_disabled_debug_config)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
class
|
|
42
|
+
class ProjectContextState:
|
|
43
|
+
rom: ReadOnlyMemory
|
|
44
|
+
const_mappings: dict[Any, int]
|
|
45
|
+
debug_str_mappings: dict[str, int]
|
|
46
|
+
lock: Lock
|
|
47
|
+
dev: bool
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
rom: ReadOnlyMemory | None = None,
|
|
52
|
+
const_mappings: dict[Any, int] | None = None,
|
|
53
|
+
debug_str_mappings: dict[str, int] | None = None,
|
|
54
|
+
dev: bool = False,
|
|
55
|
+
):
|
|
56
|
+
self.rom = ReadOnlyMemory() if rom is None else rom
|
|
57
|
+
self.const_mappings = {} if const_mappings is None else const_mappings
|
|
58
|
+
self.debug_str_mappings = {} if debug_str_mappings is None else debug_str_mappings
|
|
59
|
+
self.lock = Lock()
|
|
60
|
+
self.dev = dev
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ModeContextState:
|
|
43
64
|
archetypes: dict[type, int]
|
|
44
65
|
archetypes_by_name: dict[str, type]
|
|
45
66
|
keys_by_archetype_id: Sequence[int]
|
|
46
67
|
is_scored_by_archetype_id: Sequence[bool]
|
|
47
|
-
rom: ReadOnlyMemory
|
|
48
|
-
const_mappings: dict[Any, int]
|
|
49
68
|
environment_mappings: dict[_GlobalInfo, int]
|
|
50
69
|
environment_offsets: dict[Block, int]
|
|
51
70
|
mode: Mode
|
|
52
71
|
lock: Lock
|
|
53
72
|
|
|
54
|
-
def __init__(self, mode: Mode, archetypes: dict[type, int] | None = None
|
|
73
|
+
def __init__(self, mode: Mode, archetypes: dict[type, int] | None = None):
|
|
55
74
|
from sonolus.script.array import Array
|
|
56
75
|
|
|
57
76
|
self.archetypes = archetypes or {}
|
|
@@ -65,8 +84,6 @@ class GlobalContextState:
|
|
|
65
84
|
if archetypes
|
|
66
85
|
else Array[bool, Literal[0]]()
|
|
67
86
|
)
|
|
68
|
-
self.rom = ReadOnlyMemory() if rom is None else rom
|
|
69
|
-
self.const_mappings = {}
|
|
70
87
|
self.environment_mappings = {}
|
|
71
88
|
self.environment_offsets = {}
|
|
72
89
|
self.mode = mode
|
|
@@ -76,16 +93,19 @@ class GlobalContextState:
|
|
|
76
93
|
class CallbackContextState:
|
|
77
94
|
callback: str
|
|
78
95
|
used_names: dict[str, int]
|
|
96
|
+
debug_stack: list[str]
|
|
79
97
|
no_eval: bool
|
|
80
98
|
|
|
81
99
|
def __init__(self, callback: str, no_eval: bool = False):
|
|
82
100
|
self.callback = callback
|
|
83
101
|
self.used_names = {}
|
|
102
|
+
self.debug_stack = []
|
|
84
103
|
self.no_eval = no_eval
|
|
85
104
|
|
|
86
105
|
|
|
87
106
|
class Context:
|
|
88
|
-
|
|
107
|
+
project_state: ProjectContextState
|
|
108
|
+
mode_state: ModeContextState
|
|
89
109
|
callback_state: CallbackContextState
|
|
90
110
|
statements: list[IRStmt]
|
|
91
111
|
test: IRExpr
|
|
@@ -96,13 +116,15 @@ class Context:
|
|
|
96
116
|
|
|
97
117
|
def __init__(
|
|
98
118
|
self,
|
|
99
|
-
|
|
119
|
+
project_state: ProjectContextState,
|
|
120
|
+
mode_state: ModeContextState,
|
|
100
121
|
callback_state: CallbackContextState,
|
|
101
122
|
scope: Scope | None = None,
|
|
102
123
|
live: bool = True,
|
|
103
124
|
foldable_constants: dict[TempBlock, list[float | None]] | None = None,
|
|
104
125
|
):
|
|
105
|
-
self.
|
|
126
|
+
self.project_state = project_state
|
|
127
|
+
self.mode_state = mode_state
|
|
106
128
|
self.callback_state = callback_state
|
|
107
129
|
self.statements = []
|
|
108
130
|
self.test = IRConst(0)
|
|
@@ -114,11 +136,11 @@ class Context:
|
|
|
114
136
|
|
|
115
137
|
@property
|
|
116
138
|
def rom(self) -> ReadOnlyMemory:
|
|
117
|
-
return self.
|
|
139
|
+
return self.project_state.rom
|
|
118
140
|
|
|
119
141
|
@property
|
|
120
142
|
def blocks(self) -> type[Block]:
|
|
121
|
-
return self.
|
|
143
|
+
return self.mode_state.mode.blocks
|
|
122
144
|
|
|
123
145
|
@property
|
|
124
146
|
def callback(self) -> str:
|
|
@@ -182,7 +204,8 @@ class Context:
|
|
|
182
204
|
|
|
183
205
|
def copy_with_scope(self, scope: Scope) -> Context:
|
|
184
206
|
return Context(
|
|
185
|
-
|
|
207
|
+
project_state=self.project_state,
|
|
208
|
+
mode_state=self.mode_state,
|
|
186
209
|
callback_state=self.callback_state,
|
|
187
210
|
scope=scope,
|
|
188
211
|
live=self.live,
|
|
@@ -267,25 +290,33 @@ class Context:
|
|
|
267
290
|
)
|
|
268
291
|
|
|
269
292
|
def map_constant(self, value: Any) -> int:
|
|
270
|
-
with self.
|
|
271
|
-
const_mappings = self.
|
|
293
|
+
with self.project_state.lock:
|
|
294
|
+
const_mappings = self.project_state.const_mappings
|
|
272
295
|
if value not in const_mappings:
|
|
273
296
|
const_mappings[value] = len(const_mappings)
|
|
274
297
|
return const_mappings[value]
|
|
275
298
|
|
|
299
|
+
def map_debug_message(self, message: str) -> int:
|
|
300
|
+
with self.project_state.lock:
|
|
301
|
+
message_with_trace = "\n".join([*self.callback_state.debug_stack, message])
|
|
302
|
+
debug_str_mappings = self.project_state.debug_str_mappings
|
|
303
|
+
if message_with_trace not in debug_str_mappings:
|
|
304
|
+
debug_str_mappings[message_with_trace] = len(debug_str_mappings) + 1
|
|
305
|
+
return debug_str_mappings[message_with_trace]
|
|
306
|
+
|
|
276
307
|
def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
|
|
277
|
-
with self.
|
|
278
|
-
block = value.blocks.get(self.
|
|
308
|
+
with self.mode_state.lock:
|
|
309
|
+
block = value.blocks.get(self.mode_state.mode)
|
|
279
310
|
if block is None:
|
|
280
|
-
raise RuntimeError(f"Global {value} is not available in '{self.
|
|
281
|
-
if value not in self.
|
|
311
|
+
raise RuntimeError(f"Global {value} is not available in '{self.mode_state.mode.name}' mode")
|
|
312
|
+
if value not in self.mode_state.environment_mappings:
|
|
282
313
|
if value.offset is None:
|
|
283
|
-
offset = self.
|
|
284
|
-
self.
|
|
285
|
-
self.
|
|
314
|
+
offset = self.mode_state.environment_offsets.get(block, 0)
|
|
315
|
+
self.mode_state.environment_mappings[value] = offset
|
|
316
|
+
self.mode_state.environment_offsets[block] = offset + value.size
|
|
286
317
|
else:
|
|
287
|
-
self.
|
|
288
|
-
return BlockPlace(block, self.
|
|
318
|
+
self.mode_state.environment_mappings[value] = value.offset
|
|
319
|
+
return BlockPlace(block, self.mode_state.environment_mappings[value])
|
|
289
320
|
|
|
290
321
|
@classmethod
|
|
291
322
|
def meet(cls, contexts: list[Context]) -> Context:
|
|
@@ -303,10 +334,10 @@ class Context:
|
|
|
303
334
|
return target
|
|
304
335
|
|
|
305
336
|
def register_archetype(self, type_: type) -> int:
|
|
306
|
-
with self.
|
|
307
|
-
if type_ not in self.
|
|
308
|
-
self.
|
|
309
|
-
return self.
|
|
337
|
+
with self.mode_state.lock:
|
|
338
|
+
if type_ not in self.mode_state.archetypes:
|
|
339
|
+
self.mode_state.archetypes[type_] = len(self.mode_state.archetypes)
|
|
340
|
+
return self.mode_state.archetypes[type_]
|
|
310
341
|
|
|
311
342
|
|
|
312
343
|
def ctx() -> Context | Any: # Using Any to silence type checker warnings if it's None
|