sonolus.py 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/finalize.py +16 -4
- sonolus/backend/visitor.py +25 -9
- sonolus/script/archetype.py +25 -7
- sonolus/script/array.py +5 -3
- sonolus/script/array_like.py +25 -0
- sonolus/script/containers.py +24 -4
- sonolus/script/internal/builtin_impls.py +10 -5
- sonolus/script/internal/context.py +5 -1
- sonolus/script/internal/range.py +25 -2
- sonolus/script/internal/tuple_impl.py +3 -1
- sonolus/script/interval.py +60 -2
- sonolus/script/quad.py +39 -3
- sonolus/script/record.py +2 -0
- sonolus/script/runtime.py +28 -0
- sonolus/script/stream.py +22 -13
- sonolus/script/transform.py +4 -4
- sonolus/script/vec.py +14 -2
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.3.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.3.dist-info}/RECORD +22 -22
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.3.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.3.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.2.dist-info → sonolus_py-0.3.3.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/finalize.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from math import isfinite, isinf, isnan
|
|
2
|
+
|
|
1
3
|
from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
|
|
2
4
|
from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
|
|
3
5
|
from sonolus.backend.ops import Op
|
|
@@ -54,10 +56,20 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
54
56
|
|
|
55
57
|
def ir_to_engine_node(stmt) -> EngineNode:
|
|
56
58
|
match stmt:
|
|
57
|
-
case int() | float():
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
case int(value) | float(value) | IRConst(value=int(value) | float(value)):
|
|
60
|
+
value = float(value)
|
|
61
|
+
if value.is_integer():
|
|
62
|
+
return ConstantNode(value=int(value))
|
|
63
|
+
elif isfinite(value):
|
|
64
|
+
return ConstantNode(value=value)
|
|
65
|
+
elif isinf(value):
|
|
66
|
+
# Read values from ROM
|
|
67
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
|
|
68
|
+
elif isnan(value):
|
|
69
|
+
# Read value from ROM
|
|
70
|
+
return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=0)])
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Invalid constant value: {value}")
|
|
61
73
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
62
74
|
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
63
75
|
case IRGet(place=place):
|
sonolus/backend/visitor.py
CHANGED
|
@@ -359,7 +359,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
359
359
|
self.loop_head_ctxs.append(header_ctx)
|
|
360
360
|
self.break_ctxs.append([])
|
|
361
361
|
set_ctx(header_ctx)
|
|
362
|
-
has_next = self.
|
|
362
|
+
has_next = self.convert_to_boolean_num(node, self.handle_call(node, iterator.has_next))
|
|
363
363
|
if has_next._is_py_() and not has_next._as_py_():
|
|
364
364
|
# The loop will never run, continue after evaluating the condition
|
|
365
365
|
self.loop_head_ctxs.pop()
|
|
@@ -400,7 +400,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
400
400
|
self.loop_head_ctxs.append(header_ctx)
|
|
401
401
|
self.break_ctxs.append([])
|
|
402
402
|
set_ctx(header_ctx)
|
|
403
|
-
test = self.
|
|
403
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
404
404
|
if test._is_py_():
|
|
405
405
|
if test._as_py_():
|
|
406
406
|
# The loop will run until a break / return
|
|
@@ -454,7 +454,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
454
454
|
set_ctx(after_ctx)
|
|
455
455
|
|
|
456
456
|
def visit_If(self, node):
|
|
457
|
-
test = self.
|
|
457
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
458
458
|
|
|
459
459
|
if test._is_py_():
|
|
460
460
|
if test._as_py_():
|
|
@@ -507,7 +507,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
507
507
|
set_ctx(false_ctx)
|
|
508
508
|
continue
|
|
509
509
|
set_ctx(true_ctx)
|
|
510
|
-
guard =
|
|
510
|
+
guard = (
|
|
511
|
+
self.convert_to_boolean_num(case.guard, self.visit(case.guard)) if case.guard else validate_value(True)
|
|
512
|
+
)
|
|
511
513
|
if guard._is_py_():
|
|
512
514
|
if guard._as_py_():
|
|
513
515
|
for stmt in case.body:
|
|
@@ -544,7 +546,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
544
546
|
match pattern:
|
|
545
547
|
case ast.MatchValue(value=value):
|
|
546
548
|
value = self.visit(value)
|
|
547
|
-
test = self.
|
|
549
|
+
test = self.convert_to_boolean_num(pattern, validate_value(subject == value))
|
|
548
550
|
if test._is_py_():
|
|
549
551
|
if test._as_py_():
|
|
550
552
|
return ctx(), ctx().into_dead()
|
|
@@ -574,7 +576,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
574
576
|
target_len = len(patterns)
|
|
575
577
|
if not (isinstance(subject, Sequence | TupleImpl)):
|
|
576
578
|
return ctx().into_dead(), ctx()
|
|
577
|
-
length_test = self.
|
|
579
|
+
length_test = self.convert_to_boolean_num(pattern, validate_value(_len(subject) == target_len))
|
|
578
580
|
ctx_init = ctx()
|
|
579
581
|
if not length_test._is_py_():
|
|
580
582
|
ctx_init.test = length_test.ir()
|
|
@@ -739,7 +741,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
739
741
|
def visit_UnaryOp(self, node):
|
|
740
742
|
operand = self.visit(node.operand)
|
|
741
743
|
if isinstance(node.op, ast.Not):
|
|
742
|
-
return self.
|
|
744
|
+
return self.convert_to_boolean_num(node, operand).not_()
|
|
743
745
|
op = unary_ops[type(node.op)]
|
|
744
746
|
if operand._is_py_():
|
|
745
747
|
operand_py = operand._as_py_()
|
|
@@ -768,7 +770,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
768
770
|
return validate_value(fn)
|
|
769
771
|
|
|
770
772
|
def visit_IfExp(self, node):
|
|
771
|
-
test = self.
|
|
773
|
+
test = self.convert_to_boolean_num(node.test, self.visit(node.test))
|
|
772
774
|
|
|
773
775
|
if test._is_py_():
|
|
774
776
|
if test._as_py_():
|
|
@@ -1076,7 +1078,7 @@ class Visitor(ast.NodeVisitor):
|
|
|
1076
1078
|
|
|
1077
1079
|
def handle_call[**P, R](
|
|
1078
1080
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1079
|
-
) -> R:
|
|
1081
|
+
) -> R | Value:
|
|
1080
1082
|
"""Handles a call to the given callable."""
|
|
1081
1083
|
self.active_ctx = ctx()
|
|
1082
1084
|
if (
|
|
@@ -1127,6 +1129,20 @@ class Visitor(ast.NodeVisitor):
|
|
|
1127
1129
|
raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
|
|
1128
1130
|
return value
|
|
1129
1131
|
|
|
1132
|
+
def convert_to_boolean_num(self, node, value: Value) -> Num:
|
|
1133
|
+
if _is_num(value):
|
|
1134
|
+
return value
|
|
1135
|
+
if hasattr(type(value), "__bool__"):
|
|
1136
|
+
return self.ensure_boolean_num(self.handle_call(node, type(value).__bool__, validate_value(value)))
|
|
1137
|
+
if hasattr(type(value), "__len__"):
|
|
1138
|
+
length = self.handle_call(node, type(value).__len__, validate_value(value))
|
|
1139
|
+
if not _is_num(length):
|
|
1140
|
+
raise TypeError(f"Invalid type for __len__: {type(length).__name__}")
|
|
1141
|
+
if length._is_py_():
|
|
1142
|
+
return Num._accept_(length._as_py_() > 0)
|
|
1143
|
+
return length > Num._accept_(0)
|
|
1144
|
+
raise TypeError(f"Converting {type(value).__name__} to bool is not supported")
|
|
1145
|
+
|
|
1130
1146
|
def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
|
|
1131
1147
|
parameters: list[inspect.Parameter] = []
|
|
1132
1148
|
pos_only_count = len(arguments.posonlyargs)
|
sonolus/script/archetype.py
CHANGED
|
@@ -549,11 +549,19 @@ class _BaseArchetype:
|
|
|
549
549
|
metadata = _annotation_defaults.get(metadata, metadata)
|
|
550
550
|
if isinstance(metadata, _ArchetypeFieldInfo):
|
|
551
551
|
if field_info is not None:
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
552
|
+
if field_info.storage == metadata.storage and field_info.name is None:
|
|
553
|
+
field_info = metadata
|
|
554
|
+
elif field_info.storage == metadata.storage and (
|
|
555
|
+
metadata.name is None or field_info.name == metadata.name
|
|
556
|
+
):
|
|
557
|
+
pass
|
|
558
|
+
else:
|
|
559
|
+
raise TypeError(
|
|
560
|
+
f"Unexpected multiple field annotations for '{name}', "
|
|
561
|
+
f"expected exactly one of imported, exported, entity_memory, or shared_memory"
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
field_info = metadata
|
|
557
565
|
if field_info is None:
|
|
558
566
|
raise TypeError(
|
|
559
567
|
f"Missing field annotation for '{name}', "
|
|
@@ -880,7 +888,7 @@ class WatchArchetype(_BaseArchetype):
|
|
|
880
888
|
case _ArchetypeSelfData():
|
|
881
889
|
return _deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
|
|
882
890
|
case _ArchetypeReferenceData(index=index):
|
|
883
|
-
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(),
|
|
891
|
+
return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
|
|
884
892
|
case _:
|
|
885
893
|
raise RuntimeError("Info is only accessible from the entity itself")
|
|
886
894
|
|
|
@@ -1106,10 +1114,20 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1106
1114
|
"""Return a new reference with the given archetype type."""
|
|
1107
1115
|
return EntityRef[archetype](index=self.index)
|
|
1108
1116
|
|
|
1117
|
+
@meta_fn
|
|
1109
1118
|
def get(self) -> A:
|
|
1110
1119
|
"""Get the entity."""
|
|
1120
|
+
if ref := getattr(self, "_ref_", None):
|
|
1121
|
+
return ref
|
|
1111
1122
|
return self.archetype().at(self.index)
|
|
1112
1123
|
|
|
1124
|
+
@meta_fn
|
|
1125
|
+
def get_as(self, archetype: type[_BaseArchetype]) -> _BaseArchetype:
|
|
1126
|
+
"""Get the entity as the given archetype type."""
|
|
1127
|
+
if getattr(archetype, "_ref_", None):
|
|
1128
|
+
raise TypeError("Using get_as in level data is not supported.")
|
|
1129
|
+
return self.with_archetype(archetype).get()
|
|
1130
|
+
|
|
1113
1131
|
def archetype_matches(self) -> bool:
|
|
1114
1132
|
"""Check if entity at the index is precisely of the archetype."""
|
|
1115
1133
|
return self.index >= 0 and self.archetype().is_at(self.index)
|
|
@@ -1117,7 +1135,7 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
1117
1135
|
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
1118
1136
|
ref = getattr(self, "_ref_", None)
|
|
1119
1137
|
if ref is None:
|
|
1120
|
-
return
|
|
1138
|
+
return Num._accept_(self.index)._to_list_()
|
|
1121
1139
|
else:
|
|
1122
1140
|
if ref not in level_refs:
|
|
1123
1141
|
raise KeyError("Reference to entity not in level data")
|
sonolus/script/array.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any, Self, final
|
|
|
5
5
|
|
|
6
6
|
from sonolus.backend.ir import IRConst, IRSet
|
|
7
7
|
from sonolus.backend.place import BlockPlace
|
|
8
|
-
from sonolus.script.array_like import ArrayLike
|
|
8
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
9
9
|
from sonolus.script.debug import assert_unreachable
|
|
10
10
|
from sonolus.script.internal.context import ctx
|
|
11
11
|
from sonolus.script.internal.error import InternalError
|
|
@@ -18,6 +18,7 @@ from sonolus.script.num import Num
|
|
|
18
18
|
class ArrayMeta(type):
|
|
19
19
|
@meta_fn
|
|
20
20
|
def __pos__[T](cls: type[T]) -> T:
|
|
21
|
+
"""Create a zero-initialized array instance."""
|
|
21
22
|
return cls._zero_()
|
|
22
23
|
|
|
23
24
|
|
|
@@ -186,7 +187,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
186
187
|
|
|
187
188
|
@meta_fn
|
|
188
189
|
def __getitem__(self, index: Num) -> T:
|
|
189
|
-
index: Num = Num._accept_(index)
|
|
190
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
190
191
|
if index._is_py_() and 0 <= index._as_py_() < self.size():
|
|
191
192
|
const_index = index._as_py_()
|
|
192
193
|
if isinstance(const_index, float) and not const_index.is_integer():
|
|
@@ -230,7 +231,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
230
231
|
|
|
231
232
|
@meta_fn
|
|
232
233
|
def __setitem__(self, index: Num, value: T):
|
|
233
|
-
index: Num = Num._accept_(index)
|
|
234
|
+
index: Num = Num._accept_(get_positive_index(index, self.size()))
|
|
234
235
|
value = self.element_type()._accept_(value)
|
|
235
236
|
if ctx():
|
|
236
237
|
if isinstance(self._value, list):
|
|
@@ -303,6 +304,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
|
|
|
303
304
|
else:
|
|
304
305
|
return f"{type(self).__name__}({', '.join(repr(self[i]) for i in range(self.size()))})"
|
|
305
306
|
|
|
307
|
+
@meta_fn
|
|
306
308
|
def __pos__(self) -> Self:
|
|
307
309
|
"""Return a copy of the array."""
|
|
308
310
|
return self._copy_()
|
sonolus/script/array_like.py
CHANGED
|
@@ -5,6 +5,8 @@ from abc import abstractmethod
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from sonolus.script.internal.context import ctx
|
|
9
|
+
from sonolus.script.internal.impl import meta_fn
|
|
8
10
|
from sonolus.script.iterator import SonolusIterator
|
|
9
11
|
from sonolus.script.num import Num
|
|
10
12
|
from sonolus.script.record import Record
|
|
@@ -302,3 +304,26 @@ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
|
|
|
302
304
|
|
|
303
305
|
def advance(self):
|
|
304
306
|
self.i += 1
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@meta_fn
|
|
310
|
+
def get_positive_index(index: Num, length: Num) -> Num:
|
|
311
|
+
"""Get the positive index for the given index in the array of the given length.
|
|
312
|
+
|
|
313
|
+
This is used to convert negative indixes relative to the end of the array to positive indices.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
index: The index to convert.
|
|
317
|
+
length: The length of the array.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
The positive index.
|
|
321
|
+
"""
|
|
322
|
+
if not ctx():
|
|
323
|
+
return index if index >= 0 else index + length
|
|
324
|
+
index = Num._accept_(index)
|
|
325
|
+
length = Num._accept_(length)
|
|
326
|
+
if index._is_py_() and length._is_py_():
|
|
327
|
+
return Num._accept_(index._as_py_() + length._as_py_() if index._as_py_() < 0 else index._as_py_())
|
|
328
|
+
else:
|
|
329
|
+
return index + (index < 0) * length
|
sonolus/script/containers.py
CHANGED
|
@@ -2,10 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.visitor import compile_and_call
|
|
4
4
|
from sonolus.script.array import Array
|
|
5
|
-
from sonolus.script.array_like import ArrayLike
|
|
5
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
6
6
|
from sonolus.script.debug import error
|
|
7
7
|
from sonolus.script.internal.context import ctx
|
|
8
8
|
from sonolus.script.internal.impl import meta_fn
|
|
9
|
+
from sonolus.script.interval import clamp
|
|
9
10
|
from sonolus.script.iterator import SonolusIterator
|
|
10
11
|
from sonolus.script.num import Num
|
|
11
12
|
from sonolus.script.pointer import _deref
|
|
@@ -140,11 +141,11 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
140
141
|
assert p == Pair(5, 6) # The value of p has changed
|
|
141
142
|
```
|
|
142
143
|
"""
|
|
143
|
-
return self._array[item]
|
|
144
|
+
return self._array[get_positive_index(item, len(self))]
|
|
144
145
|
|
|
145
146
|
def __setitem__(self, key: int, value: T):
|
|
146
147
|
"""Update the element at the given index."""
|
|
147
|
-
self._array[key] = value
|
|
148
|
+
self._array[get_positive_index(key, len(self))] = value
|
|
148
149
|
|
|
149
150
|
def __delitem__(self, key: int):
|
|
150
151
|
"""Remove the element at the given index."""
|
|
@@ -190,6 +191,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
190
191
|
"""
|
|
191
192
|
if index is None:
|
|
192
193
|
index = self._size - 1
|
|
194
|
+
index = get_positive_index(index, len(self))
|
|
193
195
|
assert 0 <= index < self._size
|
|
194
196
|
value = copy(self._array[index])
|
|
195
197
|
self._size -= 1
|
|
@@ -207,7 +209,7 @@ class VarArray[T, Capacity](Record, ArrayLike[T]):
|
|
|
207
209
|
index: The index at which to insert the value. Must be in the range [0, size].
|
|
208
210
|
value: The value to insert.
|
|
209
211
|
"""
|
|
210
|
-
|
|
212
|
+
index = clamp(get_positive_index(index, len(self)), 0, self._size)
|
|
211
213
|
assert self._size < len(self._array)
|
|
212
214
|
self._size += 1
|
|
213
215
|
for i in range(self._size - 1, index, -1):
|
|
@@ -329,6 +331,7 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
329
331
|
|
|
330
332
|
@meta_fn
|
|
331
333
|
def _get_item(self, item: int) -> T:
|
|
334
|
+
item = get_positive_index(item, self.size)
|
|
332
335
|
if not ctx():
|
|
333
336
|
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
334
337
|
return _deref(
|
|
@@ -536,6 +539,23 @@ class ArrayMap[K, V, Capacity](Record):
|
|
|
536
539
|
self._array[self._size] = _ArrayMapEntry(key, value)
|
|
537
540
|
self._size += 1
|
|
538
541
|
|
|
542
|
+
def __delitem__(self, key: K):
|
|
543
|
+
"""Remove the key-value pair associated with the given key.
|
|
544
|
+
|
|
545
|
+
Must be called with a key that is present in the map.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
key: The key to remove
|
|
549
|
+
"""
|
|
550
|
+
for i in range(self._size):
|
|
551
|
+
entry = self._array[i]
|
|
552
|
+
if entry.key == key:
|
|
553
|
+
self._size -= 1
|
|
554
|
+
if i < self._size:
|
|
555
|
+
self._array[i] = self._array[self._size]
|
|
556
|
+
return
|
|
557
|
+
error()
|
|
558
|
+
|
|
539
559
|
def __contains__(self, key: K) -> bool:
|
|
540
560
|
"""Return whether the given key is present in the map.
|
|
541
561
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from typing import overload
|
|
3
3
|
|
|
4
|
+
from sonolus.script.array import Array
|
|
4
5
|
from sonolus.script.array_like import ArrayLike
|
|
5
6
|
from sonolus.script.internal.context import ctx
|
|
6
7
|
from sonolus.script.internal.dict_impl import DictImpl
|
|
@@ -129,6 +130,8 @@ def _max(*args, key: callable = _identity):
|
|
|
129
130
|
(iterable,) = args
|
|
130
131
|
if isinstance(iterable, ArrayLike):
|
|
131
132
|
return compile_and_call(iterable._max_, key=key)
|
|
133
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
134
|
+
return compile_and_call(Array(*iterable.value)._max_, key=key)
|
|
132
135
|
else:
|
|
133
136
|
raise TypeError(f"Unsupported type: {type(iterable)} for max")
|
|
134
137
|
else:
|
|
@@ -169,6 +172,8 @@ def _min(*args, key: callable = _identity):
|
|
|
169
172
|
(iterable,) = args
|
|
170
173
|
if isinstance(iterable, ArrayLike):
|
|
171
174
|
return compile_and_call(iterable._min_, key=key)
|
|
175
|
+
elif isinstance(iterable, TupleImpl) and all(_is_num(v) for v in iterable.value):
|
|
176
|
+
return compile_and_call(Array(*iterable.value)._min_, key=key)
|
|
172
177
|
else:
|
|
173
178
|
raise TypeError(f"Unsupported type: {type(iterable)} for min")
|
|
174
179
|
else:
|
|
@@ -221,12 +226,12 @@ def _float(value=0.0):
|
|
|
221
226
|
return value
|
|
222
227
|
|
|
223
228
|
|
|
224
|
-
@meta_fn
|
|
225
229
|
def _bool(value=False):
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
# Relies on the compiler to perform the conversion in a boolean context
|
|
231
|
+
if value: # noqa: SIM103
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
return False
|
|
230
235
|
|
|
231
236
|
|
|
232
237
|
_int._type_mapping_ = Num
|
sonolus/script/internal/range.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from sonolus.script.array_like import ArrayLike
|
|
1
|
+
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
2
|
+
from sonolus.script.internal.context import ctx
|
|
3
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
2
4
|
from sonolus.script.iterator import SonolusIterator
|
|
3
5
|
from sonolus.script.num import Num
|
|
4
6
|
from sonolus.script.record import Record
|
|
@@ -36,7 +38,7 @@ class Range(Record, ArrayLike[Num]):
|
|
|
36
38
|
return (diff - self.step - 1) // -self.step
|
|
37
39
|
|
|
38
40
|
def __getitem__(self, index: Num) -> Num:
|
|
39
|
-
return self.start + index * self.step
|
|
41
|
+
return self.start + get_positive_index(index, len(self)) * self.step
|
|
40
42
|
|
|
41
43
|
def __setitem__(self, index: Num, value: Num):
|
|
42
44
|
raise TypeError("Range does not support item assignment")
|
|
@@ -79,3 +81,24 @@ class RangeIterator(Record, SonolusIterator):
|
|
|
79
81
|
|
|
80
82
|
def advance(self):
|
|
81
83
|
self.value += self.step
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@meta_fn
|
|
87
|
+
def range_or_tuple(start: Num, stop: Num | None = None, step: Num = 1) -> Range | tuple[Num, ...]:
|
|
88
|
+
if stop is None:
|
|
89
|
+
start, stop = 0, start
|
|
90
|
+
if not ctx():
|
|
91
|
+
return range(start, stop, step) # type: ignore
|
|
92
|
+
start = Num._accept_(start)
|
|
93
|
+
stop = Num._accept_(stop) if stop is not None else None
|
|
94
|
+
step = Num._accept_(step)
|
|
95
|
+
if start._is_py_() and stop._is_py_() and step._is_py_():
|
|
96
|
+
start_int = start._as_py_()
|
|
97
|
+
stop_int = stop._as_py_() if stop is not None else None
|
|
98
|
+
if stop_int is None:
|
|
99
|
+
start_int, stop_int = 0, start_int
|
|
100
|
+
step_int = step._as_py_()
|
|
101
|
+
if start_int % 1 != 0 or stop_int % 1 != 0 or step_int % 1 != 0:
|
|
102
|
+
raise TypeError("Range arguments must be integers")
|
|
103
|
+
return validate_value(tuple(range(int(start_int), int(stop_int), int(step_int)))) # type: ignore
|
|
104
|
+
return Range(start, stop, step)
|
|
@@ -21,8 +21,10 @@ class TupleImpl(TransientValue):
|
|
|
21
21
|
raise TypeError(f"Cannot index tuple with {item}")
|
|
22
22
|
if int(item) != item:
|
|
23
23
|
raise TypeError(f"Cannot index tuple with non-integer {item}")
|
|
24
|
-
if not (
|
|
24
|
+
if not (-len(self.value) <= item < len(self.value)):
|
|
25
25
|
raise IndexError(f"Tuple index out of range: {item}")
|
|
26
|
+
if item < 0:
|
|
27
|
+
item += len(self.value)
|
|
26
28
|
return self.value[int(item)]
|
|
27
29
|
|
|
28
30
|
@meta_fn
|
sonolus/script/interval.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from sonolus.backend.ops import Op
|
|
4
|
+
from sonolus.script.array_like import ArrayLike
|
|
4
5
|
from sonolus.script.debug import static_error
|
|
5
6
|
from sonolus.script.internal.native import native_function
|
|
7
|
+
from sonolus.script.internal.range import range_or_tuple
|
|
6
8
|
from sonolus.script.num import Num
|
|
7
9
|
from sonolus.script.record import Record
|
|
8
10
|
|
|
@@ -296,7 +298,7 @@ def unlerp_clamped(a: float, b: float, x: float, /) -> float:
|
|
|
296
298
|
|
|
297
299
|
@native_function(Op.Remap)
|
|
298
300
|
def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
299
|
-
"""
|
|
301
|
+
"""Linearly remap a value from one interval to another.
|
|
300
302
|
|
|
301
303
|
Args:
|
|
302
304
|
a: The start of the input interval.
|
|
@@ -313,7 +315,7 @@ def remap(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
|
313
315
|
|
|
314
316
|
@native_function(Op.RemapClamped)
|
|
315
317
|
def remap_clamped(a: float, b: float, c: float, d: float, x: float, /) -> float:
|
|
316
|
-
"""
|
|
318
|
+
"""Linearly remap a value from one interval to another, clamped to the output interval.
|
|
317
319
|
|
|
318
320
|
Args:
|
|
319
321
|
a: The start of the input interval.
|
|
@@ -341,3 +343,59 @@ def clamp(x: float, a: float, b: float, /) -> float:
|
|
|
341
343
|
The clamped value.
|
|
342
344
|
"""
|
|
343
345
|
return max(a, min(b, x))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def interp(
|
|
349
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
350
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
351
|
+
x: float,
|
|
352
|
+
) -> float:
|
|
353
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
354
|
+
|
|
355
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
356
|
+
For values of x outside the range of xp, the slope of the first or last segment is used to extrapolate.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
xp: The x-coordinates of the points in increasing order.
|
|
360
|
+
fp: The y-coordinates of the points.
|
|
361
|
+
x: The x-coordinate to interpolate.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The interpolated value.
|
|
365
|
+
"""
|
|
366
|
+
assert len(xp) == len(fp)
|
|
367
|
+
assert len(xp) >= 2
|
|
368
|
+
for i in range_or_tuple(1, len(xp) - 1):
|
|
369
|
+
# At i == 1, x may be less than x[0], but since we're extrapolating, we use the first segment regardless.
|
|
370
|
+
if x <= xp[i]:
|
|
371
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
372
|
+
# x > xp[-2] so we can just use the last segment regardless of whether x is in it or to the right of it.
|
|
373
|
+
return remap(xp[-2], xp[-1], fp[-2], fp[-1], x)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def interp_clamped(
|
|
377
|
+
xp: ArrayLike[float] | tuple[float, ...],
|
|
378
|
+
fp: ArrayLike[float] | tuple[float, ...],
|
|
379
|
+
x: float,
|
|
380
|
+
):
|
|
381
|
+
"""Linearly interpolate a value within a sequence of points.
|
|
382
|
+
|
|
383
|
+
The sequence must have at least 2 elements and be sorted in increasing order of x-coordinates.
|
|
384
|
+
For x-coordinates outside the range of the sequence, the respective endpoint of fp is returned.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
xp: The x-coordinates of the points in increasing order.
|
|
388
|
+
fp: The y-coordinates of the points.
|
|
389
|
+
x: The x-coordinate to interpolate.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
The interpolated value.
|
|
393
|
+
"""
|
|
394
|
+
assert len(xp) == len(fp)
|
|
395
|
+
assert len(xp) >= 2
|
|
396
|
+
if x <= xp[0]:
|
|
397
|
+
return fp[0]
|
|
398
|
+
for i in range_or_tuple(1, len(xp)):
|
|
399
|
+
if x <= xp[i]:
|
|
400
|
+
return remap(xp[i - 1], xp[i], fp[i - 1], fp[i], x)
|
|
401
|
+
return fp[-1]
|
sonolus/script/quad.py
CHANGED
|
@@ -28,6 +28,20 @@ class Quad(Record):
|
|
|
28
28
|
br: Vec2
|
|
29
29
|
"""The bottom-right corner of the quad."""
|
|
30
30
|
|
|
31
|
+
@classmethod
|
|
32
|
+
def zero(cls) -> Quad:
|
|
33
|
+
"""Return a quad with all corners set to (0, 0).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A new quad with all corners at the origin.
|
|
37
|
+
"""
|
|
38
|
+
return cls(
|
|
39
|
+
bl=Vec2.zero(),
|
|
40
|
+
tl=Vec2.zero(),
|
|
41
|
+
tr=Vec2.zero(),
|
|
42
|
+
br=Vec2.zero(),
|
|
43
|
+
)
|
|
44
|
+
|
|
31
45
|
@classmethod
|
|
32
46
|
def from_quad(cls, value: QuadLike, /) -> Quad:
|
|
33
47
|
"""Create a quad from a quad-like value."""
|
|
@@ -80,7 +94,14 @@ class Quad(Record):
|
|
|
80
94
|
).translate(self.center * (Vec2(1, 1) - factor))
|
|
81
95
|
|
|
82
96
|
def rotate(self, angle: float, /) -> Self:
|
|
83
|
-
"""Rotate the quad by the given angle about the origin and return a new quad.
|
|
97
|
+
"""Rotate the quad by the given angle about the origin and return a new quad.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A new quad rotated by the given angle.
|
|
104
|
+
"""
|
|
84
105
|
return Quad(
|
|
85
106
|
bl=self.bl.rotate(angle),
|
|
86
107
|
tl=self.tl.rotate(angle),
|
|
@@ -94,7 +115,15 @@ class Quad(Record):
|
|
|
94
115
|
/,
|
|
95
116
|
pivot: Vec2,
|
|
96
117
|
) -> Self:
|
|
97
|
-
"""Rotate the quad by the given angle about the given pivot and return a new quad.
|
|
118
|
+
"""Rotate the quad by the given angle about the given pivot and return a new quad.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
122
|
+
pivot: The pivot point for rotation.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
A new quad rotated about the pivot by the given angle.
|
|
126
|
+
"""
|
|
98
127
|
return Quad(
|
|
99
128
|
bl=self.bl.rotate_about(angle, pivot),
|
|
100
129
|
tl=self.tl.rotate_about(angle, pivot),
|
|
@@ -103,7 +132,14 @@ class Quad(Record):
|
|
|
103
132
|
)
|
|
104
133
|
|
|
105
134
|
def rotate_centered(self, angle: float, /) -> Self:
|
|
106
|
-
"""Rotate the quad by the given angle about its center and return a new quad.
|
|
135
|
+
"""Rotate the quad by the given angle about its center and return a new quad.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A new quad rotated about its center by the given angle.
|
|
142
|
+
"""
|
|
107
143
|
return self.rotate_about(angle, self.center)
|
|
108
144
|
|
|
109
145
|
def permute(self, count: int = 1, /) -> Self:
|
sonolus/script/record.py
CHANGED
|
@@ -23,6 +23,7 @@ from sonolus.script.num import Num
|
|
|
23
23
|
class RecordMeta(type):
|
|
24
24
|
@meta_fn
|
|
25
25
|
def __pos__[T](cls: type[T]) -> T:
|
|
26
|
+
"""Create a zero-initialized record instance."""
|
|
26
27
|
return cls._zero_()
|
|
27
28
|
|
|
28
29
|
|
|
@@ -283,6 +284,7 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
283
284
|
def __hash__(self):
|
|
284
285
|
return hash(tuple(field.__get__(self) for field in self._fields))
|
|
285
286
|
|
|
287
|
+
@meta_fn
|
|
286
288
|
def __pos__(self) -> Self:
|
|
287
289
|
"""Return a copy of the record."""
|
|
288
290
|
return self._copy_()
|
sonolus/script/runtime.py
CHANGED
|
@@ -988,6 +988,25 @@ def time() -> float:
|
|
|
988
988
|
return 0
|
|
989
989
|
|
|
990
990
|
|
|
991
|
+
@meta_fn
|
|
992
|
+
def offset_adjusted_time() -> float:
|
|
993
|
+
"""Get the current time of the game adjusted by the input offset.
|
|
994
|
+
|
|
995
|
+
Returns 0 in preview mode and tutorial mode.
|
|
996
|
+
"""
|
|
997
|
+
if not ctx():
|
|
998
|
+
return 0
|
|
999
|
+
match ctx().global_state.mode:
|
|
1000
|
+
case Mode.PLAY:
|
|
1001
|
+
return _PlayRuntimeUpdate.time - _PlayRuntimeEnvironment.input_offset
|
|
1002
|
+
case Mode.WATCH:
|
|
1003
|
+
return _WatchRuntimeUpdate.time - _WatchRuntimeEnvironment.input_offset
|
|
1004
|
+
case Mode.TUTORIAL:
|
|
1005
|
+
return _TutorialRuntimeUpdate.time
|
|
1006
|
+
case _:
|
|
1007
|
+
return 0
|
|
1008
|
+
|
|
1009
|
+
|
|
991
1010
|
@meta_fn
|
|
992
1011
|
def delta_time() -> float:
|
|
993
1012
|
"""Get the time elapsed since the last frame.
|
|
@@ -1026,6 +1045,15 @@ def scaled_time() -> float:
|
|
|
1026
1045
|
return 0
|
|
1027
1046
|
|
|
1028
1047
|
|
|
1048
|
+
@meta_fn
|
|
1049
|
+
def prev_time() -> float:
|
|
1050
|
+
"""Get the time of the previous frame.
|
|
1051
|
+
|
|
1052
|
+
Returns 0 in preview mode.
|
|
1053
|
+
"""
|
|
1054
|
+
return time() - delta_time()
|
|
1055
|
+
|
|
1056
|
+
|
|
1029
1057
|
@meta_fn
|
|
1030
1058
|
def touches() -> ArrayLike[Touch]:
|
|
1031
1059
|
"""Get the current touches of the game."""
|
sonolus/script/stream.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from math import inf
|
|
3
4
|
from typing import cast, dataclass_transform
|
|
4
5
|
|
|
5
6
|
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr
|
|
@@ -14,7 +15,7 @@ from sonolus.script.internal.value import BackingValue, Value
|
|
|
14
15
|
from sonolus.script.iterator import SonolusIterator
|
|
15
16
|
from sonolus.script.num import Num
|
|
16
17
|
from sonolus.script.record import Record
|
|
17
|
-
from sonolus.script.runtime import
|
|
18
|
+
from sonolus.script.runtime import prev_time, time
|
|
18
19
|
from sonolus.script.values import sizeof
|
|
19
20
|
|
|
20
21
|
|
|
@@ -316,6 +317,14 @@ class Stream[T](Record):
|
|
|
316
317
|
_check_can_read_stream()
|
|
317
318
|
return self[self.next_key_inclusive(key)]
|
|
318
319
|
|
|
320
|
+
def get_previous_inclusive(self, key: int | float) -> T:
|
|
321
|
+
"""Get the value corresponding to the previous key, or the value at the given key if it is in the stream.
|
|
322
|
+
|
|
323
|
+
Equivalent to `self[self.previous_key_inclusive(key)]`.
|
|
324
|
+
"""
|
|
325
|
+
_check_can_read_stream()
|
|
326
|
+
return self[self.previous_key_inclusive(key)]
|
|
327
|
+
|
|
319
328
|
def iter_items_from(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
320
329
|
"""Iterate over the items in the stream in ascending order starting from the given key.
|
|
321
330
|
|
|
@@ -345,7 +354,7 @@ class Stream[T](Record):
|
|
|
345
354
|
```
|
|
346
355
|
"""
|
|
347
356
|
_check_can_read_stream()
|
|
348
|
-
return _StreamBoundedAscIterator(self, self.next_key(
|
|
357
|
+
return _StreamBoundedAscIterator(self, self.next_key(prev_time()), time())
|
|
349
358
|
|
|
350
359
|
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
351
360
|
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
@@ -391,7 +400,7 @@ class Stream[T](Record):
|
|
|
391
400
|
```
|
|
392
401
|
"""
|
|
393
402
|
_check_can_read_stream()
|
|
394
|
-
return _StreamBoundedAscKeyIterator(self, self.next_key(
|
|
403
|
+
return _StreamBoundedAscKeyIterator(self, self.next_key(prev_time()), time())
|
|
395
404
|
|
|
396
405
|
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
397
406
|
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
@@ -437,7 +446,7 @@ class Stream[T](Record):
|
|
|
437
446
|
```
|
|
438
447
|
"""
|
|
439
448
|
_check_can_read_stream()
|
|
440
|
-
return _StreamBoundedAscValueIterator(self, self.next_key(
|
|
449
|
+
return _StreamBoundedAscValueIterator(self, self.next_key(prev_time()), time())
|
|
441
450
|
|
|
442
451
|
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
443
452
|
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
@@ -513,7 +522,7 @@ class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
513
522
|
return self.current_key, self.stream[self.current_key]
|
|
514
523
|
|
|
515
524
|
def advance(self):
|
|
516
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
525
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
517
526
|
|
|
518
527
|
|
|
519
528
|
class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -528,7 +537,7 @@ class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]
|
|
|
528
537
|
return self.current_key, self.stream[self.current_key]
|
|
529
538
|
|
|
530
539
|
def advance(self):
|
|
531
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
540
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
532
541
|
|
|
533
542
|
|
|
534
543
|
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -542,7 +551,7 @@ class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
542
551
|
return self.current_key, self.stream[self.current_key]
|
|
543
552
|
|
|
544
553
|
def advance(self):
|
|
545
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
554
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
546
555
|
|
|
547
556
|
|
|
548
557
|
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -556,7 +565,7 @@ class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
556
565
|
return self.current_key
|
|
557
566
|
|
|
558
567
|
def advance(self):
|
|
559
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
568
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
560
569
|
|
|
561
570
|
|
|
562
571
|
class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -571,7 +580,7 @@ class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
571
580
|
return self.current_key
|
|
572
581
|
|
|
573
582
|
def advance(self):
|
|
574
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
583
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
575
584
|
|
|
576
585
|
|
|
577
586
|
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -585,7 +594,7 @@ class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
585
594
|
return self.current_key
|
|
586
595
|
|
|
587
596
|
def advance(self):
|
|
588
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
597
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
589
598
|
|
|
590
599
|
|
|
591
600
|
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -599,7 +608,7 @@ class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
599
608
|
return self.stream[self.current_key]
|
|
600
609
|
|
|
601
610
|
def advance(self):
|
|
602
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
611
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
603
612
|
|
|
604
613
|
|
|
605
614
|
class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -614,7 +623,7 @@ class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
614
623
|
return self.stream[self.current_key]
|
|
615
624
|
|
|
616
625
|
def advance(self):
|
|
617
|
-
self.current_key = self.stream.next_key_or_default(self.current_key,
|
|
626
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
618
627
|
|
|
619
628
|
|
|
620
629
|
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -628,7 +637,7 @@ class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
|
628
637
|
return self.stream[self.current_key]
|
|
629
638
|
|
|
630
639
|
def advance(self):
|
|
631
|
-
self.current_key = self.stream.previous_key_or_default(self.current_key,
|
|
640
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
632
641
|
|
|
633
642
|
|
|
634
643
|
@native_function(Op.StreamGetNextKey)
|
sonolus/script/transform.py
CHANGED
|
@@ -137,7 +137,7 @@ class Transform2d(Record):
|
|
|
137
137
|
"""Rotate about the origin and return a new transform.
|
|
138
138
|
|
|
139
139
|
Args:
|
|
140
|
-
angle: The angle of rotation in radians.
|
|
140
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
141
141
|
|
|
142
142
|
Returns:
|
|
143
143
|
A new transform after rotation.
|
|
@@ -160,7 +160,7 @@ class Transform2d(Record):
|
|
|
160
160
|
"""Rotate about the pivot and return a new transform.
|
|
161
161
|
|
|
162
162
|
Args:
|
|
163
|
-
angle: The angle of rotation in radians.
|
|
163
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
164
164
|
pivot: The pivot point for rotation.
|
|
165
165
|
|
|
166
166
|
Returns:
|
|
@@ -470,7 +470,7 @@ class InvertibleTransform2d(Record):
|
|
|
470
470
|
"""Rotate about the origin and return a new transform.
|
|
471
471
|
|
|
472
472
|
Args:
|
|
473
|
-
angle: The angle of rotation in radians.
|
|
473
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
474
474
|
|
|
475
475
|
Returns:
|
|
476
476
|
A new invertible transform after rotation.
|
|
@@ -484,7 +484,7 @@ class InvertibleTransform2d(Record):
|
|
|
484
484
|
"""Rotate about the pivot and return a new transform.
|
|
485
485
|
|
|
486
486
|
Args:
|
|
487
|
-
angle: The angle of rotation in radians.
|
|
487
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
488
488
|
pivot: The pivot point for rotation.
|
|
489
489
|
|
|
490
490
|
Returns:
|
sonolus/script/vec.py
CHANGED
|
@@ -73,6 +73,18 @@ class Vec2(Record):
|
|
|
73
73
|
"""
|
|
74
74
|
return cls(x=1, y=0)
|
|
75
75
|
|
|
76
|
+
@classmethod
|
|
77
|
+
def unit(cls, angle: Num) -> Self:
|
|
78
|
+
"""Return a unit vector (magnitude 1) at a given angle in radians.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
angle: The angle in radians.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A new unit vector at the specified angle.
|
|
85
|
+
"""
|
|
86
|
+
return Vec2(x=cos(angle), y=sin(angle))
|
|
87
|
+
|
|
76
88
|
@property
|
|
77
89
|
def magnitude(self) -> Num:
|
|
78
90
|
"""Calculate the magnitude (length) of the vector.
|
|
@@ -106,7 +118,7 @@ class Vec2(Record):
|
|
|
106
118
|
"""Rotate the vector by a given angle in radians and return a new vector.
|
|
107
119
|
|
|
108
120
|
Args:
|
|
109
|
-
angle: The angle to rotate the vector by, in radians.
|
|
121
|
+
angle: The angle to rotate the vector by, in radians. Positive angles rotate counterclockwise.
|
|
110
122
|
|
|
111
123
|
Returns:
|
|
112
124
|
A new vector rotated by the given angle.
|
|
@@ -120,7 +132,7 @@ class Vec2(Record):
|
|
|
120
132
|
"""Rotate the vector about a pivot by a given angle in radians and return a new vector.
|
|
121
133
|
|
|
122
134
|
Args:
|
|
123
|
-
angle: The angle to rotate the vector by, in radians.
|
|
135
|
+
angle: The angle to rotate the vector by, in radians. Positive angles rotate counterclockwise.
|
|
124
136
|
pivot: The pivot point to rotate about.
|
|
125
137
|
|
|
126
138
|
Returns:
|
|
@@ -3,7 +3,7 @@ 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=pqI9gtPBh0mlTgMNqul8bEVO1ARzKb8pNE9EN_CyDpk,994
|
|
6
|
-
sonolus/backend/finalize.py,sha256=
|
|
6
|
+
sonolus/backend/finalize.py,sha256=Ab3Z-TdHhQXQ2KAMfQlDy2VYm4rjPpOZxbFonwQ-8Ic,4614
|
|
7
7
|
sonolus/backend/interpret.py,sha256=B0jqlLmEGoyO2mxpcvwRwV17Tq_gOE9wLNt26Q5QOfs,14306
|
|
8
8
|
sonolus/backend/ir.py,sha256=TCDLMvlX2S8emFDQwFVeD2OUC4fnhbrMObgYtoa_7PQ,2845
|
|
9
9
|
sonolus/backend/mode.py,sha256=NkcPZJm8dn83LX35uP24MtQOCnfRDFZ280dHeEEfauE,613
|
|
@@ -11,7 +11,7 @@ sonolus/backend/node.py,sha256=eEzPP14jzWJp2xrZCAaPlNtokxdoqg0bSM7xQiwx1j8,1254
|
|
|
11
11
|
sonolus/backend/ops.py,sha256=7DERBPU6z9Bz3i6UxyLdFTXYcCpvRZVNWw4ZQ-Djwnk,10510
|
|
12
12
|
sonolus/backend/place.py,sha256=jABvLNNE-2pklTcb9WnyfHK8c-tYxn0ObsoLp5LYd5I,4703
|
|
13
13
|
sonolus/backend/utils.py,sha256=BuJHI-qTVn-vYlQkSr4fSDPpgckkiO2RJmwlDXY_KzM,1818
|
|
14
|
-
sonolus/backend/visitor.py,sha256=
|
|
14
|
+
sonolus/backend/visitor.py,sha256=zANjR9PkUnWS5XnvA65OnYUSe54hNcad_nb1TFFTVMY,50213
|
|
15
15
|
sonolus/backend/optimize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
sonolus/backend/optimize/allocate.py,sha256=-cpnCQ7S6wH3y-hCUGb2KT5TMuREifhsMXMI0-YtNjY,7380
|
|
17
17
|
sonolus/backend/optimize/constant_evaluation.py,sha256=ozYB__haE2A_L2NBh2H3IT__Z65FBBtbjh6BJfQRC_A,15988
|
|
@@ -34,18 +34,18 @@ 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=
|
|
38
|
-
sonolus/script/array.py,sha256=
|
|
39
|
-
sonolus/script/array_like.py,sha256=
|
|
37
|
+
sonolus/script/archetype.py,sha256=rECMAhTbVyCuxOYO3gyuBle9oCT8W18EhsUz2U_8alQ,41417
|
|
38
|
+
sonolus/script/array.py,sha256=mq5-Im7PS0bJugBPI61D5M0gGZEUSbVcbOLORl-bfjM,11915
|
|
39
|
+
sonolus/script/array_like.py,sha256=2otrTauxlkyR0PltjNLVHQoq4HbW4m4iWq5ygEJklaY,9390
|
|
40
40
|
sonolus/script/bucket.py,sha256=YFSyKS4ZngxerBlKwFBSCRAVewgQdwZ1-NqfPKcPZxI,7519
|
|
41
|
-
sonolus/script/containers.py,sha256
|
|
41
|
+
sonolus/script/containers.py,sha256=-6QooG0vIXF3fmP97Lxada7Jklm6sXQFwkevGzVNo7s,18403
|
|
42
42
|
sonolus/script/debug.py,sha256=JIMcGD6cQVWtIw3IIhq-5WenZZu0iArH6SAS1-XfR_8,5076
|
|
43
43
|
sonolus/script/easing.py,sha256=7zaDKIfM_whUpb4FBz1DAF4NNG2vk_nDjl8kL2Y90aU,11396
|
|
44
44
|
sonolus/script/effect.py,sha256=V9bJvMzs1O4C1PjTOKgsAXov-l4AnDb2h38-DzmeWpI,5838
|
|
45
45
|
sonolus/script/engine.py,sha256=BhhQTrHuGAAAD6JPQ3R0jvHdimwW83PPghEIdAdtGMA,10683
|
|
46
46
|
sonolus/script/globals.py,sha256=gUvSbxXW1ZOsyQF6qb5iMRnW-eRrg07rWOK1PAYRwsE,9741
|
|
47
47
|
sonolus/script/instruction.py,sha256=PNfxC1dhT_hB0BxhDV3KXMn_kKxfI0t1iZmg8m6ddMU,6725
|
|
48
|
-
sonolus/script/interval.py,sha256=
|
|
48
|
+
sonolus/script/interval.py,sha256=9UmHjK0fzEu3Qt6cnRviy93WKcbRlI7UG8Ez-Vz-gps,11185
|
|
49
49
|
sonolus/script/iterator.py,sha256=wH6IM3ozLuMtCJZzhahIN3o2ykwUzDftU7ylbWo2UF8,4575
|
|
50
50
|
sonolus/script/level.py,sha256=wR23xk-NOcW_JMRb3R12sqIXCLSZL-7cM3y7IpMF1J0,6333
|
|
51
51
|
sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
|
|
@@ -55,22 +55,22 @@ sonolus/script/particle.py,sha256=oeVQF01xOeW2BEn04ZK1ZOP2HGvQzxBJCpITFjy9woQ,83
|
|
|
55
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=
|
|
59
|
-
sonolus/script/record.py,sha256=
|
|
60
|
-
sonolus/script/runtime.py,sha256=
|
|
58
|
+
sonolus/script/quad.py,sha256=wt0siM3SWPyQ2JEUP6sy_jVArZ62tLZjT-0ZOWPIcLw,11185
|
|
59
|
+
sonolus/script/record.py,sha256=o2GengeFlFU71WScql-75VgZCOKlq--IueHpZUBgo9s,12426
|
|
60
|
+
sonolus/script/runtime.py,sha256=XaS3op1BewSQchIffbJoz8WWw-prqk1PqBx2hdI3AMY,32971
|
|
61
61
|
sonolus/script/sprite.py,sha256=CMcRAZ2hejXnaBmY2_n1_rj6hGOgPP5zEW-BpyaEVOY,16256
|
|
62
|
-
sonolus/script/stream.py,sha256=
|
|
62
|
+
sonolus/script/stream.py,sha256=0Ikn5EUsFG301HORlF3n7tf5pq_nmQX-jBHycAC9pGM,24242
|
|
63
63
|
sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
|
|
64
64
|
sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
|
|
65
|
-
sonolus/script/transform.py,sha256=
|
|
65
|
+
sonolus/script/transform.py,sha256=BKAokYC1EmyPcdlwapd5P1fA90LwVfJYrkDNDc4NHJ0,20904
|
|
66
66
|
sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
|
|
67
67
|
sonolus/script/values.py,sha256=woSW3we5OZZ1IaO3Qr0OAs_yuIpAzGw_IjwELKQHzv4,1469
|
|
68
|
-
sonolus/script/vec.py,sha256=
|
|
68
|
+
sonolus/script/vec.py,sha256=zA5aN9J63ihbiyYXvASIli-REVoIqWUac-STN6rtZVg,7404
|
|
69
69
|
sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
|
|
70
|
-
sonolus/script/internal/builtin_impls.py,sha256=
|
|
70
|
+
sonolus/script/internal/builtin_impls.py,sha256=VDeBpnLwu6v_r53qiXjgA1NF1aaBm9GTxP1XoWGkSLM,8569
|
|
71
71
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
72
72
|
sonolus/script/internal/constant.py,sha256=k4kIBoy-c74UbfUR7GKzyTT6syhE-lSRJfw0nGuc8ZY,3904
|
|
73
|
-
sonolus/script/internal/context.py,sha256=
|
|
73
|
+
sonolus/script/internal/context.py,sha256=aJUXAsL9KlO7i5jVvuWsbHkNi8o_YevtoWyzaUdIYp4,15561
|
|
74
74
|
sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
|
|
75
75
|
sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
|
|
76
76
|
sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
|
|
@@ -80,13 +80,13 @@ sonolus/script/internal/introspection.py,sha256=SL2zaYjid0kkcj6ZbFLIwhgh7WKZBaAH
|
|
|
80
80
|
sonolus/script/internal/math_impls.py,sha256=Xk7tLMnV2npzPJWtHlspONQHt09Gh2YLdHhAjx4jkdE,2320
|
|
81
81
|
sonolus/script/internal/native.py,sha256=XKlNnWSJ-lxbwVGWhGj_CSSoWsVN18imqT5sAsDJT1w,1551
|
|
82
82
|
sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
|
|
83
|
-
sonolus/script/internal/range.py,sha256=
|
|
83
|
+
sonolus/script/internal/range.py,sha256=rX4ixpy6vPEfT4HIAiMlAGKad50HjqL0StTCjqKBu-o,3370
|
|
84
84
|
sonolus/script/internal/simulation_context.py,sha256=jUgNxCOEc_b99w1yFyVz0nVb6rgkEsyuCBvcYxq34Vk,4819
|
|
85
85
|
sonolus/script/internal/transient.py,sha256=d6iYhM9f6DPUX5nkYQGm-x0b9XEfZUmB4AtUNnyhixo,1636
|
|
86
|
-
sonolus/script/internal/tuple_impl.py,sha256=
|
|
86
|
+
sonolus/script/internal/tuple_impl.py,sha256=DPNdmmRmupU8Ah4_XKq6-PdT336l4nt15_uCJKQGkkk,3587
|
|
87
87
|
sonolus/script/internal/value.py,sha256=gQVNQD_xGpgrN4-UXFDmWRZCJCe8wPZ_wYv4QoPRJkM,5379
|
|
88
|
-
sonolus_py-0.3.
|
|
89
|
-
sonolus_py-0.3.
|
|
90
|
-
sonolus_py-0.3.
|
|
91
|
-
sonolus_py-0.3.
|
|
92
|
-
sonolus_py-0.3.
|
|
88
|
+
sonolus_py-0.3.3.dist-info/METADATA,sha256=xdAh3JPnlG9kHnswSNxBFsPBk34G8uycm0sPS2N56Gw,302
|
|
89
|
+
sonolus_py-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
90
|
+
sonolus_py-0.3.3.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
|
|
91
|
+
sonolus_py-0.3.3.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
|
|
92
|
+
sonolus_py-0.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|