sonolus.py 0.9.2__py3-none-any.whl → 0.10.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/build/engine.py CHANGED
@@ -24,7 +24,7 @@ from sonolus.script.internal.callbacks import (
24
24
  update_callback,
25
25
  update_spawn_callback,
26
26
  )
27
- from sonolus.script.internal.context import ReadOnlyMemory
27
+ from sonolus.script.internal.context import ProjectContextState, ReadOnlyMemory
28
28
  from sonolus.script.options import Options
29
29
  from sonolus.script.particle import Particles
30
30
  from sonolus.script.project import BuildConfig
@@ -69,12 +69,14 @@ def package_engine(
69
69
  engine: EngineData,
70
70
  config: BuildConfig | None = None,
71
71
  cache: CompileCache | None = None,
72
+ project_state: ProjectContextState | None = None,
72
73
  ):
73
74
  if cache is None:
74
75
  cache = CompileCache()
75
76
 
76
77
  config = config or BuildConfig()
77
- rom = ReadOnlyMemory()
78
+ if project_state is None:
79
+ project_state = ProjectContextState()
78
80
  configuration = build_engine_configuration(engine.options, engine.ui)
79
81
  if no_gil():
80
82
  # process_cpu_count is available in Python 3.13+
@@ -99,7 +101,7 @@ def package_engine(
99
101
  effects=play_mode.effects,
100
102
  particles=play_mode.particles,
101
103
  buckets=play_mode.buckets,
102
- rom=rom,
104
+ project_state=project_state,
103
105
  config=config,
104
106
  thread_pool=thread_pool,
105
107
  cache=cache,
@@ -111,7 +113,7 @@ def package_engine(
111
113
  effects=watch_mode.effects,
112
114
  particles=watch_mode.particles,
113
115
  buckets=watch_mode.buckets,
114
- rom=rom,
116
+ project_state=project_state,
115
117
  update_spawn=watch_mode.update_spawn,
116
118
  config=config,
117
119
  thread_pool=thread_pool,
@@ -121,7 +123,7 @@ def package_engine(
121
123
  build_preview_mode,
122
124
  archetypes=preview_mode.archetypes,
123
125
  skin=preview_mode.skin,
124
- rom=rom,
126
+ project_state=project_state,
125
127
  config=config,
126
128
  thread_pool=thread_pool,
127
129
  cache=cache,
@@ -136,7 +138,7 @@ def package_engine(
136
138
  preprocess=tutorial_mode.preprocess,
137
139
  navigate=tutorial_mode.navigate,
138
140
  update=tutorial_mode.update,
139
- rom=rom,
141
+ project_state=project_state,
140
142
  config=config,
141
143
  thread_pool=thread_pool,
142
144
  cache=cache,
@@ -154,7 +156,7 @@ def package_engine(
154
156
  effects=play_mode.effects,
155
157
  particles=play_mode.particles,
156
158
  buckets=play_mode.buckets,
157
- rom=rom,
159
+ project_state=project_state,
158
160
  config=config,
159
161
  thread_pool=None,
160
162
  cache=cache,
@@ -165,7 +167,7 @@ def package_engine(
165
167
  effects=watch_mode.effects,
166
168
  particles=watch_mode.particles,
167
169
  buckets=watch_mode.buckets,
168
- rom=rom,
170
+ project_state=project_state,
169
171
  update_spawn=watch_mode.update_spawn,
170
172
  config=config,
171
173
  thread_pool=None,
@@ -174,7 +176,7 @@ def package_engine(
174
176
  preview_data = build_preview_mode(
175
177
  archetypes=preview_mode.archetypes,
176
178
  skin=preview_mode.skin,
177
- rom=rom,
179
+ project_state=project_state,
178
180
  config=config,
179
181
  thread_pool=None,
180
182
  cache=cache,
@@ -188,7 +190,7 @@ def package_engine(
188
190
  preprocess=tutorial_mode.preprocess,
189
191
  navigate=tutorial_mode.navigate,
190
192
  update=tutorial_mode.update,
191
- rom=rom,
193
+ project_state=project_state,
192
194
  config=config,
193
195
  thread_pool=None,
194
196
  cache=cache,
@@ -200,7 +202,7 @@ def package_engine(
200
202
  watch_data=package_data(watch_data),
201
203
  preview_data=package_data(preview_data),
202
204
  tutorial_data=package_data(tutorial_data),
203
- rom=package_rom(rom),
205
+ rom=package_rom(project_state.rom),
204
206
  )
205
207
 
206
208
 
@@ -209,7 +211,7 @@ def validate_engine(
209
211
  config: BuildConfig | None = None,
210
212
  ):
211
213
  config = config or BuildConfig()
212
- rom = ReadOnlyMemory()
214
+ project_state = ProjectContextState()
213
215
 
214
216
  play_mode = engine.play if config.build_play else empty_play_mode()
215
217
  watch_mode = engine.watch if config.build_watch else empty_watch_mode()
@@ -222,7 +224,7 @@ def validate_engine(
222
224
  effects=play_mode.effects,
223
225
  particles=play_mode.particles,
224
226
  buckets=play_mode.buckets,
225
- rom=rom,
227
+ project_state=project_state,
226
228
  config=config,
227
229
  thread_pool=None,
228
230
  validate_only=True,
@@ -233,7 +235,7 @@ def validate_engine(
233
235
  effects=watch_mode.effects,
234
236
  particles=watch_mode.particles,
235
237
  buckets=watch_mode.buckets,
236
- rom=rom,
238
+ project_state=project_state,
237
239
  update_spawn=watch_mode.update_spawn,
238
240
  config=config,
239
241
  thread_pool=None,
@@ -242,7 +244,7 @@ def validate_engine(
242
244
  build_preview_mode(
243
245
  archetypes=preview_mode.archetypes,
244
246
  skin=preview_mode.skin,
245
- rom=rom,
247
+ project_state=project_state,
246
248
  config=config,
247
249
  thread_pool=None,
248
250
  validate_only=True,
@@ -256,7 +258,7 @@ def validate_engine(
256
258
  preprocess=tutorial_mode.preprocess,
257
259
  navigate=tutorial_mode.navigate,
258
260
  update=tutorial_mode.update,
259
- rom=rom,
261
+ project_state=project_state,
260
262
  config=config,
261
263
  thread_pool=None,
262
264
  validate_only=True,
@@ -279,7 +281,7 @@ def build_play_mode(
279
281
  effects: Effects,
280
282
  particles: Particles,
281
283
  buckets: Buckets,
282
- rom: ReadOnlyMemory,
284
+ project_state: ProjectContextState,
283
285
  config: BuildConfig,
284
286
  thread_pool: Executor | None = None,
285
287
  validate_only: bool = False,
@@ -288,7 +290,7 @@ def build_play_mode(
288
290
  return {
289
291
  **compile_mode(
290
292
  mode=Mode.PLAY,
291
- rom=rom,
293
+ project_state=project_state,
292
294
  archetypes=archetypes,
293
295
  global_callbacks=None,
294
296
  passes=config.passes,
@@ -309,7 +311,7 @@ def build_watch_mode(
309
311
  effects: Effects,
310
312
  particles: Particles,
311
313
  buckets: Buckets,
312
- rom: ReadOnlyMemory,
314
+ project_state: ProjectContextState,
313
315
  update_spawn: Callable[[], float],
314
316
  config: BuildConfig,
315
317
  thread_pool: Executor | None = None,
@@ -319,7 +321,7 @@ def build_watch_mode(
319
321
  return {
320
322
  **compile_mode(
321
323
  mode=Mode.WATCH,
322
- rom=rom,
324
+ project_state=project_state,
323
325
  archetypes=archetypes,
324
326
  global_callbacks=[(update_spawn_callback, update_spawn)],
325
327
  passes=config.passes,
@@ -337,7 +339,7 @@ def build_watch_mode(
337
339
  def build_preview_mode(
338
340
  archetypes: list[type[_BaseArchetype]],
339
341
  skin: Skin,
340
- rom: ReadOnlyMemory,
342
+ project_state: ProjectContextState,
341
343
  config: BuildConfig,
342
344
  thread_pool: Executor | None = None,
343
345
  validate_only: bool = False,
@@ -346,7 +348,7 @@ def build_preview_mode(
346
348
  return {
347
349
  **compile_mode(
348
350
  mode=Mode.PREVIEW,
349
- rom=rom,
351
+ project_state=project_state,
350
352
  archetypes=archetypes,
351
353
  global_callbacks=None,
352
354
  passes=config.passes,
@@ -367,7 +369,7 @@ def build_tutorial_mode(
367
369
  preprocess: Callable[[], None],
368
370
  navigate: Callable[[], None],
369
371
  update: Callable[[], None],
370
- rom: ReadOnlyMemory,
372
+ project_state: ProjectContextState,
371
373
  config: BuildConfig,
372
374
  thread_pool: Executor | None = None,
373
375
  validate_only: bool = False,
@@ -376,7 +378,7 @@ def build_tutorial_mode(
376
378
  return {
377
379
  **compile_mode(
378
380
  mode=Mode.TUTORIAL,
379
- rom=rom,
381
+ project_state=project_state,
380
382
  archetypes=[],
381
383
  global_callbacks=[
382
384
  (preprocess_callback, preprocess),
sonolus/build/project.py CHANGED
@@ -7,6 +7,7 @@ from sonolus.build.compile import CompileCache
7
7
  from sonolus.build.engine import package_engine, unpackage_data
8
8
  from sonolus.build.level import package_level_data
9
9
  from sonolus.script.engine import Engine
10
+ from sonolus.script.internal.context import ProjectContextState
10
11
  from sonolus.script.level import ExternalLevelData, ExternalLevelDataDict, Level, LevelData, parse_external_level_data
11
12
  from sonolus.script.project import BuildConfig, Project, ProjectSchema
12
13
 
@@ -21,7 +22,10 @@ BLANK_AUDIO = (
21
22
 
22
23
 
23
24
  def build_project_to_collection(
24
- project: Project, config: BuildConfig | None, cache: CompileCache | None = None
25
+ project: Project,
26
+ config: BuildConfig | None,
27
+ cache: CompileCache | None = None,
28
+ project_state: ProjectContextState | None = None,
25
29
  ) -> Collection:
26
30
  collection = load_resources_files_to_collection(project.resources)
27
31
  for src_engine, converter in project.converters.items():
@@ -34,7 +38,7 @@ def build_project_to_collection(
34
38
  if config.override_resource_level_engines:
35
39
  for level in collection.categories.get("levels", {}).values():
36
40
  level["item"]["engine"] = project.engine.name
37
- add_engine_to_collection(collection, project, project.engine, config, cache=cache)
41
+ add_engine_to_collection(collection, project, project.engine, config, cache=cache, project_state=project_state)
38
42
  for level in project.levels:
39
43
  add_level_to_collection(collection, project, level)
40
44
  collection.name = f"{project.engine.name}"
@@ -72,8 +76,9 @@ def add_engine_to_collection(
72
76
  engine: Engine,
73
77
  config: BuildConfig | None,
74
78
  cache: CompileCache | None = None,
79
+ project_state: ProjectContextState | None = None,
75
80
  ):
76
- packaged_engine = package_engine(engine.data, config, cache=cache)
81
+ packaged_engine = package_engine(engine.data, config, cache=cache, project_state=project_state)
77
82
  item = {
78
83
  "name": engine.name,
79
84
  "version": engine.version,
@@ -207,7 +207,7 @@ class _IsScoredDescriptor(SonolusDescriptor):
207
207
  if instance is None:
208
208
  return self.value
209
209
  elif ctx():
210
- return ctx().global_state.is_scored_by_archetype_id[instance.id]
210
+ return ctx().mode_state.is_scored_by_archetype_id[instance.id]
211
211
  else:
212
212
  return self.value
213
213
 
@@ -220,7 +220,7 @@ class _IdDescriptor(SonolusDescriptor):
220
220
  if not ctx():
221
221
  raise RuntimeError("Archetype id is only available during compilation")
222
222
  if instance is None:
223
- result = ctx().global_state.archetypes.get(owner)
223
+ result = ctx().mode_state.archetypes.get(owner)
224
224
  if result is None:
225
225
  raise RuntimeError("Archetype is not registered")
226
226
  return result
@@ -237,7 +237,7 @@ class _KeyDescriptor(SonolusDescriptor):
237
237
 
238
238
  def __get__(self, instance, owner):
239
239
  if instance is not None and ctx():
240
- return ctx().global_state.keys_by_archetype_id[instance.id]
240
+ return ctx().mode_state.keys_by_archetype_id[instance.id]
241
241
  else:
242
242
  return self.value
243
243
 
@@ -249,8 +249,8 @@ class _ArchetypeLifeDescriptor(SonolusDescriptor):
249
249
  def __get__(self, instance, owner):
250
250
  if not ctx():
251
251
  raise RuntimeError("Archetype life is only available during compilation")
252
- if ctx().global_state.mode not in {Mode.PLAY, Mode.WATCH}:
253
- raise RuntimeError(f"Archetype life is not available in mode '{ctx().global_state.mode.value}'")
252
+ if ctx().mode_state.mode not in {Mode.PLAY, Mode.WATCH}:
253
+ raise RuntimeError(f"Archetype life is not available in mode '{ctx().mode_state.mode.value}'")
254
254
  if instance is not None:
255
255
  return _deref(ctx().blocks.ArchetypeLife, instance.id * ArchetypeLife._size_(), ArchetypeLife)
256
256
  else:
@@ -1115,7 +1115,7 @@ def get_archetype_by_name(name: str) -> AnyArchetype:
1115
1115
  name = name._as_py_() # type: ignore
1116
1116
  if not isinstance(name, str):
1117
1117
  raise TypeError(f"Invalid name: '{name}'")
1118
- archetypes_by_name = ctx().global_state.archetypes_by_name
1118
+ archetypes_by_name = ctx().mode_state.archetypes_by_name
1119
1119
  if name not in archetypes_by_name:
1120
1120
  raise KeyError(f"Unknown archetype: '{name}'")
1121
1121
  return archetypes_by_name[name] # type: ignore
@@ -1129,7 +1129,7 @@ def entity_info_at(index: int) -> PlayEntityInfo | WatchEntityInfo | PreviewEnti
1129
1129
  """
1130
1130
  if not ctx():
1131
1131
  raise RuntimeError("Calling entity_info_at is only allowed within a callback")
1132
- match ctx().global_state.mode:
1132
+ match ctx().mode_state.mode:
1133
1133
  case Mode.PLAY:
1134
1134
  return _deref(ctx().blocks.EntityInfoArray, index * PlayEntityInfo._size_(), PlayEntityInfo)
1135
1135
  case Mode.WATCH:
@@ -1137,7 +1137,7 @@ def entity_info_at(index: int) -> PlayEntityInfo | WatchEntityInfo | PreviewEnti
1137
1137
  case Mode.PREVIEW:
1138
1138
  return _deref(ctx().blocks.EntityInfoArray, index * PreviewEntityInfo._size_(), PreviewEntityInfo)
1139
1139
  case _:
1140
- raise RuntimeError(f"Entity info is not available in mode '{ctx().global_state.mode}'")
1140
+ raise RuntimeError(f"Entity info is not available in mode '{ctx().mode_state.mode}'")
1141
1141
 
1142
1142
 
1143
1143
  class PlayEntityInfo(Record):
sonolus/script/bucket.py CHANGED
@@ -156,7 +156,7 @@ class Bucket(Record):
156
156
  """The judgment window of the bucket."""
157
157
  if not ctx():
158
158
  raise RuntimeError("Bucket window access outside of compilation")
159
- match ctx().global_state.mode:
159
+ match ctx().mode_state.mode:
160
160
  case Mode.PLAY:
161
161
  return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
162
162
  case Mode.WATCH:
sonolus/script/debug.py CHANGED
@@ -7,7 +7,7 @@ from sonolus.backend.ops import Op
7
7
  from sonolus.backend.optimize.flow import cfg_to_mermaid
8
8
  from sonolus.backend.optimize.passes import CompilerPass, OptimizerConfig, run_passes
9
9
  from sonolus.backend.optimize.simplify import RenumberVars
10
- from sonolus.script.internal.context import GlobalContextState, ReadOnlyMemory, ctx, set_ctx
10
+ from sonolus.script.internal.context import ModeContextState, ProjectContextState, ctx, set_ctx
11
11
  from sonolus.script.internal.impl import meta_fn, validate_value
12
12
  from sonolus.script.internal.native import native_function
13
13
  from sonolus.script.internal.simulation_context import SimulationContext
@@ -27,7 +27,7 @@ def error(message: str | None = None) -> Never: # type: ignore
27
27
  if not isinstance(message, str):
28
28
  raise ValueError("Expected a string")
29
29
  if ctx():
30
- debug_log(ctx().map_constant(message))
30
+ debug_log(ctx().map_debug_message(message))
31
31
  debug_pause()
32
32
  terminate()
33
33
  else:
@@ -68,6 +68,19 @@ def debug_pause():
68
68
  input("[DEBUG] Paused")
69
69
 
70
70
 
71
+ @meta_fn
72
+ def notify(message: str):
73
+ """Log a code that can be decoded by the dev server and pause the game if in debug mode."""
74
+ message = validate_value(message)._as_py_() # type: ignore
75
+ if not isinstance(message, str):
76
+ raise ValueError("Expected a string")
77
+ if ctx():
78
+ debug_log(ctx().map_debug_message(message))
79
+ debug_pause()
80
+ else:
81
+ print(f"[NOTIFY] {message}")
82
+
83
+
71
84
  @meta_fn
72
85
  def assert_true(value: int | float | bool, message: str | None = None):
73
86
  if not ctx():
@@ -156,13 +169,13 @@ def visualize_cfg(
156
169
  RenumberVars(),
157
170
  ]
158
171
 
159
- global_state = GlobalContextState(
172
+ project_state = ProjectContextState()
173
+ mode_state = ModeContextState(
160
174
  mode,
161
175
  {a: i for i, a in enumerate(archetypes)} if archetypes is not None else None,
162
- ReadOnlyMemory(),
163
176
  )
164
177
 
165
- cfg = callback_to_cfg(global_state, fn, callback, archetype=archetype) # type: ignore
178
+ cfg = callback_to_cfg(project_state, mode_state, fn, callback, archetype=archetype) # type: ignore
166
179
  cfg = run_passes(cfg, passes, OptimizerConfig(mode=mode))
167
180
  return cfg_to_mermaid(cfg)
168
181
 
@@ -39,19 +39,35 @@ _disabled_debug_config = DebugConfig(
39
39
  debug_var = ContextVar("debug_var", default=_disabled_debug_config)
40
40
 
41
41
 
42
- class GlobalContextState:
42
+ class ProjectContextState:
43
+ rom: ReadOnlyMemory
44
+ const_mappings: dict[Any, int]
45
+ debug_str_mappings: dict[str, int]
46
+ lock: Lock
47
+
48
+ def __init__(
49
+ self,
50
+ rom: ReadOnlyMemory | None = None,
51
+ const_mappings: dict[Any, int] | None = None,
52
+ debug_str_mappings: dict[str, int] | None = None,
53
+ ):
54
+ self.rom = ReadOnlyMemory() if rom is None else rom
55
+ self.const_mappings = {} if const_mappings is None else const_mappings
56
+ self.debug_str_mappings = {} if debug_str_mappings is None else debug_str_mappings
57
+ self.lock = Lock()
58
+
59
+
60
+ class ModeContextState:
43
61
  archetypes: dict[type, int]
44
62
  archetypes_by_name: dict[str, type]
45
63
  keys_by_archetype_id: Sequence[int]
46
64
  is_scored_by_archetype_id: Sequence[bool]
47
- rom: ReadOnlyMemory
48
- const_mappings: dict[Any, int]
49
65
  environment_mappings: dict[_GlobalInfo, int]
50
66
  environment_offsets: dict[Block, int]
51
67
  mode: Mode
52
68
  lock: Lock
53
69
 
54
- def __init__(self, mode: Mode, archetypes: dict[type, int] | None = None, rom: ReadOnlyMemory | None = None):
70
+ def __init__(self, mode: Mode, archetypes: dict[type, int] | None = None):
55
71
  from sonolus.script.array import Array
56
72
 
57
73
  self.archetypes = archetypes or {}
@@ -65,8 +81,6 @@ class GlobalContextState:
65
81
  if archetypes
66
82
  else Array[bool, Literal[0]]()
67
83
  )
68
- self.rom = ReadOnlyMemory() if rom is None else rom
69
- self.const_mappings = {}
70
84
  self.environment_mappings = {}
71
85
  self.environment_offsets = {}
72
86
  self.mode = mode
@@ -76,16 +90,19 @@ class GlobalContextState:
76
90
  class CallbackContextState:
77
91
  callback: str
78
92
  used_names: dict[str, int]
93
+ debug_stack: list[str]
79
94
  no_eval: bool
80
95
 
81
96
  def __init__(self, callback: str, no_eval: bool = False):
82
97
  self.callback = callback
83
98
  self.used_names = {}
99
+ self.debug_stack = []
84
100
  self.no_eval = no_eval
85
101
 
86
102
 
87
103
  class Context:
88
- global_state: GlobalContextState
104
+ project_state: ProjectContextState
105
+ mode_state: ModeContextState
89
106
  callback_state: CallbackContextState
90
107
  statements: list[IRStmt]
91
108
  test: IRExpr
@@ -96,13 +113,15 @@ class Context:
96
113
 
97
114
  def __init__(
98
115
  self,
99
- global_state: GlobalContextState,
116
+ project_state: ProjectContextState,
117
+ mode_state: ModeContextState,
100
118
  callback_state: CallbackContextState,
101
119
  scope: Scope | None = None,
102
120
  live: bool = True,
103
121
  foldable_constants: dict[TempBlock, list[float | None]] | None = None,
104
122
  ):
105
- self.global_state = global_state
123
+ self.project_state = project_state
124
+ self.mode_state = mode_state
106
125
  self.callback_state = callback_state
107
126
  self.statements = []
108
127
  self.test = IRConst(0)
@@ -114,11 +133,11 @@ class Context:
114
133
 
115
134
  @property
116
135
  def rom(self) -> ReadOnlyMemory:
117
- return self.global_state.rom
136
+ return self.project_state.rom
118
137
 
119
138
  @property
120
139
  def blocks(self) -> type[Block]:
121
- return self.global_state.mode.blocks
140
+ return self.mode_state.mode.blocks
122
141
 
123
142
  @property
124
143
  def callback(self) -> str:
@@ -182,7 +201,8 @@ class Context:
182
201
 
183
202
  def copy_with_scope(self, scope: Scope) -> Context:
184
203
  return Context(
185
- global_state=self.global_state,
204
+ project_state=self.project_state,
205
+ mode_state=self.mode_state,
186
206
  callback_state=self.callback_state,
187
207
  scope=scope,
188
208
  live=self.live,
@@ -267,25 +287,33 @@ class Context:
267
287
  )
268
288
 
269
289
  def map_constant(self, value: Any) -> int:
270
- with self.global_state.lock:
271
- const_mappings = self.global_state.const_mappings
290
+ with self.project_state.lock:
291
+ const_mappings = self.project_state.const_mappings
272
292
  if value not in const_mappings:
273
293
  const_mappings[value] = len(const_mappings)
274
294
  return const_mappings[value]
275
295
 
296
+ def map_debug_message(self, message: str) -> int:
297
+ with self.project_state.lock:
298
+ message_with_trace = "\n".join([*self.callback_state.debug_stack, message])
299
+ debug_str_mappings = self.project_state.debug_str_mappings
300
+ if message_with_trace not in debug_str_mappings:
301
+ debug_str_mappings[message_with_trace] = len(debug_str_mappings) + 1
302
+ return debug_str_mappings[message_with_trace]
303
+
276
304
  def get_global_base(self, value: _GlobalInfo | _GlobalPlaceholder) -> BlockPlace:
277
- with self.global_state.lock:
278
- block = value.blocks.get(self.global_state.mode)
305
+ with self.mode_state.lock:
306
+ block = value.blocks.get(self.mode_state.mode)
279
307
  if block is None:
280
- raise RuntimeError(f"Global {value} is not available in '{self.global_state.mode.name}' mode")
281
- if value not in self.global_state.environment_mappings:
308
+ raise RuntimeError(f"Global {value} is not available in '{self.mode_state.mode.name}' mode")
309
+ if value not in self.mode_state.environment_mappings:
282
310
  if value.offset is None:
283
- offset = self.global_state.environment_offsets.get(block, 0)
284
- self.global_state.environment_mappings[value] = offset
285
- self.global_state.environment_offsets[block] = offset + value.size
311
+ offset = self.mode_state.environment_offsets.get(block, 0)
312
+ self.mode_state.environment_mappings[value] = offset
313
+ self.mode_state.environment_offsets[block] = offset + value.size
286
314
  else:
287
- self.global_state.environment_mappings[value] = value.offset
288
- return BlockPlace(block, self.global_state.environment_mappings[value])
315
+ self.mode_state.environment_mappings[value] = value.offset
316
+ return BlockPlace(block, self.mode_state.environment_mappings[value])
289
317
 
290
318
  @classmethod
291
319
  def meet(cls, contexts: list[Context]) -> Context:
@@ -303,10 +331,10 @@ class Context:
303
331
  return target
304
332
 
305
333
  def register_archetype(self, type_: type) -> int:
306
- with self.global_state.lock:
307
- if type_ not in self.global_state.archetypes:
308
- self.global_state.archetypes[type_] = len(self.global_state.archetypes)
309
- return self.global_state.archetypes[type_]
334
+ with self.mode_state.lock:
335
+ if type_ not in self.mode_state.archetypes:
336
+ self.mode_state.archetypes[type_] = len(self.mode_state.archetypes)
337
+ return self.mode_state.archetypes[type_]
310
338
 
311
339
 
312
340
  def ctx() -> Context | Any: # Using Any to silence type checker warnings if it's None
sonolus/script/options.py CHANGED
@@ -191,7 +191,7 @@ class _OptionField(SonolusDescriptor):
191
191
  if sim_ctx():
192
192
  return sim_ctx().get_or_put_value((instance, self), lambda: copy(self.info.default))
193
193
  if ctx():
194
- match ctx().global_state.mode:
194
+ match ctx().mode_state.mode:
195
195
  case Mode.PLAY:
196
196
  block = ctx().blocks.LevelOption
197
197
  case Mode.WATCH:
@@ -212,7 +212,7 @@ class _OptionField(SonolusDescriptor):
212
212
  if sim_ctx():
213
213
  return sim_ctx().set_or_put_value((instance, self), lambda: copy(self.info.default), value)
214
214
  if ctx() and debug_config().unchecked_writes:
215
- match ctx().global_state.mode:
215
+ match ctx().mode_state.mode:
216
216
  case Mode.PLAY:
217
217
  block = ctx().blocks.LevelOption
218
218
  case Mode.WATCH:
sonolus/script/project.py CHANGED
@@ -8,7 +8,6 @@ from typing import ClassVar, TypedDict
8
8
 
9
9
  from sonolus.backend.optimize import optimize
10
10
  from sonolus.backend.optimize.passes import CompilerPass
11
- from sonolus.build.compile import CompileCache
12
11
  from sonolus.script.archetype import ArchetypeSchema
13
12
  from sonolus.script.engine import Engine
14
13
  from sonolus.script.level import ExternalLevelData, Level, LevelData
@@ -64,13 +63,11 @@ class Project:
64
63
  port: The port of the development server.
65
64
  config: The build configuration.
66
65
  """
67
- from sonolus.build.cli import build_collection, run_server
66
+ from sonolus.build.cli import run_server
68
67
 
69
68
  if config is None:
70
69
  config = BuildConfig()
71
70
 
72
- cache = CompileCache()
73
- build_collection(self, Path(build_dir), config, cache=cache)
74
71
  run_server(
75
72
  Path(build_dir) / "site",
76
73
  port=port,
@@ -78,7 +75,7 @@ class Project:
78
75
  core_module_names=None,
79
76
  build_dir=Path(build_dir),
80
77
  config=config,
81
- cache=cache,
78
+ project=self,
82
79
  )
83
80
 
84
81
  def build(self, build_dir: PathLike, config: BuildConfig | None = None):