sonolus.py 0.3.0__py3-none-any.whl → 0.3.2__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/node.py +13 -5
- sonolus/backend/optimize/allocate.py +41 -4
- sonolus/backend/optimize/flow.py +24 -7
- sonolus/backend/optimize/optimize.py +2 -9
- sonolus/backend/utils.py +6 -1
- sonolus/backend/visitor.py +47 -14
- sonolus/build/cli.py +6 -1
- sonolus/build/engine.py +1 -1
- sonolus/script/archetype.py +27 -17
- sonolus/script/array.py +15 -5
- sonolus/script/array_like.py +5 -3
- sonolus/script/containers.py +3 -3
- sonolus/script/debug.py +66 -8
- sonolus/script/globals.py +17 -0
- sonolus/script/internal/builtin_impls.py +2 -3
- sonolus/script/internal/context.py +50 -0
- sonolus/script/internal/simulation_context.py +131 -0
- sonolus/script/internal/tuple_impl.py +15 -10
- sonolus/script/iterator.py +3 -2
- sonolus/script/num.py +13 -3
- sonolus/script/options.py +24 -1
- sonolus/script/quad.py +2 -0
- sonolus/script/record.py +22 -3
- sonolus/script/runtime.py +383 -0
- sonolus/script/stream.py +149 -17
- sonolus/script/transform.py +289 -0
- sonolus/script/values.py +9 -3
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/RECORD +32 -31
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/licenses/LICENSE +0 -0
sonolus/script/debug.py
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
from collections.abc import Callable, Sequence
|
|
2
2
|
from contextvars import ContextVar
|
|
3
|
-
from typing import Any, Never
|
|
3
|
+
from typing import Any, Literal, Never
|
|
4
4
|
|
|
5
5
|
from sonolus.backend.mode import Mode
|
|
6
6
|
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.backend.optimize.constant_evaluation import SparseConditionalConstantPropagation
|
|
8
|
+
from sonolus.backend.optimize.copy_coalesce import CopyCoalesce
|
|
9
|
+
from sonolus.backend.optimize.dead_code import (
|
|
10
|
+
AdvancedDeadCodeElimination,
|
|
11
|
+
DeadCodeElimination,
|
|
12
|
+
UnreachableCodeElimination,
|
|
13
|
+
)
|
|
7
14
|
from sonolus.backend.optimize.flow import cfg_to_mermaid
|
|
15
|
+
from sonolus.backend.optimize.inlining import InlineVars
|
|
8
16
|
from sonolus.backend.optimize.passes import CompilerPass, run_passes
|
|
9
|
-
from sonolus.backend.optimize.simplify import CoalesceFlow
|
|
10
|
-
from sonolus.
|
|
17
|
+
from sonolus.backend.optimize.simplify import CoalesceFlow, NormalizeSwitch, RewriteToSwitch
|
|
18
|
+
from sonolus.backend.optimize.ssa import FromSSA, ToSSA
|
|
19
|
+
from sonolus.script.internal.context import GlobalContextState, ReadOnlyMemory, ctx, set_ctx
|
|
11
20
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
12
21
|
from sonolus.script.internal.native import native_function
|
|
22
|
+
from sonolus.script.internal.simulation_context import SimulationContext
|
|
13
23
|
from sonolus.script.num import Num
|
|
14
24
|
|
|
15
25
|
debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
|
|
@@ -93,12 +103,60 @@ def terminate():
|
|
|
93
103
|
raise RuntimeError("Terminated")
|
|
94
104
|
|
|
95
105
|
|
|
96
|
-
def visualize_cfg(
|
|
106
|
+
def visualize_cfg(
|
|
107
|
+
fn: Callable[[], Any] | Callable[[type], Any],
|
|
108
|
+
/,
|
|
109
|
+
*,
|
|
110
|
+
mode: Mode = Mode.PLAY,
|
|
111
|
+
archetype: type | None = None,
|
|
112
|
+
archetypes: list[type] | None,
|
|
113
|
+
passes: Sequence[CompilerPass] | Literal["minimal", "basic", "standard"] = "basic",
|
|
114
|
+
) -> str:
|
|
97
115
|
from sonolus.build.compile import callback_to_cfg
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
match passes:
|
|
118
|
+
case "minimal":
|
|
119
|
+
passes = [
|
|
120
|
+
CoalesceFlow(),
|
|
121
|
+
]
|
|
122
|
+
case "basic":
|
|
123
|
+
passes = [
|
|
124
|
+
CoalesceFlow(),
|
|
125
|
+
UnreachableCodeElimination(),
|
|
126
|
+
AdvancedDeadCodeElimination(),
|
|
127
|
+
CoalesceFlow(),
|
|
128
|
+
]
|
|
129
|
+
case "standard":
|
|
130
|
+
passes = [
|
|
131
|
+
CoalesceFlow(),
|
|
132
|
+
UnreachableCodeElimination(),
|
|
133
|
+
DeadCodeElimination(),
|
|
134
|
+
ToSSA(),
|
|
135
|
+
SparseConditionalConstantPropagation(),
|
|
136
|
+
UnreachableCodeElimination(),
|
|
137
|
+
DeadCodeElimination(),
|
|
138
|
+
CoalesceFlow(),
|
|
139
|
+
InlineVars(),
|
|
140
|
+
DeadCodeElimination(),
|
|
141
|
+
RewriteToSwitch(),
|
|
142
|
+
FromSSA(),
|
|
143
|
+
CoalesceFlow(),
|
|
144
|
+
CopyCoalesce(),
|
|
145
|
+
AdvancedDeadCodeElimination(),
|
|
146
|
+
CoalesceFlow(),
|
|
147
|
+
NormalizeSwitch(),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
global_state = GlobalContextState(
|
|
151
|
+
mode,
|
|
152
|
+
{a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
|
|
153
|
+
ReadOnlyMemory(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
cfg = callback_to_cfg(global_state, fn, "", archetype=archetype)
|
|
103
157
|
cfg = run_passes(cfg, passes)
|
|
104
158
|
return cfg_to_mermaid(cfg)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def simulation_context() -> SimulationContext:
|
|
162
|
+
return SimulationContext()
|
sonolus/script/globals.py
CHANGED
|
@@ -5,6 +5,7 @@ from sonolus.backend.blocks import Block, PlayBlock, PreviewBlock, TutorialBlock
|
|
|
5
5
|
from sonolus.backend.mode import Mode
|
|
6
6
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
7
7
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
8
|
+
from sonolus.script.internal.simulation_context import sim_ctx
|
|
8
9
|
from sonolus.script.internal.value import Value
|
|
9
10
|
|
|
10
11
|
|
|
@@ -27,6 +28,11 @@ class _GlobalField(SonolusDescriptor):
|
|
|
27
28
|
if instance is None:
|
|
28
29
|
return self
|
|
29
30
|
|
|
31
|
+
if sim_ctx():
|
|
32
|
+
from sonolus.script.values import zeros
|
|
33
|
+
|
|
34
|
+
return sim_ctx().get_or_put_value((instance, self), lambda: zeros(self.type))
|
|
35
|
+
|
|
30
36
|
from sonolus.script.internal.context import ctx
|
|
31
37
|
|
|
32
38
|
info = owner._global_info_
|
|
@@ -38,6 +44,12 @@ class _GlobalField(SonolusDescriptor):
|
|
|
38
44
|
def __set__(self, instance, value):
|
|
39
45
|
from sonolus.script.internal.context import ctx
|
|
40
46
|
|
|
47
|
+
if sim_ctx():
|
|
48
|
+
from sonolus.script.values import zeros
|
|
49
|
+
|
|
50
|
+
sim_ctx().set_or_put_value((instance, self), lambda: zeros(self.type), value)
|
|
51
|
+
return
|
|
52
|
+
|
|
41
53
|
info = instance._global_info_
|
|
42
54
|
if not ctx():
|
|
43
55
|
raise RuntimeError("Global field access outside of compilation")
|
|
@@ -64,6 +76,11 @@ class _GlobalPlaceholder:
|
|
|
64
76
|
base = ctx().get_global_base(self)
|
|
65
77
|
return self.type._from_place_(base)
|
|
66
78
|
|
|
79
|
+
def _get_sim_replacement_(self):
|
|
80
|
+
from sonolus.script.values import zeros
|
|
81
|
+
|
|
82
|
+
return sim_ctx().get_or_put_value(self, lambda: zeros(self.type))
|
|
83
|
+
|
|
67
84
|
|
|
68
85
|
def _create_global(cls: type, blocks: dict[Mode, Block], offset: int | None):
|
|
69
86
|
if issubclass(cls, Value):
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
1
|
from collections.abc import Iterable
|
|
3
2
|
from typing import overload
|
|
4
3
|
|
|
@@ -32,7 +31,7 @@ def _isinstance(value, type_):
|
|
|
32
31
|
return isinstance(value, TupleImpl)
|
|
33
32
|
if type_ in {_int, _float, _bool}:
|
|
34
33
|
raise TypeError("Instance check against int, float, or bool is not supported, use Num instead")
|
|
35
|
-
if not (isinstance(type_, type) and issubclass(type_, Value
|
|
34
|
+
if not (isinstance(type_, type) and (issubclass(type_, Value) or getattr(type_, "_allow_instance_check_", False))):
|
|
36
35
|
raise TypeError(f"Unsupported type: {type_} for isinstance")
|
|
37
36
|
return validate_value(isinstance(value, type_))
|
|
38
37
|
|
|
@@ -87,7 +86,7 @@ def _zip(*iterables):
|
|
|
87
86
|
if any(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
88
87
|
if not all(isinstance(iterable, TupleImpl) for iterable in iterables):
|
|
89
88
|
raise TypeError("Cannot mix tuples with other types in zip")
|
|
90
|
-
return TupleImpl._accept_(tuple(zip(*(iterable.
|
|
89
|
+
return TupleImpl._accept_(tuple(zip(*(iterable.value for iterable in iterables), strict=False)))
|
|
91
90
|
iterators = [compile_and_call(iterable.__iter__) for iterable in iterables]
|
|
92
91
|
if not all(isinstance(iterator, SonolusIterator) for iterator in iterators):
|
|
93
92
|
raise TypeError("Only subclasses of SonolusIterator are supported as iterators")
|
|
@@ -19,6 +19,25 @@ _compiler_internal_ = True
|
|
|
19
19
|
context_var = ContextVar("context_var", default=None)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class DebugConfig:
|
|
24
|
+
unchecked_reads: bool
|
|
25
|
+
unchecked_writes: bool
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_full_debug_config = DebugConfig(
|
|
29
|
+
unchecked_reads=True,
|
|
30
|
+
unchecked_writes=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_disabled_debug_config = DebugConfig(
|
|
34
|
+
unchecked_reads=False,
|
|
35
|
+
unchecked_writes=False,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
debug_var = ContextVar("debug_var", default=_disabled_debug_config)
|
|
39
|
+
|
|
40
|
+
|
|
22
41
|
class GlobalContextState:
|
|
23
42
|
archetypes: dict[type, int]
|
|
24
43
|
rom: ReadOnlyMemory
|
|
@@ -92,12 +111,16 @@ class Context:
|
|
|
92
111
|
return self.callback_state.used_names
|
|
93
112
|
|
|
94
113
|
def check_readable(self, place: BlockPlace):
|
|
114
|
+
if debug_config().unchecked_reads:
|
|
115
|
+
return
|
|
95
116
|
if not self.callback:
|
|
96
117
|
return
|
|
97
118
|
if isinstance(place.block, BlockData) and self.callback not in self.blocks(place.block).readable:
|
|
98
119
|
raise RuntimeError(f"Block {place.block} is not readable in {self.callback}")
|
|
99
120
|
|
|
100
121
|
def check_writable(self, place: BlockPlace):
|
|
122
|
+
if debug_config().unchecked_writes:
|
|
123
|
+
return
|
|
101
124
|
if not self.callback:
|
|
102
125
|
return
|
|
103
126
|
if isinstance(place.block, BlockData) and self.callback not in self.blocks(place.block).writable:
|
|
@@ -238,6 +261,12 @@ class Context:
|
|
|
238
261
|
context.outgoing[None] = target
|
|
239
262
|
return target
|
|
240
263
|
|
|
264
|
+
def register_archetype(self, type_: type) -> int:
|
|
265
|
+
with self.global_state.lock:
|
|
266
|
+
if type_ not in self.global_state.archetypes:
|
|
267
|
+
self.global_state.archetypes[type_] = len(self.global_state.archetypes)
|
|
268
|
+
return self.global_state.archetypes[type_]
|
|
269
|
+
|
|
241
270
|
|
|
242
271
|
def ctx() -> Context | None:
|
|
243
272
|
return context_var.get()
|
|
@@ -283,6 +312,27 @@ class ReadOnlyMemory:
|
|
|
283
312
|
else:
|
|
284
313
|
return PlayBlock.EngineRom
|
|
285
314
|
|
|
315
|
+
def get_value(self, index: int) -> float:
|
|
316
|
+
with self._lock:
|
|
317
|
+
if index < 0 or index >= len(self.values):
|
|
318
|
+
raise IndexError(f"Index {index} out of bounds for ReadOnlyMemory")
|
|
319
|
+
return self.values[index]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@contextmanager
|
|
323
|
+
def enable_debug(config: DebugConfig | None = None):
|
|
324
|
+
if config is None:
|
|
325
|
+
config = _full_debug_config
|
|
326
|
+
token = debug_var.set(config)
|
|
327
|
+
try:
|
|
328
|
+
yield
|
|
329
|
+
finally:
|
|
330
|
+
debug_var.reset(token)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def debug_config() -> DebugConfig:
|
|
334
|
+
return debug_var.get()
|
|
335
|
+
|
|
286
336
|
|
|
287
337
|
@dataclass
|
|
288
338
|
class ValueBinding:
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from types import MethodType, ModuleType
|
|
5
|
+
from typing import Any, ClassVar, Self
|
|
6
|
+
|
|
7
|
+
from sonolus.script.internal.impl import validate_value
|
|
8
|
+
|
|
9
|
+
_MISSING = object()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimulationContext:
|
|
13
|
+
"""Context manager to simulate additional Sonolus runtime features like level memory."""
|
|
14
|
+
|
|
15
|
+
_active_context: ClassVar[Self | None] = None
|
|
16
|
+
additional_replacements: dict[Any, Any]
|
|
17
|
+
values: dict[Any, Any]
|
|
18
|
+
_original_values: dict[str, dict[str, Any]] # module_name: {var_name: original_value}
|
|
19
|
+
_original_import: Callable[[str, Any, Any, Any, Any], Any] | None
|
|
20
|
+
|
|
21
|
+
def __init__(self, *, additional_replacements: dict[Any, Any] | None = None):
|
|
22
|
+
if SimulationContext._active_context is not None:
|
|
23
|
+
raise RuntimeError("SimulationContext is already active")
|
|
24
|
+
SimulationContext._active_context = self
|
|
25
|
+
self.additional_replacements = additional_replacements or {}
|
|
26
|
+
self.values = {}
|
|
27
|
+
self._original_values = {}
|
|
28
|
+
self._original_import = None
|
|
29
|
+
|
|
30
|
+
def get_or_put_value(self, key: Any, factory: Callable[[], Any]) -> Any:
|
|
31
|
+
if key not in self.values:
|
|
32
|
+
self.values[key] = validate_value(factory())
|
|
33
|
+
return self.values[key]._get_()._as_py_()
|
|
34
|
+
|
|
35
|
+
def set_or_put_value(self, key: Any, factory: Callable[[], Any], value: Any):
|
|
36
|
+
if key not in self.values:
|
|
37
|
+
self.values[key] = validate_value(factory())
|
|
38
|
+
existing_value = self.values[key]
|
|
39
|
+
existing_type = type(existing_value)
|
|
40
|
+
value = existing_type._accept_(validate_value(value))
|
|
41
|
+
if existing_type._is_value_type_():
|
|
42
|
+
existing_value._set_(value)
|
|
43
|
+
else:
|
|
44
|
+
existing_value._copy_from_(value)
|
|
45
|
+
|
|
46
|
+
def _get_replacement(self, value: Any) -> Any:
|
|
47
|
+
try:
|
|
48
|
+
if value in self.additional_replacements:
|
|
49
|
+
return self.additional_replacements[value]
|
|
50
|
+
except TypeError:
|
|
51
|
+
pass
|
|
52
|
+
if hasattr(value, "_get_sim_replacement_") and isinstance(value._get_sim_replacement_, MethodType):
|
|
53
|
+
return value._get_sim_replacement_()
|
|
54
|
+
return _MISSING
|
|
55
|
+
|
|
56
|
+
def _substitute_module_variables(self, module) -> None:
|
|
57
|
+
if not isinstance(module, ModuleType):
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
module_name = module.__name__
|
|
61
|
+
|
|
62
|
+
if module_name in self._original_values:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
original_values = {}
|
|
66
|
+
|
|
67
|
+
for var_name, var_value in list(module.__dict__.items()):
|
|
68
|
+
replacement = self._get_replacement(var_value)
|
|
69
|
+
if replacement is not _MISSING:
|
|
70
|
+
original_values[var_name] = var_value
|
|
71
|
+
setattr(module, var_name, replacement)
|
|
72
|
+
|
|
73
|
+
if original_values:
|
|
74
|
+
self._original_values[module_name] = original_values
|
|
75
|
+
|
|
76
|
+
def _update_loaded_modules(self) -> None:
|
|
77
|
+
loaded_modules = list(sys.modules.values())
|
|
78
|
+
|
|
79
|
+
for module in loaded_modules:
|
|
80
|
+
if module is not None:
|
|
81
|
+
self._substitute_module_variables(module)
|
|
82
|
+
|
|
83
|
+
def _create_import_hook(self):
|
|
84
|
+
original_import = builtins.__import__
|
|
85
|
+
|
|
86
|
+
def hooked_import(name, globals=None, locals=None, fromlist=(), level=0): # noqa: A002
|
|
87
|
+
module = original_import(name, globals, locals, fromlist, level)
|
|
88
|
+
if isinstance(module, ModuleType):
|
|
89
|
+
self._substitute_module_variables(module)
|
|
90
|
+
|
|
91
|
+
if fromlist:
|
|
92
|
+
for item in fromlist:
|
|
93
|
+
if hasattr(module, item):
|
|
94
|
+
attr = getattr(module, item)
|
|
95
|
+
if isinstance(attr, ModuleType):
|
|
96
|
+
self._substitute_module_variables(attr)
|
|
97
|
+
|
|
98
|
+
return module
|
|
99
|
+
|
|
100
|
+
return hooked_import
|
|
101
|
+
|
|
102
|
+
def __enter__(self) -> Self:
|
|
103
|
+
self._update_loaded_modules()
|
|
104
|
+
|
|
105
|
+
self._original_import = builtins.__import__
|
|
106
|
+
builtins.__import__ = self._create_import_hook()
|
|
107
|
+
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any):
|
|
111
|
+
try:
|
|
112
|
+
if self._original_import is not None:
|
|
113
|
+
builtins.__import__ = self._original_import
|
|
114
|
+
self._original_import = None
|
|
115
|
+
|
|
116
|
+
for module_name, original_values in self._original_values.items():
|
|
117
|
+
if module_name in sys.modules:
|
|
118
|
+
module = sys.modules[module_name]
|
|
119
|
+
if isinstance(module, ModuleType):
|
|
120
|
+
for var_name, original_value in original_values.items():
|
|
121
|
+
setattr(module, var_name, original_value)
|
|
122
|
+
|
|
123
|
+
self._original_values.clear()
|
|
124
|
+
|
|
125
|
+
finally:
|
|
126
|
+
SimulationContext._active_context = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def sim_ctx() -> SimulationContext | None:
|
|
130
|
+
"""Get the current simulation context, or None if not active."""
|
|
131
|
+
return SimulationContext._active_context
|
|
@@ -30,7 +30,7 @@ class TupleImpl(TransientValue):
|
|
|
30
30
|
return len(self.value)
|
|
31
31
|
|
|
32
32
|
def __eq__(self, other):
|
|
33
|
-
if not
|
|
33
|
+
if not self._is_tuple_impl(other):
|
|
34
34
|
return False
|
|
35
35
|
if len(self) != len(other):
|
|
36
36
|
return False
|
|
@@ -40,7 +40,7 @@ class TupleImpl(TransientValue):
|
|
|
40
40
|
return True
|
|
41
41
|
|
|
42
42
|
def __ne__(self, other):
|
|
43
|
-
if not
|
|
43
|
+
if not self._is_tuple_impl(other):
|
|
44
44
|
return True
|
|
45
45
|
if len(self) != len(other):
|
|
46
46
|
return True
|
|
@@ -50,33 +50,33 @@ class TupleImpl(TransientValue):
|
|
|
50
50
|
return False
|
|
51
51
|
|
|
52
52
|
def __lt__(self, other):
|
|
53
|
-
if not
|
|
53
|
+
if not self._is_tuple_impl(other):
|
|
54
54
|
return NotImplemented
|
|
55
|
-
for a, b in zip(self
|
|
55
|
+
for a, b in zip(self, other):
|
|
56
56
|
if a != b:
|
|
57
57
|
return a < b
|
|
58
58
|
return len(self.value) < len(other.value)
|
|
59
59
|
|
|
60
60
|
def __le__(self, other):
|
|
61
|
-
if not
|
|
61
|
+
if not self._is_tuple_impl(other):
|
|
62
62
|
return NotImplemented
|
|
63
|
-
for a, b in zip(self
|
|
63
|
+
for a, b in zip(self, other):
|
|
64
64
|
if a != b:
|
|
65
65
|
return a < b
|
|
66
66
|
return len(self.value) <= len(other.value)
|
|
67
67
|
|
|
68
68
|
def __gt__(self, other):
|
|
69
|
-
if not
|
|
69
|
+
if not self._is_tuple_impl(other):
|
|
70
70
|
return NotImplemented
|
|
71
|
-
for a, b in zip(self
|
|
71
|
+
for a, b in zip(self, other):
|
|
72
72
|
if a != b:
|
|
73
73
|
return a > b
|
|
74
74
|
return len(self.value) > len(other.value)
|
|
75
75
|
|
|
76
76
|
def __ge__(self, other):
|
|
77
|
-
if not
|
|
77
|
+
if not self._is_tuple_impl(other):
|
|
78
78
|
return NotImplemented
|
|
79
|
-
for a, b in zip(self
|
|
79
|
+
for a, b in zip(self, other):
|
|
80
80
|
if a != b:
|
|
81
81
|
return a > b
|
|
82
82
|
return len(self.value) >= len(other.value)
|
|
@@ -89,6 +89,11 @@ class TupleImpl(TransientValue):
|
|
|
89
89
|
other = TupleImpl._accept_(other)
|
|
90
90
|
return TupleImpl._accept_(self.value + other.value)
|
|
91
91
|
|
|
92
|
+
@staticmethod
|
|
93
|
+
@meta_fn
|
|
94
|
+
def _is_tuple_impl(value: Any) -> bool:
|
|
95
|
+
return isinstance(value, TupleImpl)
|
|
96
|
+
|
|
92
97
|
@classmethod
|
|
93
98
|
def _accepts_(cls, value: Any) -> bool:
|
|
94
99
|
return isinstance(value, cls | tuple)
|
sonolus/script/iterator.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import abstractmethod
|
|
4
|
-
from collections.abc import Iterator
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
from sonolus.script.internal.impl import meta_fn
|
|
8
7
|
from sonolus.script.record import Record
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class SonolusIterator[T]
|
|
10
|
+
class SonolusIterator[T]:
|
|
12
11
|
"""Base class for Sonolus iterators.
|
|
13
12
|
|
|
14
13
|
This class is used to define custom iterators that can be used in Sonolus.py.
|
|
@@ -30,6 +29,8 @@ class SonolusIterator[T](Iterator[T]):
|
|
|
30
29
|
```
|
|
31
30
|
"""
|
|
32
31
|
|
|
32
|
+
_allow_instance_check_ = True
|
|
33
|
+
|
|
33
34
|
def next(self) -> T:
|
|
34
35
|
result = self.get()
|
|
35
36
|
self.advance()
|
sonolus/script/num.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# ruff: noqa: N801
|
|
2
1
|
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
import operator
|
|
@@ -81,12 +80,22 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
81
80
|
return value
|
|
82
81
|
return cls(value)
|
|
83
82
|
|
|
83
|
+
def _is_rom_constant(self) -> bool:
|
|
84
|
+
return (
|
|
85
|
+
ctx()
|
|
86
|
+
and isinstance(self.data, BlockPlace)
|
|
87
|
+
and self.data.block == ctx().blocks.EngineRom
|
|
88
|
+
and isinstance(self.data.index, int)
|
|
89
|
+
)
|
|
90
|
+
|
|
84
91
|
def _is_py_(self) -> bool:
|
|
85
|
-
return isinstance(self.data, float | int | bool)
|
|
92
|
+
return isinstance(self.data, float | int | bool) or self._is_rom_constant()
|
|
86
93
|
|
|
87
94
|
def _as_py_(self) -> Any:
|
|
88
95
|
if not self._is_py_():
|
|
89
96
|
raise ValueError("Not a compile time constant Num")
|
|
97
|
+
if self._is_rom_constant():
|
|
98
|
+
return ctx().rom.get_value(self.data.index + self.data.offset)
|
|
90
99
|
if self.data.is_integer():
|
|
91
100
|
return int(self.data)
|
|
92
101
|
return self.data
|
|
@@ -114,10 +123,11 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
114
123
|
return Num(self.data)
|
|
115
124
|
|
|
116
125
|
def _set_(self, value: Self):
|
|
126
|
+
value = Num._accept_(value)
|
|
117
127
|
if ctx():
|
|
118
128
|
match self.data:
|
|
119
129
|
case BackingValue():
|
|
120
|
-
|
|
130
|
+
self.data.write(value.ir())
|
|
121
131
|
case BlockPlace():
|
|
122
132
|
ctx().check_writable(self.data)
|
|
123
133
|
ctx().add_statements(IRSet(self.data, value.ir()))
|
sonolus/script/options.py
CHANGED
|
@@ -5,11 +5,13 @@ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
|
5
5
|
from sonolus.backend.mode import Mode
|
|
6
6
|
from sonolus.backend.place import BlockPlace
|
|
7
7
|
from sonolus.script.debug import assert_unreachable
|
|
8
|
-
from sonolus.script.internal.context import ctx
|
|
8
|
+
from sonolus.script.internal.context import ctx, debug_config
|
|
9
9
|
from sonolus.script.internal.descriptor import SonolusDescriptor
|
|
10
10
|
from sonolus.script.internal.generic import validate_concrete_type
|
|
11
11
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
12
|
+
from sonolus.script.internal.simulation_context import sim_ctx
|
|
12
13
|
from sonolus.script.num import Num
|
|
14
|
+
from sonolus.script.values import copy
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
@dataclass
|
|
@@ -186,6 +188,8 @@ class _OptionField(SonolusDescriptor):
|
|
|
186
188
|
self.index = index
|
|
187
189
|
|
|
188
190
|
def __get__(self, instance, owner):
|
|
191
|
+
if sim_ctx():
|
|
192
|
+
return sim_ctx().get_or_put_value((instance, self), lambda: copy(self.info.default))
|
|
189
193
|
if ctx():
|
|
190
194
|
match ctx().global_state.mode:
|
|
191
195
|
case Mode.PLAY:
|
|
@@ -202,8 +206,27 @@ class _OptionField(SonolusDescriptor):
|
|
|
202
206
|
return Num._from_place_(BlockPlace(block, self.index))
|
|
203
207
|
else:
|
|
204
208
|
return Num._accept_(self.info.default)
|
|
209
|
+
raise RuntimeError("Options can only be accessed in a context")
|
|
205
210
|
|
|
206
211
|
def __set__(self, instance, value):
|
|
212
|
+
if sim_ctx():
|
|
213
|
+
return sim_ctx().set_or_put_value((instance, self), lambda: copy(self.info.default), value)
|
|
214
|
+
if ctx() and debug_config().unchecked_writes:
|
|
215
|
+
match ctx().global_state.mode:
|
|
216
|
+
case Mode.PLAY:
|
|
217
|
+
block = ctx().blocks.LevelOption
|
|
218
|
+
case Mode.WATCH:
|
|
219
|
+
block = ctx().blocks.LevelOption
|
|
220
|
+
case Mode.PREVIEW:
|
|
221
|
+
block = ctx().blocks.PreviewOption
|
|
222
|
+
case Mode.TUTORIAL:
|
|
223
|
+
block = None
|
|
224
|
+
case _:
|
|
225
|
+
assert_unreachable()
|
|
226
|
+
if block is not None:
|
|
227
|
+
Num._from_place_(BlockPlace(block, self.index))._set_(Num._accept_(value))
|
|
228
|
+
else:
|
|
229
|
+
raise RuntimeError("Options in the current mode cannot be set and use the default value")
|
|
207
230
|
raise AttributeError("Options are read-only")
|
|
208
231
|
|
|
209
232
|
|
sonolus/script/quad.py
CHANGED
|
@@ -147,6 +147,8 @@ class Quad(Record):
|
|
|
147
147
|
def contains_point(self, point: Vec2, /) -> bool:
|
|
148
148
|
"""Check if the quad contains the given point.
|
|
149
149
|
|
|
150
|
+
It is not guaranteed whether points on the edges of the quad are considered inside or outside.
|
|
151
|
+
|
|
150
152
|
Args:
|
|
151
153
|
point: The point to check.
|
|
152
154
|
|
sonolus/script/record.py
CHANGED
|
@@ -20,8 +20,14 @@ from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
|
20
20
|
from sonolus.script.num import Num
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
class RecordMeta(type):
|
|
24
|
+
@meta_fn
|
|
25
|
+
def __pos__[T](cls: type[T]) -> T:
|
|
26
|
+
return cls._zero_()
|
|
27
|
+
|
|
28
|
+
|
|
23
29
|
@dataclass_transform(eq_default=True)
|
|
24
|
-
class Record(GenericValue):
|
|
30
|
+
class Record(GenericValue, metaclass=RecordMeta):
|
|
25
31
|
"""Base class for user-defined data structures.
|
|
26
32
|
|
|
27
33
|
Usage:
|
|
@@ -38,6 +44,15 @@ class Record(GenericValue):
|
|
|
38
44
|
field1: T
|
|
39
45
|
field2: U
|
|
40
46
|
```
|
|
47
|
+
|
|
48
|
+
Creating an instance:
|
|
49
|
+
```python
|
|
50
|
+
record = MyRecord(field1=42, field2=True)
|
|
51
|
+
record_2 = MyGenericRecord[int, int](field1=42, field2=100)
|
|
52
|
+
record_3 = MyGenericRecord(field1=42, field2=100) # Type arguments can be inferred
|
|
53
|
+
record_4 = +MyRecord # Create a zero-initialized record
|
|
54
|
+
record_5 = +MyGenericRecord[int, int]
|
|
55
|
+
```
|
|
41
56
|
"""
|
|
42
57
|
|
|
43
58
|
_value: dict[str, Value]
|
|
@@ -239,12 +254,12 @@ class Record(GenericValue):
|
|
|
239
254
|
|
|
240
255
|
def __str__(self):
|
|
241
256
|
return (
|
|
242
|
-
f"{self.__class__.__name__}({
|
|
257
|
+
f"{self.__class__.__name__}({', '.join(f'{field.name}={field.__get__(self)}' for field in self._fields)})"
|
|
243
258
|
)
|
|
244
259
|
|
|
245
260
|
def __repr__(self):
|
|
246
261
|
return (
|
|
247
|
-
f"{self.__class__.__name__}({
|
|
262
|
+
f"{self.__class__.__name__}({', '.join(f'{field.name}={field.__get__(self)!r}' for field in self._fields)})"
|
|
248
263
|
)
|
|
249
264
|
|
|
250
265
|
@meta_fn
|
|
@@ -268,6 +283,10 @@ class Record(GenericValue):
|
|
|
268
283
|
def __hash__(self):
|
|
269
284
|
return hash(tuple(field.__get__(self) for field in self._fields))
|
|
270
285
|
|
|
286
|
+
def __pos__(self) -> Self:
|
|
287
|
+
"""Return a copy of the record."""
|
|
288
|
+
return self._copy_()
|
|
289
|
+
|
|
271
290
|
@classmethod
|
|
272
291
|
@meta_fn
|
|
273
292
|
def type_var_value(cls, var: TypeVar, /) -> Any:
|