sonolus.py 0.1.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.

Files changed (75) hide show
  1. sonolus/__init__.py +0 -0
  2. sonolus/backend/__init__.py +0 -0
  3. sonolus/backend/allocate.py +51 -0
  4. sonolus/backend/blocks.py +756 -0
  5. sonolus/backend/excepthook.py +37 -0
  6. sonolus/backend/finalize.py +69 -0
  7. sonolus/backend/flow.py +92 -0
  8. sonolus/backend/interpret.py +333 -0
  9. sonolus/backend/ir.py +89 -0
  10. sonolus/backend/mode.py +24 -0
  11. sonolus/backend/node.py +40 -0
  12. sonolus/backend/ops.py +197 -0
  13. sonolus/backend/optimize.py +9 -0
  14. sonolus/backend/passes.py +6 -0
  15. sonolus/backend/place.py +90 -0
  16. sonolus/backend/simplify.py +30 -0
  17. sonolus/backend/utils.py +48 -0
  18. sonolus/backend/visitor.py +880 -0
  19. sonolus/build/__init__.py +0 -0
  20. sonolus/build/cli.py +170 -0
  21. sonolus/build/collection.py +293 -0
  22. sonolus/build/compile.py +90 -0
  23. sonolus/build/defaults.py +32 -0
  24. sonolus/build/engine.py +149 -0
  25. sonolus/build/level.py +23 -0
  26. sonolus/build/node.py +43 -0
  27. sonolus/build/project.py +94 -0
  28. sonolus/py.typed +0 -0
  29. sonolus/script/__init__.py +0 -0
  30. sonolus/script/archetype.py +651 -0
  31. sonolus/script/array.py +241 -0
  32. sonolus/script/bucket.py +192 -0
  33. sonolus/script/callbacks.py +105 -0
  34. sonolus/script/comptime.py +146 -0
  35. sonolus/script/containers.py +247 -0
  36. sonolus/script/debug.py +70 -0
  37. sonolus/script/effect.py +132 -0
  38. sonolus/script/engine.py +101 -0
  39. sonolus/script/globals.py +234 -0
  40. sonolus/script/graphics.py +141 -0
  41. sonolus/script/icon.py +73 -0
  42. sonolus/script/internal/__init__.py +5 -0
  43. sonolus/script/internal/builtin_impls.py +144 -0
  44. sonolus/script/internal/context.py +365 -0
  45. sonolus/script/internal/descriptor.py +17 -0
  46. sonolus/script/internal/error.py +15 -0
  47. sonolus/script/internal/generic.py +197 -0
  48. sonolus/script/internal/impl.py +69 -0
  49. sonolus/script/internal/introspection.py +14 -0
  50. sonolus/script/internal/native.py +38 -0
  51. sonolus/script/internal/value.py +144 -0
  52. sonolus/script/interval.py +98 -0
  53. sonolus/script/iterator.py +211 -0
  54. sonolus/script/level.py +52 -0
  55. sonolus/script/math.py +92 -0
  56. sonolus/script/num.py +382 -0
  57. sonolus/script/options.py +194 -0
  58. sonolus/script/particle.py +158 -0
  59. sonolus/script/pointer.py +30 -0
  60. sonolus/script/project.py +17 -0
  61. sonolus/script/range.py +58 -0
  62. sonolus/script/record.py +293 -0
  63. sonolus/script/runtime.py +526 -0
  64. sonolus/script/sprite.py +332 -0
  65. sonolus/script/text.py +404 -0
  66. sonolus/script/timing.py +42 -0
  67. sonolus/script/transform.py +118 -0
  68. sonolus/script/ui.py +160 -0
  69. sonolus/script/values.py +43 -0
  70. sonolus/script/vec.py +48 -0
  71. sonolus_py-0.1.0.dist-info/METADATA +10 -0
  72. sonolus_py-0.1.0.dist-info/RECORD +75 -0
  73. sonolus_py-0.1.0.dist-info/WHEEL +4 -0
  74. sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
  75. sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,149 @@
1
+ import gzip
2
+ import json
3
+ import struct
4
+ from collections.abc import Callable
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from sonolus.backend.mode import Mode
9
+ from sonolus.build.compile import compile_mode
10
+ from sonolus.build.defaults import EMPTY_ENGINE_PREVIEW_DATA, EMPTY_ENGINE_TUTORIAL_DATA
11
+ from sonolus.script.archetype import BaseArchetype
12
+ from sonolus.script.bucket import Buckets
13
+ from sonolus.script.callbacks import update_spawn_callback
14
+ from sonolus.script.effect import Effects
15
+ from sonolus.script.engine import EngineData
16
+ from sonolus.script.internal.context import ReadOnlyMemory
17
+ from sonolus.script.options import Options
18
+ from sonolus.script.particle import Particles
19
+ from sonolus.script.sprite import Skin
20
+ from sonolus.script.ui import UiConfig
21
+
22
+ type JsonValue = None | bool | int | float | str | list[JsonValue] | dict[str, JsonValue]
23
+
24
+
25
+ @dataclass
26
+ class PackagedEngine:
27
+ configuration: bytes
28
+ play_data: bytes
29
+ watch_data: bytes
30
+ preview_data: bytes
31
+ tutorial_data: bytes
32
+ rom: bytes
33
+
34
+ def write(self, path: Path):
35
+ path.mkdir(parents=True, exist_ok=True)
36
+ (path / "EngineConfiguration").write_bytes(self.configuration)
37
+ (path / "EnginePlayData").write_bytes(self.play_data)
38
+ (path / "EngineWatchData").write_bytes(self.watch_data)
39
+ (path / "EnginePreviewData").write_bytes(self.preview_data)
40
+ (path / "EngineTutorialData").write_bytes(self.tutorial_data)
41
+ (path / "EngineRom").write_bytes(self.rom)
42
+
43
+
44
+ def package_engine(engine: EngineData):
45
+ rom = ReadOnlyMemory()
46
+ configuration = build_engine_configuration(engine.options, engine.ui)
47
+ play_data = build_play_mode(
48
+ archetypes=engine.play.archetypes,
49
+ skin=engine.play.skin,
50
+ effects=engine.play.effects,
51
+ particles=engine.play.particles,
52
+ buckets=engine.play.buckets,
53
+ rom=rom,
54
+ )
55
+ watch_data = build_watch_mode(
56
+ archetypes=engine.watch.archetypes,
57
+ skin=engine.watch.skin,
58
+ effects=engine.watch.effects,
59
+ particles=engine.watch.particles,
60
+ buckets=engine.watch.buckets,
61
+ rom=rom,
62
+ update_spawn=engine.watch.update_spawn,
63
+ )
64
+ return PackagedEngine(
65
+ configuration=package_output(configuration),
66
+ play_data=package_output(play_data),
67
+ watch_data=package_output(watch_data),
68
+ preview_data=package_output(EMPTY_ENGINE_PREVIEW_DATA),
69
+ tutorial_data=package_output(EMPTY_ENGINE_TUTORIAL_DATA),
70
+ rom=package_rom(rom),
71
+ )
72
+
73
+
74
+ def build_engine_configuration(
75
+ options: Options,
76
+ ui: UiConfig,
77
+ ) -> JsonValue:
78
+ return {
79
+ "options": [option.to_dict() for option in options._options_],
80
+ "ui": ui.to_dict(),
81
+ }
82
+
83
+
84
+ def build_play_mode(
85
+ archetypes: list[type[BaseArchetype]],
86
+ skin: Skin,
87
+ effects: Effects,
88
+ particles: Particles,
89
+ buckets: Buckets,
90
+ rom: ReadOnlyMemory,
91
+ ):
92
+ return {
93
+ **compile_mode(mode=Mode.Play, rom=rom, archetypes=archetypes, global_callbacks=None),
94
+ "skin": build_skin(skin),
95
+ "effect": build_effects(effects),
96
+ "particle": build_particles(particles),
97
+ "buckets": build_buckets(buckets),
98
+ }
99
+
100
+
101
+ def build_watch_mode(
102
+ archetypes: list[type[BaseArchetype]],
103
+ skin: Skin,
104
+ effects: Effects,
105
+ particles: Particles,
106
+ buckets: Buckets,
107
+ rom: ReadOnlyMemory,
108
+ update_spawn: Callable[[], float],
109
+ ):
110
+ return {
111
+ **compile_mode(
112
+ mode=Mode.Watch, rom=rom, archetypes=archetypes, global_callbacks=[(update_spawn_callback, update_spawn)]
113
+ ),
114
+ "skin": build_skin(skin),
115
+ "effect": build_effects(effects),
116
+ "particle": build_particles(particles),
117
+ "buckets": build_buckets(buckets),
118
+ }
119
+
120
+
121
+ def build_skin(skin: Skin) -> JsonValue:
122
+ return {"sprites": [{"name": name, "id": i} for i, name in enumerate(skin._sprites_)]}
123
+
124
+
125
+ def build_effects(effects: Effects) -> JsonValue:
126
+ return {"clips": [{"name": name, "id": i} for i, name in enumerate(effects._effects_)]}
127
+
128
+
129
+ def build_particles(particles: Particles) -> JsonValue:
130
+ return {"effects": [{"name": name, "id": i} for i, name in enumerate(particles._particles_)]}
131
+
132
+
133
+ def build_buckets(buckets: Buckets) -> JsonValue:
134
+ return [bucket.to_dict() for bucket in buckets._buckets_]
135
+
136
+
137
+ def package_rom(rom: ReadOnlyMemory) -> bytes:
138
+ values = rom.values or [0]
139
+ output = bytearray()
140
+
141
+ for value in values:
142
+ output.extend(struct.pack("<f", value))
143
+
144
+ return gzip.compress(bytes(output))
145
+
146
+
147
+ def package_output(value: JsonValue) -> bytes:
148
+ json_data = json.dumps(value, separators=(",", ":")).encode("utf-8")
149
+ return gzip.compress(json_data)
sonolus/build/level.py ADDED
@@ -0,0 +1,23 @@
1
+ from sonolus.build.engine import package_output
2
+ from sonolus.script.level import LevelData
3
+
4
+
5
+ def package_level_data(
6
+ level_data: LevelData,
7
+ ):
8
+ return package_output(build_level_data(level_data))
9
+
10
+
11
+ def build_level_data(
12
+ level_data: LevelData,
13
+ ):
14
+ return {
15
+ "bgmOffset": level_data.bgm_offset,
16
+ "entities": [
17
+ {
18
+ "archetype": entity.name,
19
+ "data": entity._level_data_entries(),
20
+ }
21
+ for entity in level_data.entities
22
+ ],
23
+ }
sonolus/build/node.py ADDED
@@ -0,0 +1,43 @@
1
+ from typing import TypedDict
2
+
3
+ from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
4
+
5
+
6
+ class ValueOutputNode(TypedDict):
7
+ value: float
8
+
9
+
10
+ class FunctionOutputNode(TypedDict):
11
+ func: str
12
+ args: list[int]
13
+
14
+
15
+ class OutputNodeGenerator:
16
+ nodes: list[ValueOutputNode | FunctionOutputNode]
17
+ indexes: dict[EngineNode, int]
18
+
19
+ def __init__(self):
20
+ self.nodes = []
21
+ self.indexes = {}
22
+
23
+ def add(self, node: EngineNode):
24
+ if node in self.indexes:
25
+ return self.indexes[node]
26
+
27
+ match node:
28
+ case ConstantNode(value):
29
+ index = len(self.nodes)
30
+ self.nodes.append({"value": value})
31
+ self.indexes[node] = index
32
+ return index
33
+ case FunctionNode(func, args):
34
+ arg_indexes = [self.add(arg) for arg in args]
35
+ index = len(self.nodes)
36
+ self.nodes.append({"func": func.value, "args": arg_indexes})
37
+ self.indexes[node] = index
38
+ return index
39
+ case _:
40
+ raise ValueError("Invalid node")
41
+
42
+ def get(self):
43
+ return self.nodes
@@ -0,0 +1,94 @@
1
+ from pathlib import Path
2
+
3
+ from sonolus.build.collection import Asset, Collection, Srl
4
+ from sonolus.build.engine import package_engine
5
+ from sonolus.build.level import package_level_data
6
+ from sonolus.script.engine import Engine
7
+ from sonolus.script.level import Level
8
+ from sonolus.script.project import Project
9
+
10
+ BLANK_PNG = (
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$"
12
+ b"\x00\x00\x00\nIDATx\x01c`\x00\x00\x00\x02\x00\x01su\x01\x18\x00\x00\x00\x00IEND\xaeB`\x82"
13
+ )
14
+ BLANK_AUDIO = (
15
+ b"RIFF$\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00D\xac\x00\x00\x88X\x01\x00\x02"
16
+ b"\x00\x10\x00data\x00\x00\x00\x00"
17
+ )
18
+
19
+
20
+ def build_project_to_collection(project: Project):
21
+ collection = load_resources_files_to_collection(project.resources)
22
+ add_engine_to_collection(collection, project, project.engine)
23
+ for level in project.levels:
24
+ add_level_to_collection(collection, project, level)
25
+ collection.name = f"{project.engine.name}"
26
+ return collection
27
+
28
+
29
+ def add_engine_to_collection(collection: Collection, project: Project, engine: Engine):
30
+ packaged_engine = package_engine(engine.data)
31
+ item = {
32
+ "name": engine.name,
33
+ "version": engine.version,
34
+ "title": engine.title,
35
+ "subtitle": engine.subtitle,
36
+ "author": engine.author,
37
+ "tags": [],
38
+ "skin": collection.get_item("skins", engine.skin) if engine.skin else collection.get_default_item("skins"),
39
+ "background": collection.get_item("backgrounds", engine.background)
40
+ if engine.background
41
+ else collection.get_default_item("backgrounds"),
42
+ "effect": collection.get_item("effects", engine.effect)
43
+ if engine.effect
44
+ else collection.get_default_item("effects"),
45
+ "particle": collection.get_item("particles", engine.particle)
46
+ if engine.particle
47
+ else collection.get_default_item("particles"),
48
+ "thumbnail": load_resource(collection, engine.thumbnail, project.resources, BLANK_PNG),
49
+ "playData": collection.add_asset(packaged_engine.play_data),
50
+ "watchData": collection.add_asset(packaged_engine.watch_data),
51
+ "previewData": collection.add_asset(packaged_engine.preview_data),
52
+ "tutorialData": collection.add_asset(packaged_engine.tutorial_data),
53
+ "rom": collection.add_asset(packaged_engine.rom),
54
+ "configuration": collection.add_asset(packaged_engine.configuration),
55
+ }
56
+ collection.add_item("engines", engine.name, item)
57
+
58
+
59
+ def add_level_to_collection(collection: Collection, project: Project, level: Level):
60
+ packaged_level_data = package_level_data(level.data)
61
+ item = {
62
+ "name": level.name,
63
+ "version": level.version,
64
+ "rating": level.rating,
65
+ "title": level.title,
66
+ "artists": level.artists,
67
+ "author": level.author,
68
+ "tags": [],
69
+ "engine": collection.get_item("engines", project.engine.name),
70
+ "useSkin": {"useDefault": True},
71
+ "useBackground": {"useDefault": True},
72
+ "useEffect": {"useDefault": True},
73
+ "useParticle": {"useDefault": True},
74
+ "cover": load_resource(collection, level.cover, project.resources, BLANK_PNG),
75
+ "bgm": load_resource(collection, level.bgm, project.resources, BLANK_AUDIO),
76
+ "data": collection.add_asset(packaged_level_data),
77
+ }
78
+ collection.add_item("levels", level.name, item)
79
+
80
+
81
+ def load_resource(collection: Collection, asset: Asset | None, base_path: Path, default: bytes) -> Srl:
82
+ if asset is None:
83
+ return collection.add_asset(default)
84
+ if isinstance(asset, str) and not asset.startswith(("http://", "https://")):
85
+ return collection.add_asset(base_path / asset)
86
+ return collection.add_asset(asset)
87
+
88
+
89
+ def load_resources_files_to_collection(base_path: Path) -> Collection:
90
+ collection = Collection()
91
+ for path in base_path.rglob("*.scp"):
92
+ collection.load_from_scp(path)
93
+ collection.load_from_source(base_path)
94
+ return collection
sonolus/py.typed ADDED
File without changes
File without changes