sonolus.py 0.1.8__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/finalize.py +2 -1
- sonolus/backend/optimize/constant_evaluation.py +2 -2
- sonolus/backend/optimize/copy_coalesce.py +16 -7
- sonolus/backend/optimize/optimize.py +12 -4
- sonolus/backend/optimize/passes.py +2 -1
- sonolus/backend/place.py +95 -14
- sonolus/backend/visitor.py +83 -12
- sonolus/build/cli.py +60 -9
- sonolus/build/collection.py +68 -26
- sonolus/build/compile.py +87 -30
- sonolus/build/engine.py +166 -40
- sonolus/build/level.py +2 -1
- sonolus/build/node.py +8 -1
- sonolus/build/project.py +30 -11
- sonolus/script/archetype.py +169 -51
- sonolus/script/array.py +12 -1
- sonolus/script/bucket.py +26 -8
- 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 +6 -2
- sonolus/script/internal/context.py +30 -25
- sonolus/script/internal/introspection.py +8 -1
- sonolus/script/internal/math_impls.py +2 -1
- sonolus/script/internal/transient.py +5 -1
- sonolus/script/internal/value.py +10 -2
- sonolus/script/interval.py +16 -0
- sonolus/script/iterator.py +17 -0
- sonolus/script/level.py +130 -8
- sonolus/script/metadata.py +32 -0
- sonolus/script/num.py +11 -2
- sonolus/script/options.py +5 -3
- sonolus/script/pointer.py +2 -0
- sonolus/script/project.py +41 -5
- sonolus/script/record.py +8 -3
- 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.8.dist-info → sonolus_py-0.2.0.dist-info}/METADATA +3 -2
- sonolus_py-0.2.0.dist-info/RECORD +90 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/WHEEL +1 -1
- sonolus_py-0.1.8.dist-info/RECORD +0 -89
- /sonolus/script/{print.py → printing.py} +0 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.8.dist-info → sonolus_py-0.2.0.dist-info}/licenses/LICENSE +0 -0
sonolus/build/collection.py
CHANGED
|
@@ -36,7 +36,7 @@ SINGULAR_CATEGORY_NAMES: dict[Category, str] = {
|
|
|
36
36
|
}
|
|
37
37
|
BASE_PATH = "/sonolus/"
|
|
38
38
|
RESERVED_FILENAMES = {"info", "list"}
|
|
39
|
-
LOCALIZED_KEYS = {"title", "subtitle", "author", "description"}
|
|
39
|
+
LOCALIZED_KEYS = {"title", "subtitle", "author", "description", "artists"}
|
|
40
40
|
CATEGORY_SORT_ORDER = {
|
|
41
41
|
"levels": 0,
|
|
42
42
|
"engines": 1,
|
|
@@ -69,10 +69,10 @@ class Collection:
|
|
|
69
69
|
def add_item(self, category: Category, name: str, item: Any) -> None:
|
|
70
70
|
self.categories.setdefault(category, {})[name] = self._make_item_details(item)
|
|
71
71
|
|
|
72
|
-
@
|
|
73
|
-
def _make_item_details(item: dict[str, Any]) -> dict[str, Any]:
|
|
72
|
+
@classmethod
|
|
73
|
+
def _make_item_details(cls, item: dict[str, Any]) -> dict[str, Any]:
|
|
74
74
|
return {
|
|
75
|
-
"item": item,
|
|
75
|
+
"item": cls._localize_item(item),
|
|
76
76
|
"actions": [],
|
|
77
77
|
"hasCommunity": False,
|
|
78
78
|
"leaderboards": [],
|
|
@@ -81,16 +81,7 @@ class Collection:
|
|
|
81
81
|
|
|
82
82
|
@staticmethod
|
|
83
83
|
def _load_data(value: Asset) -> bytes:
|
|
84
|
-
|
|
85
|
-
case str() if value.startswith(("http://", "https://")):
|
|
86
|
-
with urllib.request.urlopen(value) as response:
|
|
87
|
-
return response.read()
|
|
88
|
-
case PathLike():
|
|
89
|
-
return Path(value).read_bytes()
|
|
90
|
-
case bytes():
|
|
91
|
-
return value
|
|
92
|
-
case _:
|
|
93
|
-
raise TypeError("value must be a URL, a path, or bytes")
|
|
84
|
+
return load_asset(value)
|
|
94
85
|
|
|
95
86
|
def add_asset(self, value: Asset, /) -> Srl:
|
|
96
87
|
data = self._load_data(value)
|
|
@@ -123,7 +114,7 @@ class Collection:
|
|
|
123
114
|
continue
|
|
124
115
|
|
|
125
116
|
try:
|
|
126
|
-
item_data = json.loads(item_json_path.read_text())
|
|
117
|
+
item_data = json.loads(item_json_path.read_text(encoding="utf-8"))
|
|
127
118
|
except json.JSONDecodeError:
|
|
128
119
|
continue
|
|
129
120
|
|
|
@@ -149,19 +140,32 @@ class Collection:
|
|
|
149
140
|
|
|
150
141
|
self.add_item(category_name, item_dir.name, item_data)
|
|
151
142
|
|
|
152
|
-
@
|
|
153
|
-
def _localize_item(item: dict[str, Any]) -> dict[str, Any]:
|
|
143
|
+
@classmethod
|
|
144
|
+
def _localize_item(cls, item: dict[str, Any]) -> dict[str, Any]:
|
|
154
145
|
localized_item = item.copy()
|
|
155
146
|
for key in LOCALIZED_KEYS:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
147
|
+
if key not in localized_item:
|
|
148
|
+
continue
|
|
149
|
+
localized_item[key] = cls._localize_text(localized_item[key])
|
|
150
|
+
if "tags" in localized_item:
|
|
151
|
+
localized_item["tags"] = [
|
|
152
|
+
{**tag, "title": cls._localize_text(tag["title"])} for tag in localized_item["tags"]
|
|
153
|
+
]
|
|
154
|
+
localized_item.pop("meta", None)
|
|
163
155
|
return localized_item
|
|
164
156
|
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _localize_text(text: str | dict[str, str]) -> str:
|
|
159
|
+
match text:
|
|
160
|
+
case str():
|
|
161
|
+
return text
|
|
162
|
+
case {"en": localized_text}:
|
|
163
|
+
return localized_text
|
|
164
|
+
case {**other_languages} if other_languages:
|
|
165
|
+
return text[min(other_languages)]
|
|
166
|
+
case _:
|
|
167
|
+
return ""
|
|
168
|
+
|
|
165
169
|
def _group_zip_entries_by_directory(self, file_list: list[zipfile.ZipInfo]) -> dict[str, list[zipfile.ZipInfo]]:
|
|
166
170
|
files_by_dir: dict[str, list[zipfile.ZipInfo]] = {}
|
|
167
171
|
|
|
@@ -207,7 +211,7 @@ class Collection:
|
|
|
207
211
|
) -> None:
|
|
208
212
|
for zip_entry in zip_entries:
|
|
209
213
|
try:
|
|
210
|
-
item_details = json.loads(zf.read(zip_entry))
|
|
214
|
+
item_details = json.loads(zf.read(zip_entry).decode("utf-8"))
|
|
211
215
|
except json.JSONDecodeError:
|
|
212
216
|
continue
|
|
213
217
|
|
|
@@ -220,11 +224,28 @@ class Collection:
|
|
|
220
224
|
self.categories[dir_name][item_name] = item_details
|
|
221
225
|
|
|
222
226
|
def write(self, path: Asset) -> None:
|
|
227
|
+
self.link()
|
|
223
228
|
base_dir = self._create_base_directory(path)
|
|
224
229
|
self._write_main_info(base_dir)
|
|
225
230
|
self._write_category_items(base_dir)
|
|
226
231
|
self._write_repository_items(base_dir)
|
|
227
232
|
|
|
233
|
+
def link(self):
|
|
234
|
+
for level_details in self.categories.get("levels", {}).values():
|
|
235
|
+
level = level_details["item"]
|
|
236
|
+
if isinstance(level["engine"], str):
|
|
237
|
+
level["engine"] = self.get_item("engines", level["engine"])
|
|
238
|
+
for key, category in (
|
|
239
|
+
("useSkin", "skins"),
|
|
240
|
+
("useBackground", "backgrounds"),
|
|
241
|
+
("useEffect", "effects"),
|
|
242
|
+
("useParticle", "particles"),
|
|
243
|
+
):
|
|
244
|
+
use_item = level[key]
|
|
245
|
+
if "item" not in use_item:
|
|
246
|
+
continue
|
|
247
|
+
use_item["item"] = self.get_item(category, use_item["item"])
|
|
248
|
+
|
|
228
249
|
def _create_base_directory(self, path: Asset) -> Path:
|
|
229
250
|
base_dir = Path(path) / BASE_PATH.strip("/")
|
|
230
251
|
base_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -280,7 +301,7 @@ class Collection:
|
|
|
280
301
|
|
|
281
302
|
@staticmethod
|
|
282
303
|
def _write_json(path: Path, content: Any) -> None:
|
|
283
|
-
path.write_text(json.dumps(content))
|
|
304
|
+
path.write_text(json.dumps(content), encoding="utf-8")
|
|
284
305
|
|
|
285
306
|
def update(self, other: Collection) -> None:
|
|
286
307
|
self.repository.update(other.repository)
|
|
@@ -291,3 +312,24 @@ class Collection:
|
|
|
291
312
|
class Srl(TypedDict):
|
|
292
313
|
hash: str
|
|
293
314
|
url: str
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def load_asset(value: Asset) -> bytes:
|
|
318
|
+
match value:
|
|
319
|
+
case str() if value.startswith(("http://", "https://")):
|
|
320
|
+
headers = {
|
|
321
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
|
322
|
+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
323
|
+
"Accept": "*/*",
|
|
324
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
325
|
+
"Connection": "keep-alive",
|
|
326
|
+
}
|
|
327
|
+
request = urllib.request.Request(value, headers=headers)
|
|
328
|
+
with urllib.request.urlopen(request) as response:
|
|
329
|
+
return response.read()
|
|
330
|
+
case PathLike():
|
|
331
|
+
return Path(value).read_bytes()
|
|
332
|
+
case bytes():
|
|
333
|
+
return value
|
|
334
|
+
case _:
|
|
335
|
+
raise TypeError("value must be a URL, a path, or bytes")
|
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,54 +29,109 @@ 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:
|
|
73
|
+
archetype._init_fields()
|
|
74
|
+
|
|
39
75
|
archetype_data = {
|
|
40
76
|
"name": archetype.name,
|
|
41
77
|
"hasInput": archetype.is_scored,
|
|
42
78
|
"imports": [{"name": name, "index": index} for name, index in archetype._imported_keys_.items()],
|
|
43
79
|
}
|
|
44
80
|
if mode == Mode.PLAY:
|
|
45
|
-
archetype_data["exports"] = [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
archetype_data["exports"] = [*archetype._exported_keys_]
|
|
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
|
+
|
|
63
104
|
archetype_entries.append(archetype_data)
|
|
64
|
-
|
|
65
|
-
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:
|
|
66
119
|
for cb_info, cb in global_callbacks:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
|
|
72
126
|
results["nodes"] = nodes.get()
|
|
73
127
|
return results
|
|
74
128
|
|
|
75
129
|
|
|
76
130
|
def callback_to_cfg(
|
|
77
|
-
global_state: GlobalContextState,
|
|
131
|
+
global_state: GlobalContextState,
|
|
132
|
+
callback: Callable,
|
|
133
|
+
name: str,
|
|
134
|
+
archetype: type[_BaseArchetype] | None = None,
|
|
78
135
|
) -> BasicBlock:
|
|
79
136
|
callback_state = CallbackContextState(name)
|
|
80
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/level.py
CHANGED
|
@@ -11,11 +11,12 @@ def package_level_data(
|
|
|
11
11
|
def build_level_data(
|
|
12
12
|
level_data: LevelData,
|
|
13
13
|
):
|
|
14
|
-
level_refs = {entity: i for i, entity in enumerate(level_data.entities)}
|
|
14
|
+
level_refs = {entity: f"{i}_{entity.name}" for i, entity in enumerate(level_data.entities)}
|
|
15
15
|
return {
|
|
16
16
|
"bgmOffset": level_data.bgm_offset,
|
|
17
17
|
"entities": [
|
|
18
18
|
{
|
|
19
|
+
"name": level_refs[entity],
|
|
19
20
|
"archetype": entity.name,
|
|
20
21
|
"data": entity._level_data_entries(level_refs),
|
|
21
22
|
}
|
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
|