sonolus.py 0.7.1__py3-none-any.whl → 0.9.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/ir.py +32 -2
- sonolus/backend/optimize/copy_coalesce.py +5 -1
- sonolus/backend/optimize/flow.py +14 -0
- sonolus/backend/optimize/inlining.py +1 -1
- sonolus/backend/optimize/liveness.py +6 -4
- sonolus/backend/visitor.py +32 -14
- sonolus/build/cli.py +18 -50
- sonolus/build/collection.py +2 -0
- sonolus/build/compile.py +49 -5
- sonolus/build/dev_server.py +222 -0
- sonolus/build/engine.py +23 -2
- sonolus/build/project.py +13 -4
- sonolus/script/array.py +2 -1
- sonolus/script/bucket.py +11 -9
- sonolus/script/debug.py +52 -5
- sonolus/script/effect.py +66 -9
- sonolus/script/internal/impl.py +8 -4
- sonolus/script/interval.py +17 -6
- sonolus/script/num.py +10 -6
- sonolus/script/particle.py +68 -9
- sonolus/script/project.py +15 -2
- sonolus/script/quad.py +95 -2
- sonolus/script/record.py +9 -0
- sonolus/script/runtime.py +4 -3
- sonolus/script/sprite.py +73 -10
- sonolus/script/vec.py +31 -14
- {sonolus_py-0.7.1.dist-info → sonolus_py-0.9.0.dist-info}/METADATA +1 -1
- {sonolus_py-0.7.1.dist-info → sonolus_py-0.9.0.dist-info}/RECORD +31 -30
- {sonolus_py-0.7.1.dist-info → sonolus_py-0.9.0.dist-info}/WHEEL +0 -0
- {sonolus_py-0.7.1.dist-info → sonolus_py-0.9.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.7.1.dist-info → sonolus_py-0.9.0.dist-info}/licenses/LICENSE +0 -0
sonolus/build/engine.py
CHANGED
|
@@ -9,7 +9,7 @@ from dataclasses import dataclass
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
from sonolus.backend.mode import Mode
|
|
12
|
-
from sonolus.build.compile import compile_mode
|
|
12
|
+
from sonolus.build.compile import CompileCache, compile_mode
|
|
13
13
|
from sonolus.script.archetype import _BaseArchetype
|
|
14
14
|
from sonolus.script.bucket import Buckets
|
|
15
15
|
from sonolus.script.effect import Effects
|
|
@@ -68,7 +68,11 @@ def no_gil() -> bool:
|
|
|
68
68
|
def package_engine(
|
|
69
69
|
engine: EngineData,
|
|
70
70
|
config: BuildConfig | None = None,
|
|
71
|
+
cache: CompileCache | None = None,
|
|
71
72
|
):
|
|
73
|
+
if cache is None:
|
|
74
|
+
cache = CompileCache()
|
|
75
|
+
|
|
72
76
|
config = config or BuildConfig()
|
|
73
77
|
rom = ReadOnlyMemory()
|
|
74
78
|
configuration = build_engine_configuration(engine.options, engine.ui)
|
|
@@ -76,7 +80,8 @@ def package_engine(
|
|
|
76
80
|
# process_cpu_count is available in Python 3.13+
|
|
77
81
|
from os import process_cpu_count
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
# Need a worker for each mode (so +4) plus at least one to do work
|
|
84
|
+
thread_pool = ThreadPoolExecutor(4 + max(1, min(8, (process_cpu_count() or 1))))
|
|
80
85
|
else:
|
|
81
86
|
thread_pool = None
|
|
82
87
|
|
|
@@ -97,6 +102,7 @@ def package_engine(
|
|
|
97
102
|
rom=rom,
|
|
98
103
|
config=config,
|
|
99
104
|
thread_pool=thread_pool,
|
|
105
|
+
cache=cache,
|
|
100
106
|
),
|
|
101
107
|
"watch": thread_pool.submit(
|
|
102
108
|
build_watch_mode,
|
|
@@ -109,6 +115,7 @@ def package_engine(
|
|
|
109
115
|
update_spawn=watch_mode.update_spawn,
|
|
110
116
|
config=config,
|
|
111
117
|
thread_pool=thread_pool,
|
|
118
|
+
cache=cache,
|
|
112
119
|
),
|
|
113
120
|
"preview": thread_pool.submit(
|
|
114
121
|
build_preview_mode,
|
|
@@ -117,6 +124,7 @@ def package_engine(
|
|
|
117
124
|
rom=rom,
|
|
118
125
|
config=config,
|
|
119
126
|
thread_pool=thread_pool,
|
|
127
|
+
cache=cache,
|
|
120
128
|
),
|
|
121
129
|
"tutorial": thread_pool.submit(
|
|
122
130
|
build_tutorial_mode,
|
|
@@ -131,6 +139,7 @@ def package_engine(
|
|
|
131
139
|
rom=rom,
|
|
132
140
|
config=config,
|
|
133
141
|
thread_pool=thread_pool,
|
|
142
|
+
cache=cache,
|
|
134
143
|
),
|
|
135
144
|
}
|
|
136
145
|
|
|
@@ -148,6 +157,7 @@ def package_engine(
|
|
|
148
157
|
rom=rom,
|
|
149
158
|
config=config,
|
|
150
159
|
thread_pool=None,
|
|
160
|
+
cache=cache,
|
|
151
161
|
)
|
|
152
162
|
watch_data = build_watch_mode(
|
|
153
163
|
archetypes=watch_mode.archetypes,
|
|
@@ -159,6 +169,7 @@ def package_engine(
|
|
|
159
169
|
update_spawn=watch_mode.update_spawn,
|
|
160
170
|
config=config,
|
|
161
171
|
thread_pool=None,
|
|
172
|
+
cache=cache,
|
|
162
173
|
)
|
|
163
174
|
preview_data = build_preview_mode(
|
|
164
175
|
archetypes=preview_mode.archetypes,
|
|
@@ -166,6 +177,7 @@ def package_engine(
|
|
|
166
177
|
rom=rom,
|
|
167
178
|
config=config,
|
|
168
179
|
thread_pool=None,
|
|
180
|
+
cache=cache,
|
|
169
181
|
)
|
|
170
182
|
tutorial_data = build_tutorial_mode(
|
|
171
183
|
skin=tutorial_mode.skin,
|
|
@@ -179,6 +191,7 @@ def package_engine(
|
|
|
179
191
|
rom=rom,
|
|
180
192
|
config=config,
|
|
181
193
|
thread_pool=None,
|
|
194
|
+
cache=cache,
|
|
182
195
|
)
|
|
183
196
|
|
|
184
197
|
return PackagedEngine(
|
|
@@ -270,6 +283,7 @@ def build_play_mode(
|
|
|
270
283
|
config: BuildConfig,
|
|
271
284
|
thread_pool: Executor | None = None,
|
|
272
285
|
validate_only: bool = False,
|
|
286
|
+
cache: CompileCache | None = None,
|
|
273
287
|
):
|
|
274
288
|
return {
|
|
275
289
|
**compile_mode(
|
|
@@ -280,6 +294,7 @@ def build_play_mode(
|
|
|
280
294
|
passes=config.passes,
|
|
281
295
|
thread_pool=thread_pool,
|
|
282
296
|
validate_only=validate_only,
|
|
297
|
+
cache=cache,
|
|
283
298
|
),
|
|
284
299
|
"skin": build_skin(skin),
|
|
285
300
|
"effect": build_effects(effects),
|
|
@@ -299,6 +314,7 @@ def build_watch_mode(
|
|
|
299
314
|
config: BuildConfig,
|
|
300
315
|
thread_pool: Executor | None = None,
|
|
301
316
|
validate_only: bool = False,
|
|
317
|
+
cache: CompileCache | None = None,
|
|
302
318
|
):
|
|
303
319
|
return {
|
|
304
320
|
**compile_mode(
|
|
@@ -309,6 +325,7 @@ def build_watch_mode(
|
|
|
309
325
|
passes=config.passes,
|
|
310
326
|
thread_pool=thread_pool,
|
|
311
327
|
validate_only=validate_only,
|
|
328
|
+
cache=cache,
|
|
312
329
|
),
|
|
313
330
|
"skin": build_skin(skin),
|
|
314
331
|
"effect": build_effects(effects),
|
|
@@ -324,6 +341,7 @@ def build_preview_mode(
|
|
|
324
341
|
config: BuildConfig,
|
|
325
342
|
thread_pool: Executor | None = None,
|
|
326
343
|
validate_only: bool = False,
|
|
344
|
+
cache: CompileCache | None = None,
|
|
327
345
|
):
|
|
328
346
|
return {
|
|
329
347
|
**compile_mode(
|
|
@@ -334,6 +352,7 @@ def build_preview_mode(
|
|
|
334
352
|
passes=config.passes,
|
|
335
353
|
thread_pool=thread_pool,
|
|
336
354
|
validate_only=validate_only,
|
|
355
|
+
cache=cache,
|
|
337
356
|
),
|
|
338
357
|
"skin": build_skin(skin),
|
|
339
358
|
}
|
|
@@ -352,6 +371,7 @@ def build_tutorial_mode(
|
|
|
352
371
|
config: BuildConfig,
|
|
353
372
|
thread_pool: Executor | None = None,
|
|
354
373
|
validate_only: bool = False,
|
|
374
|
+
cache: CompileCache | None = None,
|
|
355
375
|
):
|
|
356
376
|
return {
|
|
357
377
|
**compile_mode(
|
|
@@ -366,6 +386,7 @@ def build_tutorial_mode(
|
|
|
366
386
|
passes=config.passes,
|
|
367
387
|
thread_pool=thread_pool,
|
|
368
388
|
validate_only=validate_only,
|
|
389
|
+
cache=cache,
|
|
369
390
|
),
|
|
370
391
|
"skin": build_skin(skin),
|
|
371
392
|
"effect": build_effects(effects),
|
sonolus/build/project.py
CHANGED
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
5
|
from sonolus.build.collection import Asset, Collection, Srl
|
|
6
|
+
from sonolus.build.compile import CompileCache
|
|
6
7
|
from sonolus.build.engine import package_engine, unpackage_data
|
|
7
8
|
from sonolus.build.level import package_level_data
|
|
8
9
|
from sonolus.script.engine import Engine
|
|
@@ -19,7 +20,9 @@ BLANK_AUDIO = (
|
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def build_project_to_collection(
|
|
23
|
+
def build_project_to_collection(
|
|
24
|
+
project: Project, config: BuildConfig | None, cache: CompileCache | None = None
|
|
25
|
+
) -> Collection:
|
|
23
26
|
collection = load_resources_files_to_collection(project.resources)
|
|
24
27
|
for src_engine, converter in project.converters.items():
|
|
25
28
|
if src_engine is None:
|
|
@@ -31,7 +34,7 @@ def build_project_to_collection(project: Project, config: BuildConfig | None):
|
|
|
31
34
|
if config.override_resource_level_engines:
|
|
32
35
|
for level in collection.categories.get("levels", {}).values():
|
|
33
36
|
level["item"]["engine"] = project.engine.name
|
|
34
|
-
add_engine_to_collection(collection, project, project.engine, config)
|
|
37
|
+
add_engine_to_collection(collection, project, project.engine, config, cache=cache)
|
|
35
38
|
for level in project.levels:
|
|
36
39
|
add_level_to_collection(collection, project, level)
|
|
37
40
|
collection.name = f"{project.engine.name}"
|
|
@@ -63,8 +66,14 @@ def apply_converter_to_collection(
|
|
|
63
66
|
level["data"] = new_data_srl
|
|
64
67
|
|
|
65
68
|
|
|
66
|
-
def add_engine_to_collection(
|
|
67
|
-
|
|
69
|
+
def add_engine_to_collection(
|
|
70
|
+
collection: Collection,
|
|
71
|
+
project: Project,
|
|
72
|
+
engine: Engine,
|
|
73
|
+
config: BuildConfig | None,
|
|
74
|
+
cache: CompileCache | None = None,
|
|
75
|
+
):
|
|
76
|
+
packaged_engine = package_engine(engine.data, config, cache=cache)
|
|
68
77
|
item = {
|
|
69
78
|
"name": engine.name,
|
|
70
79
|
"version": engine.version,
|
sonolus/script/array.py
CHANGED
|
@@ -11,7 +11,7 @@ from sonolus.script.debug import assert_unreachable
|
|
|
11
11
|
from sonolus.script.internal.context import ctx
|
|
12
12
|
from sonolus.script.internal.error import InternalError
|
|
13
13
|
from sonolus.script.internal.generic import GenericValue
|
|
14
|
-
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
14
|
+
from sonolus.script.internal.impl import meta_fn, perf_meta_fn, validate_value
|
|
15
15
|
from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
16
16
|
from sonolus.script.num import Num
|
|
17
17
|
|
|
@@ -188,6 +188,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
188
188
|
else:
|
|
189
189
|
return cls._with_value([cls.element_type()._zero_() for _ in range(cls.size())])
|
|
190
190
|
|
|
191
|
+
@perf_meta_fn
|
|
191
192
|
def __len__(self):
|
|
192
193
|
return self.size()
|
|
193
194
|
|
sonolus/script/bucket.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
|
7
7
|
from sonolus.backend.mode import Mode
|
|
8
8
|
from sonolus.backend.ops import Op
|
|
9
9
|
from sonolus.script.internal.context import ctx
|
|
10
|
-
from sonolus.script.internal.impl import meta_fn
|
|
10
|
+
from sonolus.script.internal.impl import meta_fn, perf_meta_fn
|
|
11
11
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
12
12
|
from sonolus.script.internal.native import native_function
|
|
13
13
|
from sonolus.script.interval import Interval
|
|
@@ -72,20 +72,22 @@ class JudgmentWindow(Record):
|
|
|
72
72
|
*self.good.tuple,
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
+
@perf_meta_fn
|
|
75
76
|
def __mul__(self, other: float | int) -> JudgmentWindow:
|
|
76
77
|
"""Multiply the intervals by a scalar."""
|
|
77
|
-
return JudgmentWindow(
|
|
78
|
-
self.perfect * other,
|
|
79
|
-
self.great * other,
|
|
80
|
-
self.good * other,
|
|
78
|
+
return JudgmentWindow._quick_construct(
|
|
79
|
+
perfect=self.perfect * other,
|
|
80
|
+
great=self.great * other,
|
|
81
|
+
good=self.good * other,
|
|
81
82
|
)
|
|
82
83
|
|
|
84
|
+
@perf_meta_fn
|
|
83
85
|
def __add__(self, other: float | int) -> JudgmentWindow:
|
|
84
86
|
"""Add a scalar to the intervals."""
|
|
85
|
-
return JudgmentWindow(
|
|
86
|
-
self.perfect + other,
|
|
87
|
-
self.great + other,
|
|
88
|
-
self.good + other,
|
|
87
|
+
return JudgmentWindow._quick_construct(
|
|
88
|
+
perfect=self.perfect + other,
|
|
89
|
+
great=self.great + other,
|
|
90
|
+
good=self.good + other,
|
|
89
91
|
)
|
|
90
92
|
|
|
91
93
|
@property
|
sonolus/script/debug.py
CHANGED
|
@@ -68,15 +68,44 @@ def debug_pause():
|
|
|
68
68
|
input("[DEBUG] Paused")
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
@meta_fn
|
|
71
72
|
def assert_true(value: int | float | bool, message: str | None = None):
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if not ctx():
|
|
74
|
+
if not value:
|
|
75
|
+
raise AssertionError(message if message is not None else "Assertion failed")
|
|
76
|
+
return
|
|
77
|
+
value = Num._accept_(validate_value(value))
|
|
78
|
+
message = validate_value(message)
|
|
79
|
+
message = message._as_py_() or "Assertion failed"
|
|
80
|
+
if value._is_py_():
|
|
81
|
+
if value._as_py_():
|
|
82
|
+
return
|
|
83
|
+
else:
|
|
84
|
+
error(message)
|
|
85
|
+
else:
|
|
86
|
+
ctx().test = value.ir()
|
|
87
|
+
t_branch = ctx().branch(None)
|
|
88
|
+
f_branch = ctx().branch(0)
|
|
89
|
+
set_ctx(f_branch)
|
|
90
|
+
error(message) # type: ignore
|
|
91
|
+
set_ctx(t_branch)
|
|
75
92
|
|
|
76
93
|
|
|
77
94
|
def assert_false(value: int | float | bool, message: str | None = None):
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
assert_true(not value, message)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def static_assert(value: int | float | bool, message: str | None = None):
|
|
99
|
+
message = message if message is not None else "Static assertion failed"
|
|
100
|
+
if not _is_static_true(value):
|
|
101
|
+
static_error(message)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def try_static_assert(value: int | float | bool, message: str | None = None):
|
|
105
|
+
message = message if message is not None else "Static assertion failed"
|
|
106
|
+
if _is_static_false(value):
|
|
107
|
+
static_error(message)
|
|
108
|
+
if not value:
|
|
80
109
|
error(message)
|
|
81
110
|
|
|
82
111
|
|
|
@@ -140,3 +169,21 @@ def visualize_cfg(
|
|
|
140
169
|
|
|
141
170
|
def simulation_context() -> SimulationContext:
|
|
142
171
|
return SimulationContext()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@meta_fn
|
|
175
|
+
def _is_static_true(value: int | float | bool) -> bool:
|
|
176
|
+
if ctx() is None:
|
|
177
|
+
return bool(value)
|
|
178
|
+
else:
|
|
179
|
+
value = validate_value(value)
|
|
180
|
+
return value._is_py_() and value._as_py_()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@meta_fn
|
|
184
|
+
def _is_static_false(value: int | float | bool) -> bool:
|
|
185
|
+
if ctx() is None:
|
|
186
|
+
return not bool(value)
|
|
187
|
+
else:
|
|
188
|
+
value = validate_value(value)
|
|
189
|
+
return value._is_py_() and not value._as_py_()
|
sonolus/script/effect.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
5
6
|
|
|
6
7
|
from sonolus.backend.ops import Op
|
|
8
|
+
from sonolus.script.array_like import ArrayLike
|
|
9
|
+
from sonolus.script.debug import static_error
|
|
7
10
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
8
11
|
from sonolus.script.internal.native import native_function
|
|
9
12
|
from sonolus.script.record import Record
|
|
@@ -92,6 +95,29 @@ class ScheduledLoopedEffectHandle(Record):
|
|
|
92
95
|
_stop_looped_scheduled(self.id, end_time)
|
|
93
96
|
|
|
94
97
|
|
|
98
|
+
class EffectGroup(Record, ArrayLike[Effect]):
|
|
99
|
+
"""A group of effect clips.
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
```python
|
|
103
|
+
EffectGroup(start_id: int, size: int)
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
start_id: int
|
|
108
|
+
size: int
|
|
109
|
+
|
|
110
|
+
def __len__(self) -> int:
|
|
111
|
+
return self.size
|
|
112
|
+
|
|
113
|
+
def __getitem__(self, index: int) -> Effect:
|
|
114
|
+
assert 0 <= index < self.size
|
|
115
|
+
return Effect(self.start_id + index)
|
|
116
|
+
|
|
117
|
+
def __setitem__(self, index: int, value: Effect) -> None:
|
|
118
|
+
static_error("EffectGroup is read-only")
|
|
119
|
+
|
|
120
|
+
|
|
95
121
|
@native_function(Op.HasEffectClip)
|
|
96
122
|
def _has_effect_clip(effect_id: int) -> bool:
|
|
97
123
|
raise NotImplementedError
|
|
@@ -132,11 +158,21 @@ class EffectInfo:
|
|
|
132
158
|
name: str
|
|
133
159
|
|
|
134
160
|
|
|
161
|
+
@dataclass
|
|
162
|
+
class EffectGroupInfo:
|
|
163
|
+
names: list[str]
|
|
164
|
+
|
|
165
|
+
|
|
135
166
|
def effect(name: str) -> Any:
|
|
136
167
|
"""Define a sound effect clip with the given name."""
|
|
137
168
|
return EffectInfo(name)
|
|
138
169
|
|
|
139
170
|
|
|
171
|
+
def effect_group(names: Iterable[str]) -> Any:
|
|
172
|
+
"""Define an effect group with the given names."""
|
|
173
|
+
return EffectGroupInfo(list(names))
|
|
174
|
+
|
|
175
|
+
|
|
140
176
|
type Effects = NewType("Effects", Any) # type: ignore
|
|
141
177
|
|
|
142
178
|
|
|
@@ -150,24 +186,45 @@ def effects[T](cls: type[T]) -> T | Effects:
|
|
|
150
186
|
class Effects:
|
|
151
187
|
miss: StandardEffect.MISS
|
|
152
188
|
other: Effect = effect("other")
|
|
189
|
+
group_1: EffectGroup = effect_group(["one", "two", "three"])
|
|
190
|
+
group_2: EffectGroup = effect_group(f"name_{i}" for i in range(10))
|
|
153
191
|
```
|
|
154
192
|
"""
|
|
155
193
|
if len(cls.__bases__) != 1:
|
|
156
194
|
raise ValueError("Effects class must not inherit from any class (except object)")
|
|
157
195
|
instance = cls()
|
|
158
196
|
names = []
|
|
159
|
-
|
|
197
|
+
i = 0
|
|
198
|
+
for name, annotation in get_field_specifiers(cls).items():
|
|
160
199
|
if get_origin(annotation) is not Annotated:
|
|
161
|
-
raise TypeError(f"Invalid annotation for effects: {annotation}")
|
|
200
|
+
raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}")
|
|
162
201
|
annotation_type = annotation.__args__[0]
|
|
163
202
|
annotation_values = annotation.__metadata__
|
|
164
|
-
if
|
|
165
|
-
raise TypeError(f"Invalid annotation for effects: {annotation},
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
203
|
+
if len(annotation_values) != 1:
|
|
204
|
+
raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}, too many annotation values")
|
|
205
|
+
effect_info = annotation_values[0]
|
|
206
|
+
match effect_info:
|
|
207
|
+
case EffectInfo(name=effect_name):
|
|
208
|
+
if annotation_type is not Effect:
|
|
209
|
+
raise TypeError(f"Invalid annotation for effects: {annotation} on field {name}, expected Effect")
|
|
210
|
+
names.append(effect_name)
|
|
211
|
+
setattr(instance, name, Effect(i))
|
|
212
|
+
i += 1
|
|
213
|
+
case EffectGroupInfo(names=effect_names):
|
|
214
|
+
if annotation_type is not EffectGroup:
|
|
215
|
+
raise TypeError(
|
|
216
|
+
f"Invalid annotation for effects: {annotation} on field {name}, expected EffectGroup"
|
|
217
|
+
)
|
|
218
|
+
start_id = i
|
|
219
|
+
count = len(effect_names)
|
|
220
|
+
names.extend(effect_names)
|
|
221
|
+
setattr(instance, name, EffectGroup(start_id, count))
|
|
222
|
+
i += count
|
|
223
|
+
case _:
|
|
224
|
+
raise TypeError(
|
|
225
|
+
f"Invalid annotation for effects: {annotation} on field {name}, unknown effect info, "
|
|
226
|
+
f"expected an effect() or effect_group() specifier"
|
|
227
|
+
)
|
|
171
228
|
instance._effects_ = names
|
|
172
229
|
instance._is_comptime_value_ = True
|
|
173
230
|
return instance
|
sonolus/script/internal/impl.py
CHANGED
|
@@ -33,6 +33,10 @@ def meta_fn(fn=None):
|
|
|
33
33
|
return decorator(fn)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
# To indicate this was used for performance reasons rather than functionality
|
|
37
|
+
perf_meta_fn = meta_fn
|
|
38
|
+
|
|
39
|
+
|
|
36
40
|
def validate_value[T](value: T) -> Value | T:
|
|
37
41
|
result = try_validate_value(value)
|
|
38
42
|
if result is None:
|
|
@@ -59,16 +63,16 @@ def try_validate_value(value: Any) -> Value | None:
|
|
|
59
63
|
pass
|
|
60
64
|
|
|
61
65
|
match value:
|
|
62
|
-
case Enum():
|
|
63
|
-
return validate_value(value.value)
|
|
64
66
|
case Value():
|
|
65
67
|
return value
|
|
68
|
+
case int() | float() | bool():
|
|
69
|
+
return Num._accept_(value)
|
|
70
|
+
case Enum():
|
|
71
|
+
return validate_value(value.value)
|
|
66
72
|
case type():
|
|
67
73
|
if value in {int, float, bool}:
|
|
68
74
|
return BasicConstantValue.of(Num)
|
|
69
75
|
return BasicConstantValue.of(value)
|
|
70
|
-
case int() | float() | bool():
|
|
71
|
-
return Num._accept_(value)
|
|
72
76
|
case tuple():
|
|
73
77
|
return TupleImpl._accept_(value)
|
|
74
78
|
case dict():
|
sonolus/script/interval.py
CHANGED
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
4
|
from sonolus.script.array_like import ArrayLike
|
|
5
5
|
from sonolus.script.debug import static_error
|
|
6
|
+
from sonolus.script.internal.builtin_impls import _max, _min
|
|
7
|
+
from sonolus.script.internal.impl import perf_meta_fn
|
|
6
8
|
from sonolus.script.internal.native import native_function
|
|
7
9
|
from sonolus.script.internal.range import range_or_tuple
|
|
8
10
|
from sonolus.script.num import Num
|
|
@@ -66,6 +68,7 @@ class Interval(Record):
|
|
|
66
68
|
case _:
|
|
67
69
|
static_error("Invalid type for interval check")
|
|
68
70
|
|
|
71
|
+
@perf_meta_fn
|
|
69
72
|
def __add__(self, other: float | int) -> Interval:
|
|
70
73
|
"""Add a value to both ends of the interval.
|
|
71
74
|
|
|
@@ -75,8 +78,9 @@ class Interval(Record):
|
|
|
75
78
|
Returns:
|
|
76
79
|
A new interval with the value added to both ends.
|
|
77
80
|
"""
|
|
78
|
-
return Interval(self.start + other, self.end + other)
|
|
81
|
+
return Interval._quick_construct(start=self.start + other, end=self.end + other)
|
|
79
82
|
|
|
83
|
+
@perf_meta_fn
|
|
80
84
|
def __sub__(self, other: float | int) -> Interval:
|
|
81
85
|
"""Subtract a value from both ends of the interval.
|
|
82
86
|
|
|
@@ -86,8 +90,9 @@ class Interval(Record):
|
|
|
86
90
|
Returns:
|
|
87
91
|
A new interval with the value subtracted from both ends.
|
|
88
92
|
"""
|
|
89
|
-
return Interval(self.start - other, self.end - other)
|
|
93
|
+
return Interval._quick_construct(start=self.start - other, end=self.end - other)
|
|
90
94
|
|
|
95
|
+
@perf_meta_fn
|
|
91
96
|
def __mul__(self, other: float | int) -> Interval:
|
|
92
97
|
"""Multiply both ends of the interval by a value.
|
|
93
98
|
|
|
@@ -97,8 +102,9 @@ class Interval(Record):
|
|
|
97
102
|
Returns:
|
|
98
103
|
A new interval with both ends multiplied by the value.
|
|
99
104
|
"""
|
|
100
|
-
return Interval(self.start * other, self.end * other)
|
|
105
|
+
return Interval._quick_construct(start=self.start * other, end=self.end * other)
|
|
101
106
|
|
|
107
|
+
@perf_meta_fn
|
|
102
108
|
def __truediv__(self, other: float | int) -> Interval:
|
|
103
109
|
"""Divide both ends of the interval by a value.
|
|
104
110
|
|
|
@@ -108,8 +114,9 @@ class Interval(Record):
|
|
|
108
114
|
Returns:
|
|
109
115
|
A new interval with both ends divided by the value.
|
|
110
116
|
"""
|
|
111
|
-
return Interval(self.start / other, self.end / other)
|
|
117
|
+
return Interval._quick_construct(start=self.start / other, end=self.end / other)
|
|
112
118
|
|
|
119
|
+
@perf_meta_fn
|
|
113
120
|
def __floordiv__(self, other: float | int) -> Interval:
|
|
114
121
|
"""Divide both ends of the interval by a value and floor the result.
|
|
115
122
|
|
|
@@ -119,7 +126,7 @@ class Interval(Record):
|
|
|
119
126
|
Returns:
|
|
120
127
|
A new interval with both ends divided by the value and floored.
|
|
121
128
|
"""
|
|
122
|
-
return Interval(self.start // other, self.end // other)
|
|
129
|
+
return Interval._quick_construct(start=self.start // other, end=self.end // other)
|
|
123
130
|
|
|
124
131
|
def __and__(self, other: Interval) -> Interval:
|
|
125
132
|
"""Get the intersection of two intervals.
|
|
@@ -222,14 +229,17 @@ def _num_lerp_clamped(a, b, x, /):
|
|
|
222
229
|
return a + (b - a) * max(0, min(1, x))
|
|
223
230
|
|
|
224
231
|
|
|
232
|
+
@perf_meta_fn
|
|
225
233
|
def _generic_lerp[T](a: T, b: T, x: float, /) -> T:
|
|
226
234
|
return a + (b - a) * x # type: ignore
|
|
227
235
|
|
|
228
236
|
|
|
237
|
+
@perf_meta_fn
|
|
229
238
|
def _generic_lerp_clamped[T](a: T, b: T, x: float, /) -> T:
|
|
230
|
-
return a + (b - a) *
|
|
239
|
+
return a + (b - a) * _max(0, _min(1, x)) # type: ignore
|
|
231
240
|
|
|
232
241
|
|
|
242
|
+
@perf_meta_fn
|
|
233
243
|
def lerp[T](a: T, b: T, x: float, /) -> T:
|
|
234
244
|
"""Linearly interpolate between two values.
|
|
235
245
|
|
|
@@ -248,6 +258,7 @@ def lerp[T](a: T, b: T, x: float, /) -> T:
|
|
|
248
258
|
return _generic_lerp(a, b, x)
|
|
249
259
|
|
|
250
260
|
|
|
261
|
+
@perf_meta_fn
|
|
251
262
|
def lerp_clamped[T](a: T, b: T, x: float, /) -> T:
|
|
252
263
|
"""Linearly interpolate between two values, clamped to the interval.
|
|
253
264
|
|
sonolus/script/num.py
CHANGED
|
@@ -27,6 +27,8 @@ def _is_num(value: Any) -> TypeGuard[Num]:
|
|
|
27
27
|
|
|
28
28
|
@final
|
|
29
29
|
class _Num(Value, metaclass=_NumMeta):
|
|
30
|
+
__slots__ = ("data",)
|
|
31
|
+
|
|
30
32
|
# This works for ints, floats, and bools
|
|
31
33
|
# Since we don't support complex numbers, real is equal to the original number
|
|
32
34
|
__match_args__ = ("real",)
|
|
@@ -34,16 +36,10 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
34
36
|
data: DataValue
|
|
35
37
|
|
|
36
38
|
def __init__(self, data: DataValue | IRExpr):
|
|
37
|
-
if isinstance(data, complex):
|
|
38
|
-
raise TypeError("Cannot create a Num from a complex number")
|
|
39
39
|
if isinstance(data, int):
|
|
40
40
|
data = float(data)
|
|
41
41
|
if isinstance(data, IRConst | IRPureInstr | IRGet):
|
|
42
42
|
data = ExprBackingValue(data)
|
|
43
|
-
if _is_num(data):
|
|
44
|
-
raise InternalError("Cannot create a Num from a Num")
|
|
45
|
-
if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
|
|
46
|
-
raise TypeError(f"Cannot create a Num from {type(data)}")
|
|
47
43
|
self.data = data
|
|
48
44
|
|
|
49
45
|
def __str__(self) -> str:
|
|
@@ -131,6 +127,8 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
131
127
|
if isinstance(self.data.block, BlockData) and not ctx().is_writable(self.data):
|
|
132
128
|
# This block is immutable in the current callback, so no need to copy it in case it changes.
|
|
133
129
|
return Num(self.data)
|
|
130
|
+
if isinstance(self.data, int | float | bool):
|
|
131
|
+
return Num(self.data)
|
|
134
132
|
place = ctx().alloc(size=1)
|
|
135
133
|
ctx().add_statements(IRSet(place, self.ir()))
|
|
136
134
|
return Num(place)
|
|
@@ -449,6 +447,12 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
449
447
|
return 0
|
|
450
448
|
|
|
451
449
|
|
|
450
|
+
def _create_num_raw(i: int) -> Num:
|
|
451
|
+
result = object.__new__(_Num)
|
|
452
|
+
result.data = float(i)
|
|
453
|
+
return result
|
|
454
|
+
|
|
455
|
+
|
|
452
456
|
if TYPE_CHECKING:
|
|
453
457
|
from typing import Protocol
|
|
454
458
|
|