sonolus.py 0.1.9__py3-none-any.whl → 0.2.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/optimize/constant_evaluation.py +2 -2
- sonolus/backend/optimize/optimize.py +12 -4
- sonolus/backend/optimize/passes.py +2 -1
- sonolus/backend/place.py +95 -14
- sonolus/backend/visitor.py +51 -3
- sonolus/build/cli.py +60 -9
- sonolus/build/collection.py +68 -26
- sonolus/build/compile.py +85 -27
- sonolus/build/engine.py +166 -40
- sonolus/build/node.py +8 -1
- sonolus/build/project.py +30 -11
- sonolus/script/archetype.py +110 -26
- sonolus/script/array.py +11 -0
- sonolus/script/debug.py +2 -2
- sonolus/script/effect.py +2 -2
- sonolus/script/engine.py +123 -15
- sonolus/script/internal/builtin_impls.py +21 -2
- sonolus/script/internal/constant.py +5 -1
- sonolus/script/internal/context.py +30 -25
- sonolus/script/internal/math_impls.py +2 -1
- sonolus/script/internal/transient.py +4 -0
- sonolus/script/internal/value.py +6 -0
- sonolus/script/interval.py +16 -0
- sonolus/script/iterator.py +17 -0
- sonolus/script/level.py +113 -10
- sonolus/script/metadata.py +32 -0
- sonolus/script/num.py +9 -0
- sonolus/script/options.py +5 -3
- sonolus/script/pointer.py +2 -0
- sonolus/script/project.py +41 -5
- sonolus/script/record.py +7 -2
- sonolus/script/runtime.py +61 -10
- sonolus/script/sprite.py +18 -1
- sonolus/script/ui.py +7 -3
- sonolus/script/values.py +8 -5
- sonolus/script/vec.py +28 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.0.dist-info}/METADATA +3 -2
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.0.dist-info}/RECORD +42 -41
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.0.dist-info}/WHEEL +1 -1
- /sonolus/script/{print.py → printing.py} +0 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.9.dist-info → sonolus_py-0.2.0.dist-info}/licenses/LICENSE +0 -0
sonolus/build/compile.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
1
|
+
from collections.abc import Callable, Sequence
|
|
2
|
+
from concurrent.futures import Executor, Future
|
|
2
3
|
|
|
3
4
|
from sonolus.backend.finalize import cfg_to_engine_node
|
|
4
5
|
from sonolus.backend.ir import IRConst, IRInstr
|
|
5
6
|
from sonolus.backend.mode import Mode
|
|
6
7
|
from sonolus.backend.ops import Op
|
|
7
8
|
from sonolus.backend.optimize.flow import BasicBlock
|
|
8
|
-
from sonolus.backend.optimize.optimize import
|
|
9
|
+
from sonolus.backend.optimize.optimize import STANDARD_PASSES
|
|
10
|
+
from sonolus.backend.optimize.passes import CompilerPass, run_passes
|
|
9
11
|
from sonolus.backend.visitor import compile_and_call
|
|
10
12
|
from sonolus.build.node import OutputNodeGenerator
|
|
11
13
|
from sonolus.script.archetype import _BaseArchetype
|
|
@@ -27,16 +29,49 @@ def compile_mode(
|
|
|
27
29
|
rom: ReadOnlyMemory,
|
|
28
30
|
archetypes: list[type[_BaseArchetype]] | None,
|
|
29
31
|
global_callbacks: list[tuple[CallbackInfo, Callable]] | None,
|
|
32
|
+
passes: Sequence[CompilerPass] | None = None,
|
|
33
|
+
thread_pool: Executor | None = None,
|
|
30
34
|
) -> dict:
|
|
35
|
+
if passes is None:
|
|
36
|
+
passes = STANDARD_PASSES
|
|
37
|
+
|
|
31
38
|
global_state = GlobalContextState(
|
|
32
|
-
mode,
|
|
39
|
+
mode,
|
|
40
|
+
{a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
|
|
41
|
+
rom,
|
|
33
42
|
)
|
|
34
43
|
nodes = OutputNodeGenerator()
|
|
35
44
|
results = {}
|
|
45
|
+
|
|
46
|
+
def process_callback(
|
|
47
|
+
cb_info: CallbackInfo,
|
|
48
|
+
cb: Callable,
|
|
49
|
+
arch: type[_BaseArchetype] | None = None,
|
|
50
|
+
) -> tuple[str, int] | tuple[str, dict]:
|
|
51
|
+
"""Compile a single callback to a node.
|
|
52
|
+
|
|
53
|
+
Returns either:
|
|
54
|
+
- (cb_info.name, node_index) for global callbacks, or
|
|
55
|
+
- (cb_info.name, {"index": node_index, "order": cb_order}) for archetype callbacks.
|
|
56
|
+
"""
|
|
57
|
+
cfg = callback_to_cfg(global_state, cb, cb_info.name, arch)
|
|
58
|
+
cfg = run_passes(cfg, passes)
|
|
59
|
+
node = cfg_to_engine_node(cfg)
|
|
60
|
+
node_index = nodes.add(node)
|
|
61
|
+
|
|
62
|
+
if arch is not None:
|
|
63
|
+
cb_order = getattr(cb, "_callback_order_", 0)
|
|
64
|
+
return cb_info.name, {"index": node_index, "order": cb_order}
|
|
65
|
+
else:
|
|
66
|
+
return cb_info.name, node_index
|
|
67
|
+
|
|
68
|
+
all_futures = {}
|
|
69
|
+
archetype_entries = []
|
|
70
|
+
|
|
36
71
|
if archetypes is not None:
|
|
37
|
-
archetype_entries = []
|
|
38
72
|
for archetype in archetypes:
|
|
39
73
|
archetype._init_fields()
|
|
74
|
+
|
|
40
75
|
archetype_data = {
|
|
41
76
|
"name": archetype.name,
|
|
42
77
|
"hasInput": archetype.is_scored,
|
|
@@ -44,36 +79,59 @@ def compile_mode(
|
|
|
44
79
|
}
|
|
45
80
|
if mode == Mode.PLAY:
|
|
46
81
|
archetype_data["exports"] = [*archetype._exported_keys_]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
82
|
+
|
|
83
|
+
callback_items = [
|
|
84
|
+
(cb_name, cb_info, getattr(archetype, cb_name))
|
|
85
|
+
for cb_name, cb_info in archetype._supported_callbacks_.items()
|
|
86
|
+
if getattr(archetype, cb_name) not in archetype._default_callbacks_
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
if thread_pool is not None:
|
|
90
|
+
for cb_name, cb_info, cb in callback_items:
|
|
91
|
+
cb_order = getattr(cb, "_callback_order_", 0)
|
|
92
|
+
if not cb_info.supports_order and cb_order != 0:
|
|
93
|
+
raise ValueError(f"Callback '{cb_name}' does not support a non-zero order")
|
|
94
|
+
f: Future = thread_pool.submit(process_callback, cb_info, cb, archetype)
|
|
95
|
+
all_futures[f] = ("archetype", archetype_data, cb_name)
|
|
96
|
+
else:
|
|
97
|
+
for cb_name, cb_info, cb in callback_items:
|
|
98
|
+
cb_order = getattr(cb, "_callback_order_", 0)
|
|
99
|
+
if not cb_info.supports_order and cb_order != 0:
|
|
100
|
+
raise ValueError(f"Callback '{cb_name}' does not support a non-zero order")
|
|
101
|
+
cb_name, result_data = process_callback(cb_info, cb, archetype)
|
|
102
|
+
archetype_data[cb_name] = result_data
|
|
103
|
+
|
|
62
104
|
archetype_entries.append(archetype_data)
|
|
63
|
-
|
|
64
|
-
if global_callbacks is not None:
|
|
105
|
+
|
|
106
|
+
if global_callbacks is not None and thread_pool is not None:
|
|
107
|
+
for cb_info, cb in global_callbacks:
|
|
108
|
+
f: Future = thread_pool.submit(process_callback, cb_info, cb, None)
|
|
109
|
+
all_futures[f] = ("global", None, cb_info.name)
|
|
110
|
+
|
|
111
|
+
if thread_pool is not None:
|
|
112
|
+
for f, (callback_type, archetype_data, cb_name) in all_futures.items():
|
|
113
|
+
cb_name, result_data = f.result()
|
|
114
|
+
if callback_type == "archetype":
|
|
115
|
+
archetype_data[cb_name] = result_data
|
|
116
|
+
else: # global callback
|
|
117
|
+
results[cb_name] = result_data
|
|
118
|
+
elif global_callbacks is not None:
|
|
65
119
|
for cb_info, cb in global_callbacks:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
120
|
+
cb_name, node_index = process_callback(cb_info, cb, None)
|
|
121
|
+
results[cb_name] = node_index
|
|
122
|
+
|
|
123
|
+
if archetypes is not None:
|
|
124
|
+
results["archetypes"] = archetype_entries
|
|
125
|
+
|
|
71
126
|
results["nodes"] = nodes.get()
|
|
72
127
|
return results
|
|
73
128
|
|
|
74
129
|
|
|
75
130
|
def callback_to_cfg(
|
|
76
|
-
global_state: GlobalContextState,
|
|
131
|
+
global_state: GlobalContextState,
|
|
132
|
+
callback: Callable,
|
|
133
|
+
name: str,
|
|
134
|
+
archetype: type[_BaseArchetype] | None = None,
|
|
77
135
|
) -> BasicBlock:
|
|
78
136
|
callback_state = CallbackContextState(name)
|
|
79
137
|
context = Context(global_state, callback_state)
|
sonolus/build/engine.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import json
|
|
3
3
|
import struct
|
|
4
|
+
import sys
|
|
4
5
|
from collections.abc import Callable
|
|
6
|
+
from concurrent.futures import Executor
|
|
7
|
+
from concurrent.futures.thread import ThreadPoolExecutor
|
|
5
8
|
from dataclasses import dataclass
|
|
9
|
+
from os import process_cpu_count
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
|
|
8
12
|
from sonolus.backend.mode import Mode
|
|
@@ -10,7 +14,7 @@ from sonolus.build.compile import compile_mode
|
|
|
10
14
|
from sonolus.script.archetype import _BaseArchetype
|
|
11
15
|
from sonolus.script.bucket import Buckets
|
|
12
16
|
from sonolus.script.effect import Effects
|
|
13
|
-
from sonolus.script.engine import EngineData
|
|
17
|
+
from sonolus.script.engine import EngineData, empty_play_mode, empty_preview_mode, empty_tutorial_mode, empty_watch_mode
|
|
14
18
|
from sonolus.script.instruction import (
|
|
15
19
|
TutorialInstructionIcons,
|
|
16
20
|
TutorialInstructions,
|
|
@@ -24,6 +28,7 @@ from sonolus.script.internal.callbacks import (
|
|
|
24
28
|
from sonolus.script.internal.context import ReadOnlyMemory
|
|
25
29
|
from sonolus.script.options import Options
|
|
26
30
|
from sonolus.script.particle import Particles
|
|
31
|
+
from sonolus.script.project import BuildConfig
|
|
27
32
|
from sonolus.script.sprite import Skin
|
|
28
33
|
from sonolus.script.ui import UiConfig
|
|
29
34
|
|
|
@@ -48,43 +53,132 @@ class PackagedEngine:
|
|
|
48
53
|
(path / "EngineTutorialData").write_bytes(self.tutorial_data)
|
|
49
54
|
(path / "EngineRom").write_bytes(self.rom)
|
|
50
55
|
|
|
56
|
+
def read(self, path: Path):
|
|
57
|
+
self.configuration = (path / "EngineConfiguration").read_bytes()
|
|
58
|
+
self.play_data = (path / "EnginePlayData").read_bytes()
|
|
59
|
+
self.watch_data = (path / "EngineWatchData").read_bytes()
|
|
60
|
+
self.preview_data = (path / "EnginePreviewData").read_bytes()
|
|
61
|
+
self.tutorial_data = (path / "EngineTutorialData").read_bytes()
|
|
62
|
+
self.rom = (path / "EngineRom").read_bytes()
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
|
|
65
|
+
def no_gil() -> bool:
|
|
66
|
+
return sys.version_info >= (3, 13) and not sys._is_gil_enabled()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def package_engine(
|
|
70
|
+
engine: EngineData,
|
|
71
|
+
config: BuildConfig | None = None,
|
|
72
|
+
):
|
|
73
|
+
config = config or BuildConfig()
|
|
53
74
|
rom = ReadOnlyMemory()
|
|
54
75
|
configuration = build_engine_configuration(engine.options, engine.ui)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
if no_gil():
|
|
77
|
+
thread_pool = ThreadPoolExecutor(process_cpu_count() or 1)
|
|
78
|
+
else:
|
|
79
|
+
thread_pool = None
|
|
80
|
+
|
|
81
|
+
play_mode = engine.play if config.build_play else empty_play_mode()
|
|
82
|
+
watch_mode = engine.watch if config.build_watch else empty_watch_mode()
|
|
83
|
+
preview_mode = engine.preview if config.build_preview else empty_preview_mode()
|
|
84
|
+
tutorial_mode = engine.tutorial if config.build_tutorial else empty_tutorial_mode()
|
|
85
|
+
|
|
86
|
+
if thread_pool is not None:
|
|
87
|
+
futures = {
|
|
88
|
+
"play": thread_pool.submit(
|
|
89
|
+
build_play_mode,
|
|
90
|
+
archetypes=play_mode.archetypes,
|
|
91
|
+
skin=play_mode.skin,
|
|
92
|
+
effects=play_mode.effects,
|
|
93
|
+
particles=play_mode.particles,
|
|
94
|
+
buckets=play_mode.buckets,
|
|
95
|
+
rom=rom,
|
|
96
|
+
config=config,
|
|
97
|
+
thread_pool=thread_pool,
|
|
98
|
+
),
|
|
99
|
+
"watch": thread_pool.submit(
|
|
100
|
+
build_watch_mode,
|
|
101
|
+
archetypes=watch_mode.archetypes,
|
|
102
|
+
skin=watch_mode.skin,
|
|
103
|
+
effects=watch_mode.effects,
|
|
104
|
+
particles=watch_mode.particles,
|
|
105
|
+
buckets=watch_mode.buckets,
|
|
106
|
+
rom=rom,
|
|
107
|
+
update_spawn=watch_mode.update_spawn,
|
|
108
|
+
config=config,
|
|
109
|
+
thread_pool=thread_pool,
|
|
110
|
+
),
|
|
111
|
+
"preview": thread_pool.submit(
|
|
112
|
+
build_preview_mode,
|
|
113
|
+
archetypes=preview_mode.archetypes,
|
|
114
|
+
skin=preview_mode.skin,
|
|
115
|
+
rom=rom,
|
|
116
|
+
config=config,
|
|
117
|
+
thread_pool=thread_pool,
|
|
118
|
+
),
|
|
119
|
+
"tutorial": thread_pool.submit(
|
|
120
|
+
build_tutorial_mode,
|
|
121
|
+
skin=tutorial_mode.skin,
|
|
122
|
+
effects=tutorial_mode.effects,
|
|
123
|
+
particles=tutorial_mode.particles,
|
|
124
|
+
instructions=tutorial_mode.instructions,
|
|
125
|
+
instruction_icons=tutorial_mode.instruction_icons,
|
|
126
|
+
preprocess=tutorial_mode.preprocess,
|
|
127
|
+
navigate=tutorial_mode.navigate,
|
|
128
|
+
update=tutorial_mode.update,
|
|
129
|
+
rom=rom,
|
|
130
|
+
config=config,
|
|
131
|
+
thread_pool=thread_pool,
|
|
132
|
+
),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
play_data = futures["play"].result()
|
|
136
|
+
watch_data = futures["watch"].result()
|
|
137
|
+
preview_data = futures["preview"].result()
|
|
138
|
+
tutorial_data = futures["tutorial"].result()
|
|
139
|
+
else:
|
|
140
|
+
play_data = build_play_mode(
|
|
141
|
+
archetypes=play_mode.archetypes,
|
|
142
|
+
skin=play_mode.skin,
|
|
143
|
+
effects=play_mode.effects,
|
|
144
|
+
particles=play_mode.particles,
|
|
145
|
+
buckets=play_mode.buckets,
|
|
146
|
+
rom=rom,
|
|
147
|
+
config=config,
|
|
148
|
+
thread_pool=None,
|
|
149
|
+
)
|
|
150
|
+
watch_data = build_watch_mode(
|
|
151
|
+
archetypes=watch_mode.archetypes,
|
|
152
|
+
skin=watch_mode.skin,
|
|
153
|
+
effects=watch_mode.effects,
|
|
154
|
+
particles=watch_mode.particles,
|
|
155
|
+
buckets=watch_mode.buckets,
|
|
156
|
+
rom=rom,
|
|
157
|
+
update_spawn=watch_mode.update_spawn,
|
|
158
|
+
config=config,
|
|
159
|
+
thread_pool=None,
|
|
160
|
+
)
|
|
161
|
+
preview_data = build_preview_mode(
|
|
162
|
+
archetypes=preview_mode.archetypes,
|
|
163
|
+
skin=preview_mode.skin,
|
|
164
|
+
rom=rom,
|
|
165
|
+
config=config,
|
|
166
|
+
thread_pool=None,
|
|
167
|
+
)
|
|
168
|
+
tutorial_data = build_tutorial_mode(
|
|
169
|
+
skin=tutorial_mode.skin,
|
|
170
|
+
effects=tutorial_mode.effects,
|
|
171
|
+
particles=tutorial_mode.particles,
|
|
172
|
+
instructions=tutorial_mode.instructions,
|
|
173
|
+
instruction_icons=tutorial_mode.instruction_icons,
|
|
174
|
+
preprocess=tutorial_mode.preprocess,
|
|
175
|
+
navigate=tutorial_mode.navigate,
|
|
176
|
+
update=tutorial_mode.update,
|
|
177
|
+
rom=rom,
|
|
178
|
+
config=config,
|
|
179
|
+
thread_pool=None,
|
|
180
|
+
)
|
|
181
|
+
|
|
88
182
|
return PackagedEngine(
|
|
89
183
|
configuration=package_output(configuration),
|
|
90
184
|
play_data=package_output(play_data),
|
|
@@ -112,9 +206,18 @@ def build_play_mode(
|
|
|
112
206
|
particles: Particles,
|
|
113
207
|
buckets: Buckets,
|
|
114
208
|
rom: ReadOnlyMemory,
|
|
209
|
+
config: BuildConfig,
|
|
210
|
+
thread_pool: Executor | None = None,
|
|
115
211
|
):
|
|
116
212
|
return {
|
|
117
|
-
**compile_mode(
|
|
213
|
+
**compile_mode(
|
|
214
|
+
mode=Mode.PLAY,
|
|
215
|
+
rom=rom,
|
|
216
|
+
archetypes=archetypes,
|
|
217
|
+
global_callbacks=None,
|
|
218
|
+
passes=config.passes,
|
|
219
|
+
thread_pool=thread_pool,
|
|
220
|
+
),
|
|
118
221
|
"skin": build_skin(skin),
|
|
119
222
|
"effect": build_effects(effects),
|
|
120
223
|
"particle": build_particles(particles),
|
|
@@ -130,10 +233,17 @@ def build_watch_mode(
|
|
|
130
233
|
buckets: Buckets,
|
|
131
234
|
rom: ReadOnlyMemory,
|
|
132
235
|
update_spawn: Callable[[], float],
|
|
236
|
+
config: BuildConfig,
|
|
237
|
+
thread_pool: Executor | None = None,
|
|
133
238
|
):
|
|
134
239
|
return {
|
|
135
240
|
**compile_mode(
|
|
136
|
-
mode=Mode.WATCH,
|
|
241
|
+
mode=Mode.WATCH,
|
|
242
|
+
rom=rom,
|
|
243
|
+
archetypes=archetypes,
|
|
244
|
+
global_callbacks=[(update_spawn_callback, update_spawn)],
|
|
245
|
+
passes=config.passes,
|
|
246
|
+
thread_pool=thread_pool,
|
|
137
247
|
),
|
|
138
248
|
"skin": build_skin(skin),
|
|
139
249
|
"effect": build_effects(effects),
|
|
@@ -146,9 +256,18 @@ def build_preview_mode(
|
|
|
146
256
|
archetypes: list[type[_BaseArchetype]],
|
|
147
257
|
skin: Skin,
|
|
148
258
|
rom: ReadOnlyMemory,
|
|
259
|
+
config: BuildConfig,
|
|
260
|
+
thread_pool: Executor | None = None,
|
|
149
261
|
):
|
|
150
262
|
return {
|
|
151
|
-
**compile_mode(
|
|
263
|
+
**compile_mode(
|
|
264
|
+
mode=Mode.PREVIEW,
|
|
265
|
+
rom=rom,
|
|
266
|
+
archetypes=archetypes,
|
|
267
|
+
global_callbacks=None,
|
|
268
|
+
passes=config.passes,
|
|
269
|
+
thread_pool=thread_pool,
|
|
270
|
+
),
|
|
152
271
|
"skin": build_skin(skin),
|
|
153
272
|
}
|
|
154
273
|
|
|
@@ -160,9 +279,11 @@ def build_tutorial_mode(
|
|
|
160
279
|
instructions: TutorialInstructions,
|
|
161
280
|
instruction_icons: TutorialInstructionIcons,
|
|
162
281
|
preprocess: Callable[[], None],
|
|
163
|
-
navigate: Callable[[
|
|
282
|
+
navigate: Callable[[], None],
|
|
164
283
|
update: Callable[[], None],
|
|
165
284
|
rom: ReadOnlyMemory,
|
|
285
|
+
config: BuildConfig,
|
|
286
|
+
thread_pool: Executor | None = None,
|
|
166
287
|
):
|
|
167
288
|
return {
|
|
168
289
|
**compile_mode(
|
|
@@ -174,6 +295,8 @@ def build_tutorial_mode(
|
|
|
174
295
|
(navigate_callback, navigate),
|
|
175
296
|
(update_callback, update),
|
|
176
297
|
],
|
|
298
|
+
passes=config.passes,
|
|
299
|
+
thread_pool=thread_pool,
|
|
177
300
|
),
|
|
178
301
|
"skin": build_skin(skin),
|
|
179
302
|
"effect": build_effects(effects),
|
|
@@ -183,7 +306,10 @@ def build_tutorial_mode(
|
|
|
183
306
|
|
|
184
307
|
|
|
185
308
|
def build_skin(skin: Skin) -> JsonValue:
|
|
186
|
-
return {
|
|
309
|
+
return {
|
|
310
|
+
"renderMode": skin.render_mode,
|
|
311
|
+
"sprites": [{"name": name, "id": i} for i, name in enumerate(skin._sprites_)],
|
|
312
|
+
}
|
|
187
313
|
|
|
188
314
|
|
|
189
315
|
def build_effects(effects: Effects) -> JsonValue:
|
sonolus/build/node.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from threading import Lock
|
|
1
2
|
from typing import TypedDict
|
|
2
3
|
|
|
3
4
|
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
@@ -15,12 +16,18 @@ class FunctionOutputNode(TypedDict):
|
|
|
15
16
|
class OutputNodeGenerator:
|
|
16
17
|
nodes: list[ValueOutputNode | FunctionOutputNode]
|
|
17
18
|
indexes: dict[EngineNode, int]
|
|
19
|
+
lock: Lock
|
|
18
20
|
|
|
19
21
|
def __init__(self):
|
|
20
22
|
self.nodes = []
|
|
21
23
|
self.indexes = {}
|
|
24
|
+
self.lock = Lock()
|
|
22
25
|
|
|
23
26
|
def add(self, node: EngineNode):
|
|
27
|
+
with self.lock:
|
|
28
|
+
return self._add(node)
|
|
29
|
+
|
|
30
|
+
def _add(self, node: EngineNode):
|
|
24
31
|
if node in self.indexes:
|
|
25
32
|
return self.indexes[node]
|
|
26
33
|
|
|
@@ -31,7 +38,7 @@ class OutputNodeGenerator:
|
|
|
31
38
|
self.indexes[node] = index
|
|
32
39
|
return index
|
|
33
40
|
case FunctionNode(func, args):
|
|
34
|
-
arg_indexes = [self.
|
|
41
|
+
arg_indexes = [self._add(arg) for arg in args]
|
|
35
42
|
index = len(self.nodes)
|
|
36
43
|
self.nodes.append({"func": func.value, "args": arg_indexes})
|
|
37
44
|
self.indexes[node] = index
|
sonolus/build/project.py
CHANGED
|
@@ -5,7 +5,7 @@ from sonolus.build.engine import package_engine
|
|
|
5
5
|
from sonolus.build.level import package_level_data
|
|
6
6
|
from sonolus.script.engine import Engine
|
|
7
7
|
from sonolus.script.level import Level
|
|
8
|
-
from sonolus.script.project import Project, ProjectSchema
|
|
8
|
+
from sonolus.script.project import BuildConfig, Project, ProjectSchema
|
|
9
9
|
|
|
10
10
|
BLANK_PNG = (
|
|
11
11
|
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x007n\xf9$"
|
|
@@ -17,24 +17,24 @@ BLANK_AUDIO = (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def build_project_to_collection(project: Project):
|
|
20
|
+
def build_project_to_collection(project: Project, config: BuildConfig):
|
|
21
21
|
collection = load_resources_files_to_collection(project.resources)
|
|
22
|
-
add_engine_to_collection(collection, project, project.engine)
|
|
22
|
+
add_engine_to_collection(collection, project, project.engine, config)
|
|
23
23
|
for level in project.levels:
|
|
24
24
|
add_level_to_collection(collection, project, level)
|
|
25
25
|
collection.name = f"{project.engine.name}"
|
|
26
26
|
return collection
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def add_engine_to_collection(collection: Collection, project: Project, engine: Engine):
|
|
30
|
-
packaged_engine = package_engine(engine.data)
|
|
29
|
+
def add_engine_to_collection(collection: Collection, project: Project, engine: Engine, config: BuildConfig):
|
|
30
|
+
packaged_engine = package_engine(engine.data, config)
|
|
31
31
|
item = {
|
|
32
32
|
"name": engine.name,
|
|
33
33
|
"version": engine.version,
|
|
34
34
|
"title": engine.title,
|
|
35
35
|
"subtitle": engine.subtitle,
|
|
36
36
|
"author": engine.author,
|
|
37
|
-
"tags": [],
|
|
37
|
+
"tags": [tag.as_dict() for tag in engine.tags],
|
|
38
38
|
"skin": collection.get_item("skins", engine.skin) if engine.skin else collection.get_default_item("skins"),
|
|
39
39
|
"background": collection.get_item("backgrounds", engine.background)
|
|
40
40
|
if engine.background
|
|
@@ -53,6 +53,8 @@ def add_engine_to_collection(collection: Collection, project: Project, engine: E
|
|
|
53
53
|
"rom": collection.add_asset(packaged_engine.rom),
|
|
54
54
|
"configuration": collection.add_asset(packaged_engine.configuration),
|
|
55
55
|
}
|
|
56
|
+
if engine.description is not None:
|
|
57
|
+
item["description"] = engine.description
|
|
56
58
|
collection.add_item("engines", engine.name, item)
|
|
57
59
|
|
|
58
60
|
|
|
@@ -65,16 +67,28 @@ def add_level_to_collection(collection: Collection, project: Project, level: Lev
|
|
|
65
67
|
"title": level.title,
|
|
66
68
|
"artists": level.artists,
|
|
67
69
|
"author": level.author,
|
|
68
|
-
"tags": [],
|
|
70
|
+
"tags": [tag.as_dict() for tag in level.tags],
|
|
69
71
|
"engine": collection.get_item("engines", project.engine.name),
|
|
70
|
-
"useSkin": {"useDefault": True}
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
72
|
+
"useSkin": {"useDefault": True}
|
|
73
|
+
if level.use_skin is None
|
|
74
|
+
else {"useDefault": False, "item": collection.get_item("skins", level.use_skin)},
|
|
75
|
+
"useBackground": {"useDefault": True}
|
|
76
|
+
if level.use_background is None
|
|
77
|
+
else {"useDefault": False, "item": collection.get_item("backgrounds", level.use_background)},
|
|
78
|
+
"useEffect": {"useDefault": True}
|
|
79
|
+
if level.use_effect is None
|
|
80
|
+
else {"useDefault": False, "item": collection.get_item("effects", level.use_effect)},
|
|
81
|
+
"useParticle": {"useDefault": True}
|
|
82
|
+
if level.use_particle is None
|
|
83
|
+
else {"useDefault": False, "item": collection.get_item("particles", level.use_particle)},
|
|
74
84
|
"cover": load_resource(collection, level.cover, project.resources, BLANK_PNG),
|
|
75
85
|
"bgm": load_resource(collection, level.bgm, project.resources, BLANK_AUDIO),
|
|
76
86
|
"data": collection.add_asset(packaged_level_data),
|
|
77
87
|
}
|
|
88
|
+
if level.description is not None:
|
|
89
|
+
item["description"] = level.description
|
|
90
|
+
if level.preview is not None:
|
|
91
|
+
item["preview"] = load_resource(collection, level.preview, project.resources, BLANK_AUDIO)
|
|
78
92
|
collection.add_item("levels", level.name, item)
|
|
79
93
|
|
|
80
94
|
|
|
@@ -97,6 +111,7 @@ def load_resources_files_to_collection(base_path: Path) -> Collection:
|
|
|
97
111
|
def get_project_schema(project: Project) -> ProjectSchema:
|
|
98
112
|
by_archetype: dict[str, dict[str, bool]] = {}
|
|
99
113
|
for archetype in project.engine.data.play.archetypes:
|
|
114
|
+
archetype._init_fields()
|
|
100
115
|
fields = by_archetype.setdefault(archetype.name, {})
|
|
101
116
|
# If a field is exported, we should exclude it if it's imported in watch mode
|
|
102
117
|
for field in archetype._exported_keys_:
|
|
@@ -104,11 +119,15 @@ def get_project_schema(project: Project) -> ProjectSchema:
|
|
|
104
119
|
for field in archetype._imported_keys_:
|
|
105
120
|
fields[field] = True
|
|
106
121
|
for archetype in project.engine.data.watch.archetypes:
|
|
122
|
+
archetype._init_fields()
|
|
107
123
|
fields = by_archetype.setdefault(archetype.name, {})
|
|
108
124
|
for field in archetype._imported_keys_:
|
|
125
|
+
if field in {"#ACCURACY", "#JUDGMENT"}:
|
|
126
|
+
continue
|
|
109
127
|
if field not in fields:
|
|
110
128
|
fields[field] = True
|
|
111
129
|
for archetype in project.engine.data.preview.archetypes:
|
|
130
|
+
archetype._init_fields()
|
|
112
131
|
fields = by_archetype.setdefault(archetype.name, {})
|
|
113
132
|
for field in archetype._imported_keys_:
|
|
114
133
|
fields[field] = True
|