sonolus.py 0.2.0__py3-none-any.whl → 0.2.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.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

@@ -8,10 +8,9 @@ from enum import Enum, StrEnum
8
8
  from types import FunctionType
9
9
  from typing import Annotated, Any, ClassVar, Self, TypedDict, get_origin
10
10
 
11
- from sonolus.backend.ir import IRConst, IRInstr
11
+ 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
- from sonolus.backend.place import BlockPlace
15
14
  from sonolus.script.bucket import Bucket, Judgment
16
15
  from sonolus.script.internal.callbacks import PLAY_CALLBACKS, PREVIEW_CALLBACKS, WATCH_ARCHETYPE_CALLBACKS, CallbackInfo
17
16
  from sonolus.script.internal.context import ctx
@@ -20,9 +19,9 @@ from sonolus.script.internal.generic import validate_concrete_type
20
19
  from sonolus.script.internal.impl import meta_fn, validate_value
21
20
  from sonolus.script.internal.introspection import get_field_specifiers
22
21
  from sonolus.script.internal.native import native_call
23
- from sonolus.script.internal.value import Value
22
+ from sonolus.script.internal.value import BackingValue, DataValue, Value
24
23
  from sonolus.script.num import Num
25
- from sonolus.script.pointer import _deref
24
+ from sonolus.script.pointer import _backing_deref, _deref
26
25
  from sonolus.script.record import Record
27
26
  from sonolus.script.values import zeros
28
27
 
@@ -44,6 +43,17 @@ class _ArchetypeFieldInfo:
44
43
  storage: _StorageType
45
44
 
46
45
 
46
+ class _ExportBackingValue(BackingValue):
47
+ def __init__(self, index: IRExpr):
48
+ self.index = index
49
+
50
+ def read(self) -> IRExpr:
51
+ raise NotImplementedError("Exported fields are write-only")
52
+
53
+ def write(self, value: IRExpr) -> IRStmt:
54
+ return IRInstr(Op.ExportValue, [self.index, value])
55
+
56
+
47
57
  class _ArchetypeField(SonolusDescriptor):
48
58
  def __init__(self, name: str, data_name: str, storage: _StorageType, offset: int, type_: type[Value]):
49
59
  self.name = name
@@ -70,7 +80,20 @@ class _ArchetypeField(SonolusDescriptor):
70
80
  case _ArchetypeLevelData(values=values):
71
81
  result = values[self.name]
72
82
  case _StorageType.EXPORTED:
73
- raise RuntimeError("Exported fields are write-only")
83
+ match instance._data_:
84
+ case _ArchetypeSelfData():
85
+
86
+ def backing_source(i: IRExpr):
87
+ return _ExportBackingValue(IRPureInstr(Op.Add, [i, IRConst(self.offset)]))
88
+
89
+ result = _backing_deref(
90
+ backing_source,
91
+ self.type,
92
+ )
93
+ case _ArchetypeReferenceData():
94
+ raise RuntimeError("Exported fields of other entities are not accessible")
95
+ case _ArchetypeLevelData():
96
+ raise RuntimeError("Exported fields are not available in level data")
74
97
  case _StorageType.MEMORY:
75
98
  match instance._data_:
76
99
  case _ArchetypeSelfData():
@@ -584,6 +607,7 @@ class _BaseArchetype:
584
607
  cls._spawn_signature_ = inspect.Signature(
585
608
  [inspect.Parameter(name, inspect.Parameter.POSITIONAL_OR_KEYWORD) for name in cls._memory_fields_]
586
609
  )
610
+ cls._post_init_fields()
587
611
 
588
612
  @property
589
613
  @abstractmethod
@@ -609,6 +633,10 @@ class _BaseArchetype:
609
633
  case _:
610
634
  raise RuntimeError("Invalid entity data")
611
635
 
636
+ @classmethod
637
+ def _post_init_fields(cls):
638
+ pass
639
+
612
640
 
613
641
  class PlayArchetype(_BaseArchetype):
614
642
  """Base class for play mode archetypes.
@@ -885,6 +913,11 @@ class WatchArchetype(_BaseArchetype):
885
913
  case _:
886
914
  raise RuntimeError("Result is only accessible from the entity itself")
887
915
 
916
+ @classmethod
917
+ def _post_init_fields(cls):
918
+ if cls._exported_fields_:
919
+ raise RuntimeError("Watch archetypes cannot have exported fields")
920
+
888
921
 
889
922
  class PreviewArchetype(_BaseArchetype):
890
923
  """Base class for preview mode archetypes.
@@ -934,6 +967,11 @@ class PreviewArchetype(_BaseArchetype):
934
967
  """The index of this entity."""
935
968
  return self._info.index
936
969
 
970
+ @classmethod
971
+ def _post_init_fields(cls):
972
+ if cls._exported_fields_:
973
+ raise RuntimeError("Preview archetypes cannot have exported fields")
974
+
937
975
 
938
976
  @meta_fn
939
977
  def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
@@ -1066,7 +1104,7 @@ class EntityRef[A: _BaseArchetype](Record):
1066
1104
  """Check if entity at the index is precisely of the archetype."""
1067
1105
  return self.index >= 0 and self.archetype().is_at(self.index)
1068
1106
 
1069
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
1107
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
1070
1108
  ref = getattr(self, "_ref_", None)
1071
1109
  if ref is None:
1072
1110
  return [self.index]
sonolus/script/array.py CHANGED
@@ -12,7 +12,7 @@ from sonolus.script.internal.context import ctx
12
12
  from sonolus.script.internal.error import InternalError
13
13
  from sonolus.script.internal.generic import GenericValue
14
14
  from sonolus.script.internal.impl import meta_fn, validate_value
15
- from sonolus.script.internal.value import Value
15
+ from sonolus.script.internal.value import DataValue, Value
16
16
  from sonolus.script.num import Num
17
17
 
18
18
 
@@ -108,11 +108,11 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
108
108
  return self
109
109
 
110
110
  @classmethod
111
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
111
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
112
112
  iterator = iter(values)
113
113
  return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
114
114
 
115
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
115
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
116
116
  match self._value:
117
117
  case list():
118
118
  return [entry for value in self._value for entry in value._to_list_(level_refs)]
@@ -158,10 +158,14 @@ class ArrayLike[T](Sequence, ABC):
158
158
  return min_index
159
159
 
160
160
  def _max_(self, key: Callable[T, Any] | None = None) -> T:
161
- return self[self.index_of_max(key=key)]
161
+ index = self.index_of_max(key=key)
162
+ assert index != -1
163
+ return self[index]
162
164
 
163
165
  def _min_(self, key: Callable[T, Any] | None = None) -> T:
164
- return self[self.index_of_min(key=key)]
166
+ index = self.index_of_min(key=key)
167
+ assert index != -1
168
+ return self[index]
165
169
 
166
170
  def swap(self, i: Num, j: Num, /):
167
171
  """Swap the values at the given indices.
@@ -1,13 +1,44 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from sonolus.backend.visitor import compile_and_call
3
4
  from sonolus.script.array import Array
4
5
  from sonolus.script.array_like import ArrayLike
5
6
  from sonolus.script.debug import error
7
+ from sonolus.script.internal.context import ctx
8
+ from sonolus.script.internal.impl import meta_fn
6
9
  from sonolus.script.iterator import SonolusIterator
10
+ from sonolus.script.num import Num
11
+ from sonolus.script.pointer import _deref
7
12
  from sonolus.script.record import Record
8
13
  from sonolus.script.values import alloc, copy
9
14
 
10
15
 
16
+ class Box[T](Record):
17
+ """A box that contains a value.
18
+
19
+ This can be helpful for generic code that can handle both Num and non-Num types.
20
+
21
+ Usage:
22
+ ```python
23
+ Box[T](value: T)
24
+ ```
25
+
26
+ Examples:
27
+ ```python
28
+ box = Box(1)
29
+ box = Box[int](2)
30
+
31
+ x: T = ...
32
+ y: T = ...
33
+ box = Box(x)
34
+ box.value = y # Works regardless of whether x is a Num or not
35
+ ```
36
+ """
37
+
38
+ value: T
39
+ """The value contained in the box."""
40
+
41
+
11
42
  class Pair[T, U](Record):
12
43
  """A generic pair of values.
13
44
 
@@ -129,6 +160,17 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
129
160
  self._array[self._size] = value
130
161
  self._size += 1
131
162
 
163
+ def append_unchecked(self, value: T):
164
+ """Append the given value to the end of the array without checking the capacity.
165
+
166
+ Use with caution as this may cause hard to debug issues if the array is full.
167
+
168
+ Args:
169
+ value: The value to append.
170
+ """
171
+ self._array[self._size] = value
172
+ self._size += 1
173
+
132
174
  def extend(self, values: ArrayLike[T]):
133
175
  """Appends copies of the values in the given array to the end of the array.
134
176
 
@@ -258,6 +300,130 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
258
300
  raise TypeError("unhashable type: 'VarArray'")
259
301
 
260
302
 
303
+ class ArrayPointer[T](Record, ArrayLike[T]):
304
+ """An array defined by a size and pointer to the first element.
305
+
306
+ This is intended to be created internally and improper use may result in hard to debug issues.
307
+
308
+ Usage:
309
+ ```python
310
+ ArrayPointer[T](size: int, block: int, offset: int)
311
+ ```
312
+ """
313
+
314
+ size: int
315
+ block: int
316
+ offset: int
317
+
318
+ def __len__(self) -> int:
319
+ """Return the number of elements in the array."""
320
+ return self.size
321
+
322
+ @classmethod
323
+ def element_type(cls) -> type[T]:
324
+ """Return the type of the elements in the array."""
325
+ return cls.type_var_value(T)
326
+
327
+ def _check_index(self, index: int):
328
+ assert 0 <= index < self.size
329
+
330
+ @meta_fn
331
+ def _get_item(self, item: int) -> T:
332
+ if not ctx():
333
+ raise TypeError("ArrayPointer values cannot be accessed outside of a context")
334
+ return _deref(
335
+ self.block,
336
+ self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
337
+ self.element_type(),
338
+ )
339
+
340
+ @meta_fn
341
+ def __getitem__(self, item: int) -> T:
342
+ compile_and_call(self._check_index, item)
343
+ return self._get_item(item)._get_()
344
+
345
+ @meta_fn
346
+ def __setitem__(self, key: int, value: T):
347
+ compile_and_call(self._check_index, key)
348
+ dst = self._get_item(key)
349
+ if self.element_type()._is_value_type_():
350
+ dst._set_(value)
351
+ else:
352
+ dst._copy_from__(value)
353
+
354
+
355
+ class ArraySet[T, Capacity](Record):
356
+ """A set implemented as an array with a fixed maximum capacity.
357
+
358
+ Usage:
359
+ ```python
360
+ ArraySet[T, Capacity].new() # Create a new empty set
361
+ ```
362
+
363
+ Examples:
364
+ ```python
365
+ s = ArraySet[int, 10].new()
366
+ s.add(1)
367
+ s.add(2)
368
+ assert 1 in s
369
+ assert 3 not in s
370
+ s.remove(1)
371
+ assert 1 not in s
372
+ ```
373
+ """
374
+
375
+ _values: VarArray[T, Capacity]
376
+
377
+ @classmethod
378
+ def new(cls):
379
+ """Create a new empty set."""
380
+ element_type = cls.type_var_value(T)
381
+ capacity = cls.type_var_value(Capacity)
382
+ return cls(VarArray[element_type, capacity].new())
383
+
384
+ def __len__(self):
385
+ """Return the number of elements in the set."""
386
+ return len(self._values)
387
+
388
+ def __contains__(self, value):
389
+ """Return whether the given value is present in the set."""
390
+ return value in self._values
391
+
392
+ def __iter__(self):
393
+ """Return an iterator over the values in the set."""
394
+ return self._values.__iter__()
395
+
396
+ def add(self, value: T) -> bool:
397
+ """Add a copy of the given value to the set.
398
+
399
+ This has no effect and returns False if the value is already present or if the set is full.
400
+
401
+ Args:
402
+ value: The value to add.
403
+
404
+ Returns:
405
+ True if the value was added, False otherwise.
406
+ """
407
+ return self._values.set_add(value)
408
+
409
+ def remove(self, value: T) -> bool:
410
+ """Remove the given value from the set.
411
+
412
+ This has no effect and returns False if the value is not present.
413
+
414
+ Args:
415
+ value: The value to remove.
416
+
417
+ Returns:
418
+ True if the value was removed, False otherwise.
419
+ """
420
+ return self._values.set_remove(value)
421
+
422
+ def clear(self):
423
+ """Clear the set, removing all elements."""
424
+ self._values.clear()
425
+
426
+
261
427
  class _ArrayMapEntry[K, V](Record):
262
428
  key: K
263
429
  value: V
sonolus/script/debug.py CHANGED
@@ -16,8 +16,13 @@ debug_log_callback = ContextVar[Callable[[Num], None]]("debug_log_callback")
16
16
 
17
17
 
18
18
  @meta_fn
19
- def error(message: str | None = None) -> None:
20
- message = message._as_py_() if message is not None else "Error"
19
+ def error(message: str | None = None) -> Never:
20
+ """Raise an error.
21
+
22
+ This function is used to raise an error during runtime.
23
+ When this happens, the game will pause in debug mode. The current callback will also immediately return 0.
24
+ """
25
+ message = validate_value(message)._as_py_() if message is not None else "Error"
21
26
  if not isinstance(message, str):
22
27
  raise ValueError("Expected a string")
23
28
  if ctx():
@@ -28,6 +33,19 @@ def error(message: str | None = None) -> None:
28
33
  raise RuntimeError(message)
29
34
 
30
35
 
36
+ @meta_fn
37
+ def static_error(message: str | None = None) -> Never:
38
+ """Raise a static error.
39
+
40
+ This function is used to raise an error during compile-time if the compiler cannot guarantee that
41
+ this function will not be called during runtime.
42
+ """
43
+ message = validate_value(message)._as_py_() if message is not None else "Error"
44
+ if not isinstance(message, str):
45
+ raise ValueError("Expected a string")
46
+ raise RuntimeError(message)
47
+
48
+
31
49
  @meta_fn
32
50
  def debug_log(value: Num):
33
51
  """Log a value in debug mode."""
sonolus/script/engine.py CHANGED
@@ -4,7 +4,7 @@ import json
4
4
  from collections.abc import Callable
5
5
  from os import PathLike
6
6
  from pathlib import Path
7
- from typing import Any
7
+ from typing import Any, Literal
8
8
 
9
9
  from sonolus.build.collection import Asset, load_asset
10
10
  from sonolus.script.archetype import PlayArchetype, PreviewArchetype, WatchArchetype, _BaseArchetype
@@ -81,7 +81,7 @@ class Engine:
81
81
  meta: Additional metadata of the engine.
82
82
  """
83
83
 
84
- version = 12
84
+ version: Literal[13] = 13
85
85
 
86
86
  def __init__(
87
87
  self,
@@ -3,7 +3,7 @@ from typing import Any, ClassVar, Self
3
3
 
4
4
  from sonolus.backend.place import BlockPlace
5
5
  from sonolus.script.internal.impl import meta_fn
6
- from sonolus.script.internal.value import Value
6
+ from sonolus.script.internal.value import DataValue, Value
7
7
 
8
8
 
9
9
  class _Missing:
@@ -94,10 +94,10 @@ class ConstantValue(Value):
94
94
  return self.value()
95
95
 
96
96
  @classmethod
97
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
97
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
98
98
  return cls()
99
99
 
100
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
100
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
101
101
  return []
102
102
 
103
103
  @classmethod
@@ -2,7 +2,7 @@ from collections.abc import Iterable
2
2
  from typing import Any, Self
3
3
 
4
4
  from sonolus.backend.place import BlockPlace
5
- from sonolus.script.internal.value import Value
5
+ from sonolus.script.internal.value import DataValue, Value
6
6
 
7
7
 
8
8
  class TransientValue(Value):
@@ -23,10 +23,10 @@ class TransientValue(Value):
23
23
  raise TypeError(f"{cls.__name__} cannot be dereferenced")
24
24
 
25
25
  @classmethod
26
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
26
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
27
27
  raise TypeError(f"{cls.__name__} cannot be constructed from list")
28
28
 
29
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
29
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
30
30
  raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
31
31
 
32
32
  @classmethod
@@ -1,10 +1,23 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import Iterable
2
+ from collections.abc import Callable, Iterable
3
3
  from typing import Any, Self
4
4
 
5
+ from sonolus.backend.ir import IRConst, IRExpr, IRStmt
5
6
  from sonolus.backend.place import BlockPlace
6
7
 
7
8
 
9
+ class BackingValue:
10
+ def read(self) -> IRExpr:
11
+ raise NotImplementedError()
12
+
13
+ def write(self, value: IRExpr) -> IRStmt:
14
+ raise NotImplementedError()
15
+
16
+
17
+ type DataValue = BlockPlace | BackingValue | float | int | bool
18
+ type BackingSource = Callable[[IRExpr], BackingValue]
19
+
20
+
8
21
  class Value:
9
22
  """Base class for values."""
10
23
 
@@ -59,15 +72,20 @@ class Value:
59
72
  """
60
73
  raise NotImplementedError
61
74
 
75
+ @classmethod
76
+ def _from_backing_source_(cls, source: BackingSource) -> Self:
77
+ """Creates a value from a backing source."""
78
+ return cls._from_list_(source(IRConst(i)) for i in range(cls._size_()))
79
+
62
80
  @classmethod
63
81
  @abstractmethod
64
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
65
- """Creates a value from a list of floats."""
82
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
83
+ """Creates a value from a list of data values."""
66
84
  raise NotImplementedError
67
85
 
68
86
  @abstractmethod
69
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
70
- """Converts this value to a list of floats."""
87
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
88
+ """Converts this value to a list of data values."""
71
89
  raise NotImplementedError
72
90
 
73
91
  @classmethod
@@ -76,9 +94,7 @@ class Value:
76
94
  """Returns the keys to a flat representation of this value."""
77
95
  raise NotImplementedError
78
96
 
79
- def _to_flat_dict_(
80
- self, prefix: str, level_refs: dict[Any, str] | None = None
81
- ) -> dict[str, float | str | BlockPlace]:
97
+ def _to_flat_dict_(self, prefix: str, level_refs: dict[Any, str] | None = None) -> dict[str, DataValue | str]:
82
98
  """Converts this value to a flat dictionary."""
83
99
  return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
84
100
 
@@ -96,12 +112,12 @@ class Value:
96
112
  v: Num
97
113
 
98
114
  a = 1
99
- b = X(a) # (1) _get() is called on a
100
- c = b.v # (2) _get() is called on the value for v
115
+ b = X(a) # (1) _get_() is called on a
116
+ c = b.v # (2) _get_() is called on the value for v
101
117
 
102
118
  # (1) prevents this from changing the value of a
103
119
  # (2) prevents this from changing the value of c
104
- # Thus, both calls to _get() are necessary to ensure values behave immutably.
120
+ # Thus, both calls to _get_() are necessary to ensure values behave immutably.
105
121
  b.v = 2
106
122
  ```
107
123
  """
@@ -1,7 +1,7 @@
1
1
  from typing import Self
2
2
 
3
3
  from sonolus.backend.ops import Op
4
- from sonolus.script.debug import error
4
+ from sonolus.script.debug import static_error
5
5
  from sonolus.script.internal.native import native_function
6
6
  from sonolus.script.num import Num
7
7
  from sonolus.script.record import Record
@@ -24,17 +24,6 @@ class Interval(Record):
24
24
  """Get an empty interval."""
25
25
  return cls(0, 0)
26
26
 
27
- def then(self, length: float) -> Self:
28
- """Get the interval after this one with a given length.
29
-
30
- Args:
31
- length: The length of the interval.
32
-
33
- Returns:
34
- An interval that has the end of this interval as the start and has the given length.
35
- """
36
- return Interval(self.end, self.end + length)
37
-
38
27
  @property
39
28
  def length(self) -> float:
40
29
  """The length of the interval.
@@ -45,7 +34,7 @@ class Interval(Record):
45
34
 
46
35
  @property
47
36
  def is_empty(self) -> bool:
48
- """Whether the interval has length of zero or less."""
37
+ """Whether the has a start greater than its end."""
49
38
  return self.start > self.end
50
39
 
51
40
  @property
@@ -73,7 +62,7 @@ class Interval(Record):
73
62
  case Num(value):
74
63
  return self.start <= value <= self.end
75
64
  case _:
76
- error("Invalid type for interval check")
65
+ static_error("Invalid type for interval check")
77
66
 
78
67
  def __add__(self, other: float | int) -> Self:
79
68
  """Add a value to both ends of the interval.
sonolus/script/num.py CHANGED
@@ -7,11 +7,11 @@ from typing import TYPE_CHECKING, Any, Self, TypeGuard, final, runtime_checkable
7
7
 
8
8
  from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
9
9
  from sonolus.backend.ops import Op
10
- from sonolus.backend.place import BlockPlace, Place
10
+ from sonolus.backend.place import BlockPlace
11
11
  from sonolus.script.internal.context import ctx
12
12
  from sonolus.script.internal.error import InternalError
13
13
  from sonolus.script.internal.impl import meta_fn
14
- from sonolus.script.internal.value import Value
14
+ from sonolus.script.internal.value import BackingValue, DataValue, Value
15
15
 
16
16
 
17
17
  class _NumMeta(type):
@@ -30,15 +30,17 @@ class _Num(Value, metaclass=_NumMeta):
30
30
  # Since we don't support complex numbers, real is equal to the original number
31
31
  __match_args__ = ("real",)
32
32
 
33
- data: BlockPlace | float | int | bool
33
+ data: DataValue
34
34
 
35
- def __init__(self, data: Place | float | int | bool):
35
+ def __init__(self, data: DataValue):
36
36
  if isinstance(data, complex):
37
37
  raise TypeError("Cannot create a Num from a complex number")
38
38
  if isinstance(data, int):
39
39
  data = float(data)
40
40
  if _is_num(data):
41
41
  raise InternalError("Cannot create a Num from a Num")
42
+ if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
43
+ raise TypeError(f"Cannot create a Num from {type(data)}")
42
44
  self.data = data
43
45
 
44
46
  def __str__(self) -> str:
@@ -78,7 +80,7 @@ class _Num(Value, metaclass=_NumMeta):
78
80
  return cls(value)
79
81
 
80
82
  def _is_py_(self) -> bool:
81
- return not isinstance(self.data, BlockPlace)
83
+ return isinstance(self.data, float | int | bool)
82
84
 
83
85
  def _as_py_(self) -> Any:
84
86
  if not self._is_py_():
@@ -88,11 +90,11 @@ class _Num(Value, metaclass=_NumMeta):
88
90
  return self.data
89
91
 
90
92
  @classmethod
91
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
93
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
92
94
  value = next(iter(values))
93
95
  return Num(value)
94
96
 
95
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
97
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue]:
96
98
  return [self.data]
97
99
 
98
100
  @classmethod
@@ -111,10 +113,14 @@ class _Num(Value, metaclass=_NumMeta):
111
113
 
112
114
  def _set_(self, value: Self):
113
115
  if ctx():
114
- if not isinstance(self.data, BlockPlace):
115
- raise ValueError("Cannot set a compile time constant value")
116
- ctx().check_writable(self.data)
117
- ctx().add_statements(IRSet(self.data, value.ir()))
116
+ match self.data:
117
+ case BackingValue():
118
+ ctx().add_statements(self.data.write(value))
119
+ case BlockPlace():
120
+ ctx().check_writable(self.data)
121
+ ctx().add_statements(IRSet(self.data, value.ir()))
122
+ case _:
123
+ raise ValueError("Cannot set a read-only value")
118
124
  else:
119
125
  self.data = value.data
120
126
 
@@ -144,12 +150,17 @@ class _Num(Value, metaclass=_NumMeta):
144
150
  return cls(0)
145
151
 
146
152
  def ir(self):
147
- if isinstance(self.data, BlockPlace):
148
- return IRGet(self.data)
149
- else:
150
- return IRConst(self.data)
153
+ match self.data:
154
+ case BlockPlace():
155
+ return IRGet(self.data)
156
+ case BackingValue():
157
+ return self.data.read()
158
+ case _:
159
+ return IRConst(self.data)
151
160
 
152
161
  def index(self) -> int | BlockPlace:
162
+ if isinstance(self.data, BlockPlace):
163
+ return self._get_().data
153
164
  return self.data
154
165
 
155
166
  def _bin_op(self, other: Self, const_fn: Callable[[Self, Self], Self | None], ir_op: Op) -> Self:
sonolus/script/options.py CHANGED
@@ -15,6 +15,7 @@ from sonolus.script.num import Num
15
15
  @dataclass
16
16
  class _SliderOption:
17
17
  name: str | None
18
+ description: str | None
18
19
  standard: bool
19
20
  advanced: bool
20
21
  scope: str | None
@@ -35,6 +36,8 @@ class _SliderOption:
35
36
  "max": self.max,
36
37
  "step": self.step,
37
38
  }
39
+ if self.description is not None:
40
+ result["description"] = self.description
38
41
  if self.scope is not None:
39
42
  result["scope"] = self.scope
40
43
  if self.unit is not None:
@@ -45,6 +48,7 @@ class _SliderOption:
45
48
  @dataclass
46
49
  class _ToggleOption:
47
50
  name: str | None
51
+ description: str | None
48
52
  standard: bool
49
53
  advanced: bool
50
54
  scope: str | None
@@ -58,6 +62,8 @@ class _ToggleOption:
58
62
  "advanced": self.advanced,
59
63
  "def": int(self.default),
60
64
  }
65
+ if self.description is not None:
66
+ result["description"] = self.description
61
67
  if self.scope is not None:
62
68
  result["scope"] = self.scope
63
69
  return result
@@ -66,6 +72,7 @@ class _ToggleOption:
66
72
  @dataclass
67
73
  class _SelectOption:
68
74
  name: str | None
75
+ description: str | None
69
76
  standard: bool
70
77
  advanced: bool
71
78
  scope: str | None
@@ -81,6 +88,8 @@ class _SelectOption:
81
88
  "def": self.default,
82
89
  "values": self.values,
83
90
  }
91
+ if self.description is not None:
92
+ result["description"] = self.description
84
93
  if self.scope is not None:
85
94
  result["scope"] = self.scope
86
95
  return result
@@ -89,6 +98,7 @@ class _SelectOption:
89
98
  def slider_option(
90
99
  *,
91
100
  name: str | None = None,
101
+ description: str | None = None,
92
102
  standard: bool = False,
93
103
  advanced: bool = False,
94
104
  default: float,
@@ -102,6 +112,7 @@ def slider_option(
102
112
 
103
113
  Args:
104
114
  name: The name of the option.
115
+ description: The description of the option.
105
116
  standard: Whether the option is standard.
106
117
  advanced: Whether the option is advanced.
107
118
  default: The default value of the option.
@@ -111,12 +122,13 @@ def slider_option(
111
122
  unit: The unit of the option.
112
123
  scope: The scope of the option.
113
124
  """
114
- return _SliderOption(name, standard, advanced, scope, default, min, max, step, unit)
125
+ return _SliderOption(name, description, standard, advanced, scope, default, min, max, step, unit)
115
126
 
116
127
 
117
128
  def toggle_option(
118
129
  *,
119
130
  name: str | None = None,
131
+ description: str | None = None,
120
132
  standard: bool = False,
121
133
  advanced: bool = False,
122
134
  default: bool,
@@ -126,17 +138,19 @@ def toggle_option(
126
138
 
127
139
  Args:
128
140
  name: The name of the option.
141
+ description: The description of the option.
129
142
  standard: Whether the option is standard.
130
143
  advanced: Whether the option is advanced.
131
144
  default: The default value of the option.
132
145
  scope: The scope of the option.
133
146
  """
134
- return _ToggleOption(name, standard, advanced, scope, default)
147
+ return _ToggleOption(name, description, standard, advanced, scope, default)
135
148
 
136
149
 
137
150
  def select_option(
138
151
  *,
139
152
  name: str | None = None,
153
+ description: str | None = None,
140
154
  standard: bool = False,
141
155
  advanced: bool = False,
142
156
  default: str | int,
@@ -147,6 +161,7 @@ def select_option(
147
161
 
148
162
  Args:
149
163
  name: The name of the option.
164
+ description: The description of the option.
150
165
  standard: Whether the option is standard.
151
166
  advanced: Whether the option is advanced.
152
167
  default: The default value of the option.
@@ -155,7 +170,7 @@ def select_option(
155
170
  """
156
171
  if isinstance(default, str):
157
172
  default = values.index(default)
158
- return _SelectOption(name, standard, advanced, scope, default, values)
173
+ return _SelectOption(name, description, standard, advanced, scope, default, values)
159
174
 
160
175
 
161
176
  type Options = NewType("Options", Any)
sonolus/script/pointer.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from sonolus.backend.place import BlockPlace
2
2
  from sonolus.script.internal.context import ctx
3
3
  from sonolus.script.internal.impl import meta_fn, validate_value
4
- from sonolus.script.internal.value import Value
4
+ from sonolus.script.internal.value import BackingSource, Value
5
5
  from sonolus.script.num import Num, _is_num
6
6
 
7
7
 
@@ -30,3 +30,11 @@ def _deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
30
30
  if not (isinstance(type_, type) and issubclass(type_, Value)):
31
31
  raise TypeError("type_ must be a Value")
32
32
  return type_._from_place_(BlockPlace(block, offset))
33
+
34
+
35
+ @meta_fn
36
+ def _backing_deref[T: Value](source: BackingSource, type_: type[T]) -> T:
37
+ type_ = validate_value(type_)._as_py_()
38
+ if not isinstance(type_, type) or not issubclass(type_, Value):
39
+ raise TypeError("type_ must be a Value")
40
+ return type_._from_backing_source_(source)
sonolus/script/quad.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import Protocol, Self
4
4
 
5
5
  from sonolus.script.record import Record
6
+ from sonolus.script.values import zeros
6
7
  from sonolus.script.vec import Vec2, pnpoly
7
8
 
8
9
 
@@ -27,6 +28,16 @@ class Quad(Record):
27
28
  br: Vec2
28
29
  """The bottom-right corner of the quad."""
29
30
 
31
+ @classmethod
32
+ def from_quad(cls, value: QuadLike, /) -> Quad:
33
+ """Create a quad from a quad-like value."""
34
+ return cls(
35
+ bl=value.bl,
36
+ tl=value.tl,
37
+ tr=value.tr,
38
+ br=value.br,
39
+ )
40
+
30
41
  @property
31
42
  def center(self) -> Vec2:
32
43
  """The center of the quad."""
@@ -95,6 +106,44 @@ class Quad(Record):
95
106
  """Rotate the quad by the given angle about its center and return a new quad."""
96
107
  return self.rotate_about(angle, self.center)
97
108
 
109
+ def permute(self, count: int = 1, /) -> Self:
110
+ """Perform a cyclic permutation of the quad's vertices and return a new quad.
111
+
112
+ On a square, this operation is equivalent to rotating the square counterclockwise 90 degrees `count` times.
113
+
114
+ Negative values of `count` are allowed and will rotate the quad clockwise.
115
+
116
+ Args:
117
+ count: The number of vertices to shift. Defaults to 1.
118
+
119
+ Returns:
120
+ The permuted quad.
121
+ """
122
+ count = int(count % 4)
123
+ result = zeros(Quad)
124
+ match count:
125
+ case 0:
126
+ result.bl @= self.bl
127
+ result.tl @= self.tl
128
+ result.tr @= self.tr
129
+ result.br @= self.br
130
+ case 1:
131
+ result.bl @= self.br
132
+ result.tl @= self.bl
133
+ result.tr @= self.tl
134
+ result.br @= self.tr
135
+ case 2:
136
+ result.bl @= self.tr
137
+ result.tl @= self.br
138
+ result.tr @= self.bl
139
+ result.br @= self.tl
140
+ case 3:
141
+ result.bl @= self.tl
142
+ result.tl @= self.tr
143
+ result.tr @= self.br
144
+ result.br @= self.bl
145
+ return result
146
+
98
147
  def contains_point(self, point: Vec2, /) -> bool:
99
148
  """Check if the quad contains the given point.
100
149
 
@@ -248,7 +297,7 @@ class Rect(Record):
248
297
  return self.l <= point.x <= self.r and self.b <= point.y <= self.t
249
298
 
250
299
 
251
- class QuadLike(Protocol):
300
+ class _QuadLike(Protocol):
252
301
  """A protocol for types that can be used as quads."""
253
302
 
254
303
  @property
@@ -268,6 +317,11 @@ class QuadLike(Protocol):
268
317
  """The bottom-right corner of the quad."""
269
318
 
270
319
 
320
+ # PyCharm doesn't recognize attributes as satisfying the protocol.
321
+ type QuadLike = _QuadLike | Quad
322
+ """A type that can be used as a quad."""
323
+
324
+
271
325
  def flatten_quad(quad: QuadLike) -> tuple[float, float, float, float, float, float, float, float]:
272
326
  bl = quad.bl
273
327
  tl = quad.tl
sonolus/script/record.py CHANGED
@@ -16,7 +16,7 @@ from sonolus.script.internal.generic import (
16
16
  validate_type_spec,
17
17
  )
18
18
  from sonolus.script.internal.impl import meta_fn
19
- from sonolus.script.internal.value import Value
19
+ from sonolus.script.internal.value import DataValue, Value
20
20
  from sonolus.script.num import Num
21
21
 
22
22
 
@@ -182,11 +182,11 @@ class Record(GenericValue):
182
182
  return self
183
183
 
184
184
  @classmethod
185
- def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
185
+ def _from_list_(cls, values: Iterable[DataValue]) -> Self:
186
186
  iterator = iter(values)
187
187
  return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
188
188
 
189
- def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
189
+ def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
190
190
  result = []
191
191
  for field in self._fields:
192
192
  result.extend(self._value[field.name]._to_list_(level_refs))
sonolus/script/runtime.py CHANGED
@@ -2,7 +2,8 @@ from enum import IntEnum
2
2
 
3
3
  from sonolus.backend.mode import Mode
4
4
  from sonolus.script.array import Array
5
- from sonolus.script.containers import VarArray
5
+ from sonolus.script.array_like import ArrayLike
6
+ from sonolus.script.containers import ArrayPointer
6
7
  from sonolus.script.globals import (
7
8
  _level_life,
8
9
  _level_score,
@@ -315,7 +316,8 @@ class Touch(Record):
315
316
 
316
317
  @_runtime_touch_array
317
318
  class _TouchArray:
318
- touches: Array[Touch, 999]
319
+ # Handled specially, see touches()
320
+ pass
319
321
 
320
322
 
321
323
  @_runtime_skin_transform
@@ -500,6 +502,15 @@ def is_tutorial() -> bool:
500
502
  return ctx() and ctx().global_state.mode == Mode.TUTORIAL
501
503
 
502
504
 
505
+ @meta_fn
506
+ def is_preprocessing() -> bool:
507
+ """Check if the game is in the preprocessing stage.
508
+
509
+ Returns True if the current callback is one of preprocess, spawn_order, spawn_time, or despawn_time.
510
+ """
511
+ return ctx() and ctx().callback in {"preprocess", "spawnOrder", "spawnTime", "despawnTime"}
512
+
513
+
503
514
  @meta_fn
504
515
  def aspect_ratio() -> float:
505
516
  """Get the aspect ratio of the game."""
@@ -640,15 +651,15 @@ def scaled_time() -> float:
640
651
 
641
652
 
642
653
  @meta_fn
643
- def touches() -> VarArray[Touch, 999]:
654
+ def touches() -> ArrayLike[Touch]:
644
655
  """Get the current touches of the game."""
645
656
  if not ctx():
646
- return VarArray(0, Array[Touch, 0]())
657
+ return Array[Touch, 0]()
647
658
  match ctx().global_state.mode:
648
659
  case Mode.PLAY:
649
- return VarArray(_PlayRuntimeUpdate.touch_count, _TouchArray.touches)
660
+ return ArrayPointer[Touch](_PlayRuntimeUpdate.touch_count, ctx().blocks.RuntimeTouchArray, 0)
650
661
  case _:
651
- return VarArray(0, Array[Touch, 0]())
662
+ return Array[Touch, 0]()
652
663
 
653
664
 
654
665
  @meta_fn
sonolus/script/text.py CHANGED
@@ -157,6 +157,15 @@ class StandardText(StrEnum):
157
157
  SIMLINE_COLOR = "#SIMLINE_COLOR"
158
158
  SIMLINE_ALPHA = "#SIMLINE_ALPHA"
159
159
  SIMLINE_ANIMATION = "#SIMLINE_ANIMATION"
160
+ PREVIEW_SCALE_VERTICAL = "#PREVIEW_SCALE_VERTICAL"
161
+ PREVIEW_SCALE_HORIZONTAL = "#PREVIEW_SCALE_HORIZONTAL"
162
+ PREVIEW_TIME = "#PREVIEW_TIME"
163
+ PREVIEW_SCORE = "#PREVIEW_SCORE"
164
+ PREVIEW_BPM = "#PREVIEW_BPM"
165
+ PREVIEW_TIMESCALE = "#PREVIEW_TIMESCALE"
166
+ PREVIEW_BEAT = "#PREVIEW_BEAT"
167
+ PREVIEW_MEASURE = "#PREVIEW_MEASURE"
168
+ PREVIEW_COMBO = "#PREVIEW_COMBO"
160
169
  NONE = "#NONE"
161
170
  ANY = "#ANY"
162
171
  ALL = "#ALL"
sonolus/script/ui.py CHANGED
@@ -20,9 +20,14 @@ class UiMetric(StrEnum):
20
20
 
21
21
 
22
22
  class UiJudgmentErrorStyle(StrEnum):
23
- """The style of the judgment error."""
23
+ """The style of the judgment error.
24
+
25
+ The name of each member refers to what's used for positive (late) judgment errors.
26
+ """
24
27
 
25
28
  NONE = "none"
29
+ LATE = "late"
30
+ EARLY = "early" # Not really useful
26
31
  PLUS = "plus"
27
32
  MINUS = "minus"
28
33
  ARROW_UP = "arrowUp"
@@ -38,9 +43,13 @@ class UiJudgmentErrorStyle(StrEnum):
38
43
  class UiJudgmentErrorPlacement(StrEnum):
39
44
  """The placement of the judgment error."""
40
45
 
41
- BOTH = "both"
42
46
  LEFT = "left"
43
47
  RIGHT = "right"
48
+ LEFT_RIGHT = "leftRight"
49
+ TOP = "top"
50
+ BOTTOM = "bottom"
51
+ TOP_BOTTOM = "topBottom"
52
+ CENTER = "center"
44
53
 
45
54
 
46
55
  class EaseType(StrEnum):
@@ -193,8 +202,8 @@ class UiConfig:
193
202
  scale=UiAnimationTween(1.2, 1, 0.2, EaseType.IN_CUBIC), alpha=UiAnimationTween(1, 1, 0, EaseType.NONE)
194
203
  )
195
204
  )
196
- judgment_error_style: UiJudgmentErrorStyle = UiJudgmentErrorStyle.NONE
197
- judgment_error_placement: UiJudgmentErrorPlacement = UiJudgmentErrorPlacement.BOTH
205
+ judgment_error_style: UiJudgmentErrorStyle = UiJudgmentErrorStyle.LATE
206
+ judgment_error_placement: UiJudgmentErrorPlacement = UiJudgmentErrorPlacement.TOP
198
207
  judgment_error_min: float = 0.0
199
208
 
200
209
  def to_dict(self):
sonolus/script/values.py CHANGED
@@ -33,7 +33,7 @@ def copy[T](value: T) -> T:
33
33
 
34
34
 
35
35
  def swap[T](a: T, b: T):
36
- """Swap the values of the given variables."""
36
+ """Swap the values of the two given arguments."""
37
37
  temp = copy(a)
38
38
  a @= b
39
39
  b @= temp
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Sonolus engine development in Python
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Description-Content-Type: text/markdown
8
8
 
9
9
  # Sonolus.py
10
- Sonolus engine development in Python.
10
+ Sonolus engine development in Python. See [docs](https://sonolus.py.qwewqa.xyz) for more information.
@@ -34,41 +34,41 @@ sonolus/build/level.py,sha256=AjvK4725nqDcg7oGn5kWocBdG-AcirXpku74T7c2epA,673
34
34
  sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
35
35
  sonolus/build/project.py,sha256=DhNqgHnm73qKUOhrg1JPlWEL0Vg7VxcGUbNokpMWzVE,6315
36
36
  sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- sonolus/script/archetype.py,sha256=UbIAn6xmCOUpp6Aa8Y_Bd85LhWglinG6o-TE0sRdLPw,38716
38
- sonolus/script/array.py,sha256=mUOY0cbjbSBssQNwmJ4EbySmyUGg2I-lnLmqDKu8Vh8,9798
39
- sonolus/script/array_like.py,sha256=OVOLMadS-Jj941S-EgoxlFPdaQLT6ZrHzaMnKtB6PfU,8346
37
+ sonolus/script/archetype.py,sha256=I-K9uT4l6S5w4toDplzG8v01w0KTM4TsZZPgiZgwOrA,40098
38
+ sonolus/script/array.py,sha256=l-KdtHvxF4NpCSZAw4t7MG4N5aZ_gOTGkemDobC6mQY,9791
39
+ sonolus/script/array_like.py,sha256=nCIt_TxV7Ck6ovbSXyViqUkxsO9BksFOjUhpwF0uuxA,8444
40
40
  sonolus/script/bucket.py,sha256=oaaB7OXtBWzr8sLzPOpm8-GMhs7FzEhYrI1uVcM0DHM,7453
41
- sonolus/script/containers.py,sha256=tf0-UUSq8NH6k9jIKq6qgXRxanFQ0f5olH6uHghTynk,13171
42
- sonolus/script/debug.py,sha256=-rFPOgtmGLjfBsCPzPKObb4t-x5VN3QYb2BznZGDMgI,2457
41
+ sonolus/script/containers.py,sha256=QUFUk1lDwM2AzIPH6uHkZtZXT2D4_xT3P6pJIp6i7Ew,17640
42
+ sonolus/script/debug.py,sha256=aYyFm8DuoifK0L0pY4G7ZZEEeSqbig90Yogy6Z_M8Yg,3135
43
43
  sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
44
44
  sonolus/script/effect.py,sha256=V9bJvMzs1O4C1PjTOKgsAXov-l4AnDb2h38-DzmeWpI,5838
45
- sonolus/script/engine.py,sha256=_5ZYPT4irTePjki16sFI7A8dzdm3kXAS7tvrp_ocG7Y,10661
45
+ sonolus/script/engine.py,sha256=BhhQTrHuGAAAD6JPQ3R0jvHdimwW83PPghEIdAdtGMA,10683
46
46
  sonolus/script/globals.py,sha256=Z8RfLkgDuXPIKiq-aOqblP0s__ALGmvcKdlyWZH21EM,9166
47
47
  sonolus/script/instruction.py,sha256=PNfxC1dhT_hB0BxhDV3KXMn_kKxfI0t1iZmg8m6ddMU,6725
48
- sonolus/script/interval.py,sha256=H-xedXbiLia-JU-Tafxq2rSoQ7pBOAeNOnyovrQyp14,9462
48
+ sonolus/script/interval.py,sha256=IYNgJx0ngREnEVu_yMAu0MrA_Q7mZuToT8U3fbdb3Sc,9122
49
49
  sonolus/script/iterator.py,sha256=OHnIOKRchzVCMaN33Tubo6EzqV61ZYUxDWIJ2S5G6iQ,4590
50
50
  sonolus/script/level.py,sha256=wR23xk-NOcW_JMRb3R12sqIXCLSZL-7cM3y7IpMF1J0,6333
51
51
  sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
52
- sonolus/script/num.py,sha256=5PmfugfKtQ8Mobt9Ul_agv4XyqZr_EtNNPYMQcL14xQ,14178
53
- sonolus/script/options.py,sha256=AljmjEyWH_Aquv_Jj-sx3GA3l5ZoOSl9acDv19FMHNA,7576
52
+ sonolus/script/num.py,sha256=FpMi-w_rdkozZSFxraNQ4IkOzQpTr4YAH3hbqMIFYy8,14612
53
+ sonolus/script/options.py,sha256=ZD9I898wMrpPQ8IyuDoYef804rZzhSvPoC_0bxovIec,8245
54
54
  sonolus/script/particle.py,sha256=K06ArT9tstRNdbuGIviDmDDWcK3-ieA53LHg0Xvizow,8304
55
- sonolus/script/pointer.py,sha256=Rlu3fW4odvJzZRFNwk6pGA8JLrp4J-PlONBOg3mSA8A,1203
55
+ sonolus/script/pointer.py,sha256=IH2_a0XE76uG_UyYM9jAYIf7qZ5LhUNc9ksXDIvAPZA,1511
56
56
  sonolus/script/printing.py,sha256=mNYu9QWiacBBGZrnePZQMVwbbguoelUps9GiOK_aVRU,2096
57
57
  sonolus/script/project.py,sha256=jLndgGJHdkqFYe-lDl_IzTjQ4gOSuy80en8WoSWXnB8,3340
58
- sonolus/script/quad.py,sha256=e2kXKSp9K46Q9qstLsGLFPxaW7Z2kfVfignA8Ka2-Pw,8375
59
- sonolus/script/record.py,sha256=jHt2RjDD7FZ_eWAukEOZePu9VJaH-rwRqHdoEJpxCNY,11339
60
- sonolus/script/runtime.py,sha256=xmf4HLShrcAqQ8qK3N-hGyVzEolgp34j_JSJH9orwIY,19930
58
+ sonolus/script/quad.py,sha256=7uhBwRSvA4tZ_JFjc3Y9n8C9AwQirX5E8MAA0QaHCak,10067
59
+ sonolus/script/record.py,sha256=EV4wywagBl3RU40Bqo9DRdx7Ta8xBgwKtgKlDF02X0o,11332
60
+ sonolus/script/runtime.py,sha256=MIGqDqNitlI6vFtZ2SP32D6LCLJ3RAuGKSxKKl-1tJw,20303
61
61
  sonolus/script/sprite.py,sha256=CMcRAZ2hejXnaBmY2_n1_rj6hGOgPP5zEW-BpyaEVOY,16256
62
- sonolus/script/text.py,sha256=IsoINZJXefjReYDjJFwVaFsUCdgeQvPBDeywljM2dWo,13025
62
+ sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
63
63
  sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
64
64
  sonolus/script/transform.py,sha256=hH6KSRQC8vV-Z10CRCrGewMYqQwUMH3mQIEmniuC2Zw,10760
65
- sonolus/script/ui.py,sha256=R5J1QSvul-cFCUCjuEHbnzp4-CA2I8u608_wfJRr80s,7277
66
- sonolus/script/values.py,sha256=GWl_gmNzwEBwm5fqj2LErfwOz8iWTJEAoFyOUnkBRS0,1028
65
+ sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
66
+ sonolus/script/values.py,sha256=bwh_9ikxuKyrO_m3_lTKIvZDze4j88Fz-_HT_jB657g,1032
67
67
  sonolus/script/vec.py,sha256=4ntfJ96zxKR-nVZluUgHd-Ud4vNfButfiv7EsroZxfE,7002
68
68
  sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
69
69
  sonolus/script/internal/builtin_impls.py,sha256=w9QqxJQ2YR5pVy820qWmAy0utbsW4hcSzMvA_yNgsvg,8186
70
70
  sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
71
- sonolus/script/internal/constant.py,sha256=4kJpLek7PcdB0aTL8ygV4GYDZSDuHoWHljaIZpTtRrY,3842
71
+ sonolus/script/internal/constant.py,sha256=XljC1bXpjmQsekFFDNbaCLoHLPXzDeE17QDHf-rY70Q,3835
72
72
  sonolus/script/internal/context.py,sha256=xW7yFWr13yf77Uk4C_F7v9Kp4ZP1o30uZuBkvK1KyLA,14157
73
73
  sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
74
74
  sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
@@ -80,11 +80,11 @@ sonolus/script/internal/math_impls.py,sha256=Xk7tLMnV2npzPJWtHlspONQHt09Gh2YLdHh
80
80
  sonolus/script/internal/native.py,sha256=XKlNnWSJ-lxbwVGWhGj_CSSoWsVN18imqT5sAsDJT1w,1551
81
81
  sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
82
82
  sonolus/script/internal/range.py,sha256=lrTanQFHU7RuQxSSPwDdoC30Y8FnHGxcP1Ahditu3zU,2297
83
- sonolus/script/internal/transient.py,sha256=c_Jg4yJJ_xvxMGNx9XECHCdiDlMhjfClf5ntzrPr6uc,1643
83
+ sonolus/script/internal/transient.py,sha256=d6iYhM9f6DPUX5nkYQGm-x0b9XEfZUmB4AtUNnyhixo,1636
84
84
  sonolus/script/internal/tuple_impl.py,sha256=vjXmScLVdeTkDn3t9fgIRqtW31iwngnaP2rmA6nlsLw,3431
85
- sonolus/script/internal/value.py,sha256=hEq0YhXMj4uaGQA-r_W8PDOp_MY97nW30JKlpj-aZLM,4496
86
- sonolus_py-0.2.0.dist-info/METADATA,sha256=LOjlu1hCu6r3VgzL7DbplSaBsjabZ2GWzsDsilNdSFc,238
87
- sonolus_py-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
- sonolus_py-0.2.0.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
89
- sonolus_py-0.2.0.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
90
- sonolus_py-0.2.0.dist-info/RECORD,,
85
+ sonolus/script/internal/value.py,sha256=St7OqBX-Tad_qBfu6aWMFU4lDXRyFnvzzkb-yFmOzg0,5042
86
+ sonolus_py-0.2.1.dist-info/METADATA,sha256=OZkz271-5AHDS0iRKllUD5f3Wb5FL9s7BQghhIhI6Nw,302
87
+ sonolus_py-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
+ sonolus_py-0.2.1.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
89
+ sonolus_py-0.2.1.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
90
+ sonolus_py-0.2.1.dist-info/RECORD,,