sonolus.py 0.11.0__py3-none-any.whl → 0.11.1__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.
@@ -1,7 +1,7 @@
1
1
  from math import isfinite, isinf, isnan
2
2
 
3
3
  from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
4
- from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
4
+ from sonolus.backend.node import EngineNode, FunctionNode
5
5
  from sonolus.backend.ops import Op
6
6
  from sonolus.backend.optimize.flow import BasicBlock, traverse_cfg_reverse_postorder
7
7
  from sonolus.backend.place import BlockPlace
@@ -18,18 +18,18 @@ def cfg_to_engine_node(entry: BasicBlock):
18
18
  }
19
19
  match outgoing:
20
20
  case {**other} if not other:
21
- statements.append(ConstantNode(value=len(block_indexes)))
21
+ statements.append(len(block_indexes))
22
22
  case {None: target, **other} if not other:
23
- statements.append(ConstantNode(value=block_indexes[target]))
23
+ statements.append(block_indexes[target])
24
24
  case {0: f_branch, None: t_branch, **other} if not other:
25
25
  statements.append(
26
26
  FunctionNode(
27
27
  func=Op.If,
28
- args=[
28
+ args=(
29
29
  ir_to_engine_node(block.test),
30
- ConstantNode(value=block_indexes[t_branch]),
31
- ConstantNode(value=block_indexes[f_branch]),
32
- ],
30
+ block_indexes[t_branch],
31
+ block_indexes[f_branch],
32
+ ),
33
33
  )
34
34
  )
35
35
  case {None: default_branch, **other} if len(other) == 1:
@@ -37,11 +37,11 @@ def cfg_to_engine_node(entry: BasicBlock):
37
37
  statements.append(
38
38
  FunctionNode(
39
39
  func=Op.If,
40
- args=[
40
+ args=(
41
41
  ir_to_engine_node(IRPureInstr(Op.Equal, args=[block.test, IRConst(cond)])),
42
- ConstantNode(value=block_indexes[cond_branch]),
43
- ConstantNode(value=block_indexes[default_branch]),
44
- ],
42
+ block_indexes[cond_branch],
43
+ block_indexes[default_branch],
44
+ ),
45
45
  )
46
46
  )
47
47
  case dict() as targets:
@@ -49,23 +49,23 @@ def cfg_to_engine_node(entry: BasicBlock):
49
49
  default = len(block_indexes)
50
50
  conds = [cond for cond in targets if cond is not None]
51
51
  if min(conds) == 0 and max(conds) == len(conds) - 1 and all(int(cond) == cond for cond in conds):
52
- args.extend(ConstantNode(value=block_indexes[targets[cond]]) for cond in range(len(conds)))
52
+ args.extend(block_indexes[targets[cond]] for cond in range(len(conds)))
53
53
  if None in targets:
54
54
  default = block_indexes[targets[None]]
55
- args.append(ConstantNode(value=default))
56
- statements.append(FunctionNode(Op.SwitchIntegerWithDefault, args))
55
+ args.append(default)
56
+ statements.append(FunctionNode(Op.SwitchIntegerWithDefault, tuple(args)))
57
57
  else:
58
58
  for cond, target in targets.items():
59
59
  if cond is None:
60
60
  default = block_indexes[target]
61
61
  continue
62
- args.append(ConstantNode(value=cond))
63
- args.append(ConstantNode(value=block_indexes[target]))
64
- args.append(ConstantNode(value=default))
65
- statements.append(FunctionNode(Op.SwitchWithDefault, args))
66
- block_statements.append(FunctionNode(Op.Execute, statements))
67
- block_statements.append(ConstantNode(value=0))
68
- result = FunctionNode(Op.Block, [FunctionNode(Op.JumpLoop, block_statements)])
62
+ args.append(cond)
63
+ args.append(block_indexes[target])
64
+ args.append(default)
65
+ statements.append(FunctionNode(Op.SwitchWithDefault, tuple(args)))
66
+ block_statements.append(FunctionNode(Op.Execute, tuple(statements)))
67
+ block_statements.append(0)
68
+ result = FunctionNode(Op.Block, (FunctionNode(Op.JumpLoop, tuple(block_statements)),))
69
69
  for block in block_indexes:
70
70
  # Clean up without relying on gc
71
71
  del block.incoming
@@ -81,32 +81,30 @@ def ir_to_engine_node(stmt) -> EngineNode:
81
81
  case int(value) | float(value) | IRConst(value=int(value) | float(value)):
82
82
  value = float(value)
83
83
  if value.is_integer():
84
- return ConstantNode(value=int(value))
84
+ return int(value)
85
85
  elif isfinite(value):
86
- return ConstantNode(value=value)
86
+ return value
87
87
  elif isinf(value):
88
88
  # Read values from ROM
89
- return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
89
+ return FunctionNode(Op.Get, args=(3000, 1 if value > 0 else 2))
90
90
  elif isnan(value):
91
91
  # Read value from ROM
92
- return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=0)])
92
+ return FunctionNode(Op.Get, args=(3000, 0))
93
93
  else:
94
94
  raise ValueError(f"Invalid constant value: {value}")
95
95
  case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
96
- return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
96
+ return FunctionNode(func=op, args=tuple(ir_to_engine_node(arg) for arg in args))
97
97
  case IRGet(place=place):
98
98
  return ir_to_engine_node(place)
99
99
  case BlockPlace() as place:
100
100
  if place.offset == 0:
101
101
  index = ir_to_engine_node(place.index)
102
102
  elif place.index == 0:
103
- index = ConstantNode(value=place.offset)
103
+ index = place.offset
104
104
  else:
105
- index = FunctionNode(
106
- func=Op.Add, args=[ir_to_engine_node(place.index), ConstantNode(value=place.offset)]
107
- )
108
- return FunctionNode(func=Op.Get, args=[ir_to_engine_node(place.block), index])
105
+ index = FunctionNode(func=Op.Add, args=(ir_to_engine_node(place.index), place.offset))
106
+ return FunctionNode(func=Op.Get, args=(ir_to_engine_node(place.block), index))
109
107
  case IRSet(place=place, value=value):
110
- return FunctionNode(func=Op.Set, args=[*ir_to_engine_node(place).args, ir_to_engine_node(value)])
108
+ return FunctionNode(func=Op.Set, args=(*ir_to_engine_node(place).args, ir_to_engine_node(value)))
111
109
  case _:
112
110
  raise TypeError(f"Unsupported IR statement: {stmt}")
@@ -2,7 +2,7 @@ import math
2
2
  import operator
3
3
  import random
4
4
 
5
- from sonolus.backend.node import ConstantNode, EngineNode
5
+ from sonolus.backend.node import EngineNode, FunctionNode
6
6
  from sonolus.backend.ops import Op
7
7
 
8
8
 
@@ -24,8 +24,8 @@ class Interpreter:
24
24
  self.log = []
25
25
 
26
26
  def run(self, node: EngineNode) -> float:
27
- if isinstance(node, ConstantNode):
28
- return node.value
27
+ if not isinstance(node, FunctionNode):
28
+ return node
29
29
  func = node.func
30
30
  args = node.args
31
31
  match func:
sonolus/backend/node.py CHANGED
@@ -1,40 +1,18 @@
1
1
  import textwrap
2
- from dataclasses import dataclass, field
2
+ from typing import NamedTuple
3
3
 
4
4
  from sonolus.backend.ops import Op
5
5
 
6
- type EngineNode = ConstantNode | FunctionNode
6
+ type EngineNode = int | float | FunctionNode
7
7
 
8
8
 
9
- @dataclass(slots=True)
10
- class ConstantNode:
11
- value: float
12
- _hash: int = field(init=False, repr=False)
13
-
14
- def __post_init__(self):
15
- self._hash = hash(self.value)
16
-
17
- def __hash__(self):
18
- return hash(self.value)
19
-
20
-
21
- @dataclass(slots=True)
22
- class FunctionNode:
9
+ class FunctionNode(NamedTuple):
23
10
  func: Op
24
- args: list[EngineNode]
25
- _hash: int = field(init=False, repr=False)
26
-
27
- def __post_init__(self):
28
- self._hash = hash((self.func, tuple(self.args)))
29
-
30
- def __hash__(self):
31
- return self._hash
11
+ args: tuple[EngineNode, ...]
32
12
 
33
13
 
34
14
  def format_engine_node(node: EngineNode) -> str:
35
- if isinstance(node, ConstantNode):
36
- return str(node.value)
37
- elif isinstance(node, FunctionNode):
15
+ if isinstance(node, FunctionNode):
38
16
  match len(node.args):
39
17
  case 0:
40
18
  return f"{node.func.name}()"
@@ -45,4 +23,4 @@ def format_engine_node(node: EngineNode) -> str:
45
23
  textwrap.indent('\n'.join(format_engine_node(arg) for arg in node.args), ' ')
46
24
  }\n)"
47
25
  else:
48
- raise ValueError(f"Invalid engine node: {node}")
26
+ return str(node)
sonolus/build/compile.py CHANGED
@@ -32,9 +32,11 @@ class CompileCache:
32
32
  self._cache = {}
33
33
  self._lock = Lock()
34
34
  self._event = Event()
35
+ self._accessed_hashes = set()
35
36
 
36
37
  def get(self, entry_hash: int) -> EngineNode | None:
37
38
  with self._lock:
39
+ self._accessed_hashes.add(entry_hash)
38
40
  if entry_hash not in self._cache:
39
41
  self._cache[entry_hash] = None # Mark as being compiled
40
42
  return None
@@ -47,10 +49,21 @@ class CompileCache:
47
49
 
48
50
  def set(self, entry_hash: int, node: EngineNode) -> None:
49
51
  with self._lock:
52
+ self._accessed_hashes.add(entry_hash)
50
53
  self._cache[entry_hash] = node
51
54
  self._event.set()
52
55
  self._event.clear()
53
56
 
57
+ def reset_accessed(self) -> None:
58
+ with self._lock:
59
+ self._accessed_hashes.clear()
60
+
61
+ def prune_unaccessed(self) -> None:
62
+ with self._lock:
63
+ unaccessed_hashes = set(self._cache.keys()) - self._accessed_hashes
64
+ for h in unaccessed_hashes:
65
+ del self._cache[h]
66
+
54
67
 
55
68
  def compile_mode(
56
69
  mode: Mode,
@@ -119,6 +119,7 @@ class RebuildCommand:
119
119
  print("Rebuilding...")
120
120
  try:
121
121
  start_time = perf_counter()
122
+ server_state.cache.reset_accessed()
122
123
  server_state.project_state = ProjectContextState.from_build_config(server_state.config)
123
124
  server_state.project = project_module.project
124
125
  build_collection(
@@ -128,6 +129,7 @@ class RebuildCommand:
128
129
  cache=server_state.cache,
129
130
  project_state=server_state.project_state,
130
131
  )
132
+ server_state.cache.prune_unaccessed()
131
133
  end_time = perf_counter()
132
134
  print(f"Rebuild completed in {end_time - start_time:.2f} seconds")
133
135
  except CompilationError:
sonolus/build/node.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from threading import Lock
2
2
  from typing import TypedDict
3
3
 
4
- from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
4
+ from sonolus.backend.node import EngineNode, FunctionNode
5
5
 
6
6
 
7
7
  class ValueOutputNode(TypedDict):
@@ -32,19 +32,17 @@ class OutputNodeGenerator:
32
32
  return self.indexes[node]
33
33
 
34
34
  match node:
35
- case ConstantNode(value):
36
- index = len(self.nodes)
37
- self.nodes.append({"value": value})
38
- self.indexes[node] = index
39
- return index
40
35
  case FunctionNode(func, args):
41
36
  arg_indexes = [self._add(arg) for arg in args]
42
37
  index = len(self.nodes)
43
38
  self.nodes.append({"func": func.value, "args": arg_indexes})
44
39
  self.indexes[node] = index
45
40
  return index
46
- case _:
47
- raise ValueError("Invalid node")
41
+ case constant:
42
+ index = len(self.nodes)
43
+ self.nodes.append({"value": constant})
44
+ self.indexes[node] = index
45
+ return index
48
46
 
49
47
  def get(self):
50
48
  return self.nodes
@@ -12,6 +12,7 @@ from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr, IRStmt
12
12
  from sonolus.backend.mode import Mode
13
13
  from sonolus.backend.ops import Op
14
14
  from sonolus.script.bucket import Bucket, Judgment
15
+ from sonolus.script.debug import static_error
15
16
  from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
16
17
  from sonolus.script.internal.context import ctx
17
18
  from sonolus.script.internal.descriptor import SonolusDescriptor
@@ -1210,6 +1211,8 @@ class EntityRef[A: _BaseArchetype](Record):
1210
1211
 
1211
1212
  Usage:
1212
1213
  ```python
1214
+ ref = EntityRef[MyArchetype](index=123)
1215
+
1213
1216
  class MyArchetype(PlayArchetype):
1214
1217
  ref_1: EntityRef[OtherArchetype] = imported()
1215
1218
  ref_2: EntityRef[Any] = imported()
@@ -1238,6 +1241,12 @@ class EntityRef[A: _BaseArchetype](Record):
1238
1241
  return hash(id(self._ref_))
1239
1242
  return super().__hash__()
1240
1243
 
1244
+ @meta_fn
1245
+ def __bool__(self):
1246
+ if ctx():
1247
+ static_error("EntityRef cannot be used in a boolean context. Check index directly instead.")
1248
+ return True
1249
+
1241
1250
  @meta_fn
1242
1251
  def get(self) -> A:
1243
1252
  """Get the entity."""
@@ -468,11 +468,8 @@ class FrozenNumSet[Size](Record):
468
468
  return len(self._values)
469
469
 
470
470
  def __contains__(self, value: Num) -> bool:
471
- if len(self) < 15:
472
- for i in range(len(self)): # noqa: SIM110
473
- if self._values.get_unchecked(i) == value:
474
- return True
475
- return False
471
+ if len(self) < 8:
472
+ return value in self._as_tuple()
476
473
  else:
477
474
  left = 0
478
475
  right = len(self) - 1
@@ -490,6 +487,10 @@ class FrozenNumSet[Size](Record):
490
487
  def __iter__(self) -> SonolusIterator[Num]:
491
488
  return self._values.__iter__()
492
489
 
490
+ @meta_fn
491
+ def _as_tuple(self) -> tuple[Num, ...]:
492
+ return tuple(self._values.get_unchecked(i) for i in range(Num._accept_(len(self))._as_py_()))
493
+
493
494
 
494
495
  class _ArrayMapEntry[K, V](Record):
495
496
  key: K
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.11.0
3
+ Version: 0.11.1
4
4
  Summary: Sonolus engine development in Python
5
5
  Project-URL: Documentation, https://sonolus.py.qwewqa.xyz/
6
6
  Project-URL: Repository, https://github.com/qwewqa/sonolus.py
@@ -3,11 +3,11 @@ sonolus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  sonolus/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  sonolus/backend/blocks.py,sha256=3peyb9eYBy0s53xNVJ1KmK4IgoyVkkwG-lqDQ_VZTHc,18531
5
5
  sonolus/backend/excepthook.py,sha256=ezsTi8hPXSUhqZ7-H0rmkWcndBQcZFAShF543zzaEPM,1912
6
- sonolus/backend/finalize.py,sha256=kytaqAVyoryWJTyJEbvIGysfxmt_DeCic2oxmrqAPYI,5508
7
- sonolus/backend/interpret.py,sha256=B0jqlLmEGoyO2mxpcvwRwV17Tq_gOE9wLNt26Q5QOfs,14306
6
+ sonolus/backend/finalize.py,sha256=MRp_ATPreSmBGn8N6iwesS50rFWH9FIiQI6_Ea8RVTM,5090
7
+ sonolus/backend/interpret.py,sha256=dnm0SJFfI4KIE6Ca2tKrLq10XwBC9EPSqN7OOYtA8Ws,14304
8
8
  sonolus/backend/ir.py,sha256=eyNXorOQY4zgKOvN4kO1MdJF3sU8H0Qw5RTPqbEjJHY,3854
9
9
  sonolus/backend/mode.py,sha256=NkcPZJm8dn83LX35uP24MtQOCnfRDFZ280dHeEEfauE,613
10
- sonolus/backend/node.py,sha256=eEzPP14jzWJp2xrZCAaPlNtokxdoqg0bSM7xQiwx1j8,1254
10
+ sonolus/backend/node.py,sha256=w5y2GwSc2E9rQvGJNaMzvPW_FYjjfHw4QDUmKs1dAnc,714
11
11
  sonolus/backend/ops.py,sha256=5weB_vIxbkwCSJuzYZyKUk7vVXsSIEDJYRlvE-2ke8A,10572
12
12
  sonolus/backend/place.py,sha256=7qwV732hZ4WP-9GNN8FQSEKssPJZELip1wLXTWfop7Y,4717
13
13
  sonolus/backend/utils.py,sha256=OwD1EPh8j-hsfkLzeKNzPQojT_3kklpJou0WTJNoCbc,2337
@@ -28,18 +28,18 @@ sonolus/backend/optimize/ssa.py,sha256=raQO0furQQRPYb8iIBKfNrJlj-_5wqtI4EWNfLZ8Q
28
28
  sonolus/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  sonolus/build/cli.py,sha256=uLY7JG3PTAb2a1bbjulFlgUGDzsHXyRAfkGV0lbuMgQ,10538
30
30
  sonolus/build/collection.py,sha256=6hniAzriPWBKUeGDkXabNXpbdHiHnqiK9shs6U1OExM,12748
31
- sonolus/build/compile.py,sha256=KOmncDKmGfgzC_FWB_LTxAl0s9w4wnaDe-luACMlCVs,8397
32
- sonolus/build/dev_server.py,sha256=u-p3wVdIPNTHjFLNZzPdW4JlFmeVbqqEle_O3otUhyw,10607
31
+ sonolus/build/compile.py,sha256=YKqldBTWAXz5kzsS9Qmwy3FdrFloYZWvapR5QWsx2G0,8862
32
+ sonolus/build/dev_server.py,sha256=yHD3KGqzebdqcEBOC5JDlNptzlH8Sq4i-DNnh_zXWCA,10705
33
33
  sonolus/build/engine.py,sha256=jMymxbBXu-ekv71uU8TF2KbFaHs3yGjyJAztd1SoRDs,14808
34
34
  sonolus/build/level.py,sha256=KLqUAtxIuIqrzeFURJA97rdqjA5pcvYSmwNZQhElaMQ,702
35
- sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
35
+ sonolus/build/node.py,sha256=Dhuz_-UlRd-EJC7-AP1NuyvrjHWNo7jGssniRh4dZhI,1239
36
36
  sonolus/build/project.py,sha256=Uuz82QtTNFdklrVJ_i7EPp8hSjyOxLU1xAeOloa6G00,8579
37
37
  sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- sonolus/script/archetype.py,sha256=J_0E6a183Spez8NTd_jyH0kjIO81Q1a3i6GrNzjyWuw,49631
38
+ sonolus/script/archetype.py,sha256=TZ9Semvzh6zHtUyJGN7ZrLYkux4zgqxFhrh-dGiaLEo,49907
39
39
  sonolus/script/array.py,sha256=EbrNwl_WuJ0JjjkX0s_VJNXWqvYdm_ljTbyrDEMLGUY,13348
40
40
  sonolus/script/array_like.py,sha256=E6S4TW2muXgcyVkhUASQVt7JSYUkpvdJPgHz6YiSHNo,14708
41
41
  sonolus/script/bucket.py,sha256=LNePLmCwgXfKLmH4Z7ZcTFKWR32eq4AnnagI7jacrsU,7782
42
- sonolus/script/containers.py,sha256=iZWPVvVr24iHBb8umuYV0LrCkr9Xgj1piWaat_he7-w,28643
42
+ sonolus/script/containers.py,sha256=KKySXTh_ON9hut1ScMS31ewRNnVd4t7OwpIZg2bPhno,28676
43
43
  sonolus/script/debug.py,sha256=21_bvhP2cZ4kwS3Spxp8tP6ojIfmthQxlq4gtqrt0lo,7757
44
44
  sonolus/script/easing.py,sha256=2FUJI_nfp990P_armCcRqHm2329O985glJAhSC6tnxs,11379
45
45
  sonolus/script/effect.py,sha256=SfJxSNF3RlPCRXnkt62ZlWhCXw3mmmRCsoMsvTErUP0,7960
@@ -87,8 +87,8 @@ sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDw
87
87
  sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
88
88
  sonolus/script/internal/tuple_impl.py,sha256=WaI5HSF5h03ddXiSHEwzY9ttfsPUItaf86Y5VbZypek,3754
89
89
  sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
90
- sonolus_py-0.11.0.dist-info/METADATA,sha256=hhf11U7J2Zk1rkB-vixzUjS94Ygoh2R1DZ2jmdhnbC4,554
91
- sonolus_py-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- sonolus_py-0.11.0.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
93
- sonolus_py-0.11.0.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
94
- sonolus_py-0.11.0.dist-info/RECORD,,
90
+ sonolus_py-0.11.1.dist-info/METADATA,sha256=RUqohCNBMfRgWQrbN51EW2yocoNb136sH8eRKCr_T_o,554
91
+ sonolus_py-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ sonolus_py-0.11.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
93
+ sonolus_py-0.11.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
94
+ sonolus_py-0.11.1.dist-info/RECORD,,