sonolus.py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/__init__.py +0 -0
- sonolus/backend/__init__.py +0 -0
- sonolus/backend/allocate.py +51 -0
- sonolus/backend/blocks.py +756 -0
- sonolus/backend/excepthook.py +37 -0
- sonolus/backend/finalize.py +69 -0
- sonolus/backend/flow.py +92 -0
- sonolus/backend/interpret.py +333 -0
- sonolus/backend/ir.py +89 -0
- sonolus/backend/mode.py +24 -0
- sonolus/backend/node.py +40 -0
- sonolus/backend/ops.py +197 -0
- sonolus/backend/optimize.py +9 -0
- sonolus/backend/passes.py +6 -0
- sonolus/backend/place.py +90 -0
- sonolus/backend/simplify.py +30 -0
- sonolus/backend/utils.py +48 -0
- sonolus/backend/visitor.py +880 -0
- sonolus/build/__init__.py +0 -0
- sonolus/build/cli.py +170 -0
- sonolus/build/collection.py +293 -0
- sonolus/build/compile.py +90 -0
- sonolus/build/defaults.py +32 -0
- sonolus/build/engine.py +149 -0
- sonolus/build/level.py +23 -0
- sonolus/build/node.py +43 -0
- sonolus/build/project.py +94 -0
- sonolus/py.typed +0 -0
- sonolus/script/__init__.py +0 -0
- sonolus/script/archetype.py +651 -0
- sonolus/script/array.py +241 -0
- sonolus/script/bucket.py +192 -0
- sonolus/script/callbacks.py +105 -0
- sonolus/script/comptime.py +146 -0
- sonolus/script/containers.py +247 -0
- sonolus/script/debug.py +70 -0
- sonolus/script/effect.py +132 -0
- sonolus/script/engine.py +101 -0
- sonolus/script/globals.py +234 -0
- sonolus/script/graphics.py +141 -0
- sonolus/script/icon.py +73 -0
- sonolus/script/internal/__init__.py +5 -0
- sonolus/script/internal/builtin_impls.py +144 -0
- sonolus/script/internal/context.py +365 -0
- sonolus/script/internal/descriptor.py +17 -0
- sonolus/script/internal/error.py +15 -0
- sonolus/script/internal/generic.py +197 -0
- sonolus/script/internal/impl.py +69 -0
- sonolus/script/internal/introspection.py +14 -0
- sonolus/script/internal/native.py +38 -0
- sonolus/script/internal/value.py +144 -0
- sonolus/script/interval.py +98 -0
- sonolus/script/iterator.py +211 -0
- sonolus/script/level.py +52 -0
- sonolus/script/math.py +92 -0
- sonolus/script/num.py +382 -0
- sonolus/script/options.py +194 -0
- sonolus/script/particle.py +158 -0
- sonolus/script/pointer.py +30 -0
- sonolus/script/project.py +17 -0
- sonolus/script/range.py +58 -0
- sonolus/script/record.py +293 -0
- sonolus/script/runtime.py +526 -0
- sonolus/script/sprite.py +332 -0
- sonolus/script/text.py +404 -0
- sonolus/script/timing.py +42 -0
- sonolus/script/transform.py +118 -0
- sonolus/script/ui.py +160 -0
- sonolus/script/values.py +43 -0
- sonolus/script/vec.py +48 -0
- sonolus_py-0.1.0.dist-info/METADATA +10 -0
- sonolus_py-0.1.0.dist-info/RECORD +75 -0
- sonolus_py-0.1.0.dist-info/WHEEL +4 -0
- sonolus_py-0.1.0.dist-info/entry_points.txt +2 -0
- sonolus_py-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
|
|
6
|
+
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.script.internal.context import ctx
|
|
8
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
9
|
+
from sonolus.script.num import Num
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def native_call(op: Op, *args: Num) -> Num:
|
|
13
|
+
if not ctx():
|
|
14
|
+
raise RuntimeError("Unexpected native call")
|
|
15
|
+
args = tuple(validate_value(arg) for arg in args)
|
|
16
|
+
if not all(isinstance(arg, Num) for arg in args):
|
|
17
|
+
raise RuntimeError("All arguments must be of type Num")
|
|
18
|
+
result = ctx().alloc(size=1)
|
|
19
|
+
ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
|
|
20
|
+
return Num._from_place_(result)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
24
|
+
def decorator(fn: Callable[P, Num]) -> Callable[P, Num]:
|
|
25
|
+
signature = inspect.signature(fn)
|
|
26
|
+
|
|
27
|
+
@functools.wraps(fn)
|
|
28
|
+
@meta_fn
|
|
29
|
+
def wrapper(*args: Num) -> Num:
|
|
30
|
+
if len(args) != len(signature.parameters):
|
|
31
|
+
raise TypeError(f"Expected {len(signature.parameters)} arguments, got {len(args)}")
|
|
32
|
+
if ctx():
|
|
33
|
+
return native_call(op, *args)
|
|
34
|
+
return fn(*args)
|
|
35
|
+
|
|
36
|
+
return wrapper
|
|
37
|
+
|
|
38
|
+
return decorator
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from typing import Any, Self
|
|
4
|
+
|
|
5
|
+
from sonolus.backend.place import BlockPlace
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Value:
|
|
9
|
+
"""Base class for values."""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def _is_concrete_(cls) -> bool:
|
|
13
|
+
"""Returns whether this type is concrete (i.e. can be instantiated)."""
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def _size_(cls) -> int:
|
|
19
|
+
"""Returns the size of this value."""
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def _is_value_type_(cls) -> bool:
|
|
25
|
+
"""Returns whether this is a value type.
|
|
26
|
+
|
|
27
|
+
If this is true, the value behaves immutably and _set_ is supported.
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def _from_place_(cls, place: BlockPlace) -> Self:
|
|
34
|
+
"""Creates a value from a place."""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def _accepts_(cls, value: Any) -> bool:
|
|
40
|
+
"""Returns whether this value can accept the given value."""
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def _accept_(cls, value: Any) -> Self:
|
|
46
|
+
"""Accepts the given value."""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def _is_py_(self) -> bool:
|
|
51
|
+
"""Returns whether this value is a valid Python value."""
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def _as_py_(self) -> Any:
|
|
56
|
+
"""Returns the Python equivalent of this value.
|
|
57
|
+
|
|
58
|
+
Will fail if _is_py_ returns false.
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
|
|
65
|
+
"""Creates a value from a list of floats."""
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def _to_list_(self) -> list[float | BlockPlace]:
|
|
70
|
+
"""Converts this value to a list of floats."""
|
|
71
|
+
raise NotImplementedError
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def _flat_keys_(cls, prefix: str) -> list[str]:
|
|
76
|
+
"""Returns the keys to a flat representation of this value."""
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
def _to_flat_dict_(self, prefix: str) -> dict[str, float | BlockPlace]:
|
|
80
|
+
"""Converts this value to a flat dictionary."""
|
|
81
|
+
return dict(zip(self._flat_keys_(prefix), self._to_list_(), strict=False))
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def _get_(self) -> Self:
|
|
85
|
+
"""Implements access to the value.
|
|
86
|
+
|
|
87
|
+
This is used when accessing a value from a record or array (and when storing into a record).
|
|
88
|
+
For value (immutable) types, this makes a copy to preserve the appearance of immutability.
|
|
89
|
+
For mutable types, this returns self.
|
|
90
|
+
|
|
91
|
+
For instance:
|
|
92
|
+
```
|
|
93
|
+
class X(Record):
|
|
94
|
+
v: Num
|
|
95
|
+
|
|
96
|
+
a = 1
|
|
97
|
+
b = X(a) # (1) _get() is called on a
|
|
98
|
+
c = b.v # (2) _get() is called on the value for v
|
|
99
|
+
|
|
100
|
+
# (1) prevents this from changing the value of a
|
|
101
|
+
# (2) prevents this from changing the value of c
|
|
102
|
+
# Thus, both calls to _get() are necessary to ensure values behave immutably.
|
|
103
|
+
b.v = 2
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
raise NotImplementedError
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def _set_(self, value: Self):
|
|
110
|
+
"""Implements assignment (=).
|
|
111
|
+
|
|
112
|
+
This is only supported by value types.
|
|
113
|
+
This method must not change the active context like by branching.
|
|
114
|
+
|
|
115
|
+
In some places, = might instead call assign_, such as when setting the value of a record field
|
|
116
|
+
with a reference type.
|
|
117
|
+
"""
|
|
118
|
+
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def _copy_from_(self, value: Self):
|
|
122
|
+
"""Implements copy assignment (@=).
|
|
123
|
+
|
|
124
|
+
This is only supported by mutable reference types.
|
|
125
|
+
"""
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def _copy_(self) -> Self:
|
|
130
|
+
"""Returns a deep copy of this value."""
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def _alloc_(cls) -> Self:
|
|
136
|
+
"""Allocates a new value which may be uninitialized."""
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
|
|
139
|
+
def __imatmul__(self, other):
|
|
140
|
+
self._copy_from_(other)
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
Value.__imatmul__._meta_fn_ = True
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from sonolus.backend.ops import Op
|
|
4
|
+
from sonolus.script.debug import error
|
|
5
|
+
from sonolus.script.internal.native import native_function
|
|
6
|
+
from sonolus.script.record import Record
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Interval(Record):
|
|
10
|
+
"""A closed interval."""
|
|
11
|
+
|
|
12
|
+
start: float
|
|
13
|
+
end: float
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def length(self) -> float:
|
|
17
|
+
return self.end - self.start
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def is_empty(self) -> bool:
|
|
21
|
+
return self.start > self.end
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def mid(self) -> float:
|
|
25
|
+
return (self.start + self.end) / 2
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def tuple(self):
|
|
29
|
+
return self.start, self.end
|
|
30
|
+
|
|
31
|
+
def __contains__(self, item: Self | float | int) -> bool:
|
|
32
|
+
match item:
|
|
33
|
+
case Interval(start, end):
|
|
34
|
+
return self.start <= start and end <= self.end
|
|
35
|
+
case float() | int() as value:
|
|
36
|
+
return self.start <= value <= self.end
|
|
37
|
+
case _:
|
|
38
|
+
error("Invalid type for interval check")
|
|
39
|
+
|
|
40
|
+
def __add__(self, other: float | int) -> Self:
|
|
41
|
+
return Interval(self.start + other, self.end + other)
|
|
42
|
+
|
|
43
|
+
def __sub__(self, other: float | int) -> Self:
|
|
44
|
+
return Interval(self.start - other, self.end - other)
|
|
45
|
+
|
|
46
|
+
def __mul__(self, other: float | int) -> Self:
|
|
47
|
+
return Interval(self.start * other, self.end * other)
|
|
48
|
+
|
|
49
|
+
def __truediv__(self, other: float | int) -> Self:
|
|
50
|
+
return Interval(self.start / other, self.end / other)
|
|
51
|
+
|
|
52
|
+
def __floordiv__(self, other: float | int) -> Self:
|
|
53
|
+
return Interval(self.start // other, self.end // other)
|
|
54
|
+
|
|
55
|
+
def __and__(self, other: Self) -> Self:
|
|
56
|
+
return Interval(max(self.start, other.start), min(self.end, other.end))
|
|
57
|
+
|
|
58
|
+
def lerp(self, x: float, /) -> float:
|
|
59
|
+
return lerp(self.start, self.end, x)
|
|
60
|
+
|
|
61
|
+
def lerp_clamped(self, x: float, /) -> float:
|
|
62
|
+
return lerp_clamped(self.start, self.end, x)
|
|
63
|
+
|
|
64
|
+
def unlerp(self, x: float, /) -> float:
|
|
65
|
+
return unlerp(self.start, self.end, x)
|
|
66
|
+
|
|
67
|
+
def unlerp_clamped(self, x: float, /) -> float:
|
|
68
|
+
return unlerp_clamped(self.start, self.end, x)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@native_function(Op.Lerp)
|
|
72
|
+
def lerp(a, b, x, /):
|
|
73
|
+
return a + (b - a) * x
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@native_function(Op.LerpClamped)
|
|
77
|
+
def lerp_clamped(a, b, x, /):
|
|
78
|
+
return a + (b - a) * max(0, min(1, x))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@native_function(Op.Unlerp)
|
|
82
|
+
def unlerp(a, b, x, /):
|
|
83
|
+
return (x - a) / (b - a)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@native_function(Op.UnlerpClamped)
|
|
87
|
+
def unlerp_clamped(a, b, x, /):
|
|
88
|
+
return max(0, min(1, (x - a) / (b - a)))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@native_function(Op.Remap)
|
|
92
|
+
def remap(a, b, c, d, x, /):
|
|
93
|
+
return c + (d - c) * (x - a) / (b - a)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@native_function(Op.RemapClamped)
|
|
97
|
+
def remap_clamped(a, b, c, d, x, /):
|
|
98
|
+
return c + (d - c) * max(0, min(1, (x - a) / (b - a)))
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Collection, Iterator
|
|
5
|
+
|
|
6
|
+
from sonolus.script.num import Num
|
|
7
|
+
from sonolus.script.record import Record
|
|
8
|
+
from sonolus.script.values import copy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SonolusIterator[T](Iterator[T]):
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def has_next(self) -> bool:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def next(self) -> T:
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def __next__(self) -> T:
|
|
21
|
+
if not self.has_next():
|
|
22
|
+
raise StopIteration
|
|
23
|
+
return self.next()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ArrayLike[T](Collection):
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def size(self) -> int:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def __getitem__(self, index: Num) -> T:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def __setitem__(self, index: Num, value: T):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def __len__(self) -> int:
|
|
40
|
+
return self.size()
|
|
41
|
+
|
|
42
|
+
def __iter__(self) -> SonolusIterator[T]:
|
|
43
|
+
return ArrayIterator(0, self)
|
|
44
|
+
|
|
45
|
+
def __contains__(self, value: T) -> bool:
|
|
46
|
+
i = 0
|
|
47
|
+
while i < self.size():
|
|
48
|
+
if self[i] == value:
|
|
49
|
+
return True
|
|
50
|
+
i += 1
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def reversed(self) -> ArrayLike[T]:
|
|
54
|
+
return ArrayReverser(self)
|
|
55
|
+
|
|
56
|
+
def iter(self) -> SonolusIterator[T]:
|
|
57
|
+
return self.__iter__() # noqa: PLC2801
|
|
58
|
+
|
|
59
|
+
def enumerate(self, start: Num = 0) -> SonolusIterator[T]:
|
|
60
|
+
return ArrayEnumerator(0, start, self)
|
|
61
|
+
|
|
62
|
+
def index_of(self, value: T, start: Num = 0) -> Num:
|
|
63
|
+
i = start
|
|
64
|
+
while i < self.size():
|
|
65
|
+
if self[i] == value:
|
|
66
|
+
return i
|
|
67
|
+
i += 1
|
|
68
|
+
return -1
|
|
69
|
+
|
|
70
|
+
def last_index_of(self, value: T) -> Num:
|
|
71
|
+
i = self.size() - 1
|
|
72
|
+
while i >= 0:
|
|
73
|
+
if self[i] == value:
|
|
74
|
+
return i
|
|
75
|
+
i -= 1
|
|
76
|
+
return -1
|
|
77
|
+
|
|
78
|
+
def index_of_max(self) -> Num:
|
|
79
|
+
if self.size() == 0:
|
|
80
|
+
return -1
|
|
81
|
+
max_index = 0
|
|
82
|
+
i = 1
|
|
83
|
+
while i < self.size():
|
|
84
|
+
if self[i] > self[max_index]:
|
|
85
|
+
max_index = i
|
|
86
|
+
i += 1
|
|
87
|
+
return max_index
|
|
88
|
+
|
|
89
|
+
def index_of_min(self) -> Num:
|
|
90
|
+
if self.size() == 0:
|
|
91
|
+
return -1
|
|
92
|
+
min_index = 0
|
|
93
|
+
i = 1
|
|
94
|
+
while i < self.size():
|
|
95
|
+
if self[i] < self[min_index]:
|
|
96
|
+
min_index = i
|
|
97
|
+
i += 1
|
|
98
|
+
return min_index
|
|
99
|
+
|
|
100
|
+
def max(self) -> T:
|
|
101
|
+
return self[self.index_of_max()]
|
|
102
|
+
|
|
103
|
+
def min(self) -> T:
|
|
104
|
+
return self[self.index_of_min()]
|
|
105
|
+
|
|
106
|
+
def swap(self, i: Num, j: Num):
|
|
107
|
+
temp = copy(self[i])
|
|
108
|
+
self[i] = self[j]
|
|
109
|
+
self[j] = temp
|
|
110
|
+
|
|
111
|
+
def sort(self, *, reverse: bool = False):
|
|
112
|
+
if self.size() < 15:
|
|
113
|
+
_insertion_sort(self, 0, self.size(), reverse)
|
|
114
|
+
else:
|
|
115
|
+
_heap_sort(self, 0, self.size(), reverse)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
|
|
119
|
+
i = start + 1
|
|
120
|
+
while i < end:
|
|
121
|
+
value = copy(array[i])
|
|
122
|
+
j = i - 1
|
|
123
|
+
while j >= start and (array[j] > value) != reverse:
|
|
124
|
+
array[j + 1] = array[j]
|
|
125
|
+
j -= 1
|
|
126
|
+
array[j + 1] = value
|
|
127
|
+
i += 1
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
|
|
131
|
+
while True:
|
|
132
|
+
left = index * 2 + 1
|
|
133
|
+
right = left + 1
|
|
134
|
+
largest = index
|
|
135
|
+
if left < end and (array[left] > array[largest]) != reverse:
|
|
136
|
+
largest = left
|
|
137
|
+
if right < end and (array[right] > array[largest]) != reverse:
|
|
138
|
+
largest = right
|
|
139
|
+
if largest == index:
|
|
140
|
+
break
|
|
141
|
+
array.swap(index, largest)
|
|
142
|
+
index = largest
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Heap sort is simple to implement iteratively without dynamic memory allocation
|
|
146
|
+
def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
|
|
147
|
+
i = end // 2 - 1
|
|
148
|
+
while i >= start:
|
|
149
|
+
_heapify(array, end, i, reverse)
|
|
150
|
+
i -= 1
|
|
151
|
+
i = end - 1
|
|
152
|
+
while i > start:
|
|
153
|
+
array.swap(start, i)
|
|
154
|
+
_heapify(array, i, start, reverse)
|
|
155
|
+
i -= 1
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ArrayIterator[V: ArrayLike](Record, SonolusIterator):
|
|
159
|
+
i: int
|
|
160
|
+
array: V
|
|
161
|
+
|
|
162
|
+
def has_next(self) -> bool:
|
|
163
|
+
return self.i < self.array.size()
|
|
164
|
+
|
|
165
|
+
def next(self) -> V:
|
|
166
|
+
value = self.array[self.i]
|
|
167
|
+
self.i += 1
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ArrayReverser[V: ArrayLike](Record, ArrayLike):
|
|
172
|
+
array: V
|
|
173
|
+
|
|
174
|
+
def size(self) -> int:
|
|
175
|
+
return self.array.size()
|
|
176
|
+
|
|
177
|
+
def __getitem__(self, index: Num) -> V:
|
|
178
|
+
return self.array[self.size() - 1 - index]
|
|
179
|
+
|
|
180
|
+
def __setitem__(self, index: Num, value: V):
|
|
181
|
+
self.array[self.size() - 1 - index] = value
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Enumerator[V: SonolusIterator](Record, SonolusIterator):
|
|
185
|
+
i: int
|
|
186
|
+
offset: int
|
|
187
|
+
iterator: V
|
|
188
|
+
|
|
189
|
+
def has_next(self) -> bool:
|
|
190
|
+
return self.iterator.has_next()
|
|
191
|
+
|
|
192
|
+
def next(self):
|
|
193
|
+
value = self.iterator.next()
|
|
194
|
+
index = self.i + self.offset
|
|
195
|
+
self.i += 1
|
|
196
|
+
return index, value
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
|
|
200
|
+
i: int
|
|
201
|
+
offset: int
|
|
202
|
+
array: V
|
|
203
|
+
|
|
204
|
+
def has_next(self) -> bool:
|
|
205
|
+
return self.i < self.array.size()
|
|
206
|
+
|
|
207
|
+
def next(self):
|
|
208
|
+
value = self.array[self.i]
|
|
209
|
+
index = self.i + self.offset
|
|
210
|
+
self.i += 1
|
|
211
|
+
return index, value
|
sonolus/script/level.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sonolus.build.collection import Asset
|
|
4
|
+
from sonolus.script.archetype import PlayArchetype, StandardImport
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Level:
|
|
8
|
+
version = 1
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
*,
|
|
13
|
+
name: str,
|
|
14
|
+
title: str | None = None,
|
|
15
|
+
rating: int = 0,
|
|
16
|
+
artists: str = "Unknown",
|
|
17
|
+
author: str = "Unknown",
|
|
18
|
+
cover: Asset | None = None,
|
|
19
|
+
bgm: Asset | None = None,
|
|
20
|
+
data: LevelData,
|
|
21
|
+
) -> None:
|
|
22
|
+
self.name = name
|
|
23
|
+
self.title = title or name
|
|
24
|
+
self.rating = rating
|
|
25
|
+
self.artists = artists
|
|
26
|
+
self.author = author
|
|
27
|
+
self.cover = cover
|
|
28
|
+
self.bgm = bgm
|
|
29
|
+
self.data = data
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LevelData:
|
|
33
|
+
bgm_offset: float
|
|
34
|
+
entities: list[PlayArchetype]
|
|
35
|
+
|
|
36
|
+
def __init__(self, bgm_offset: float, entities: list[PlayArchetype]) -> None:
|
|
37
|
+
self.bgm_offset = bgm_offset
|
|
38
|
+
self.entities = entities
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BpmChange(PlayArchetype):
|
|
42
|
+
name = "#BPM_CHANGE"
|
|
43
|
+
|
|
44
|
+
beat: StandardImport.Beat
|
|
45
|
+
bpm: StandardImport.Bpm
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TimescaleChange(PlayArchetype):
|
|
49
|
+
name = "#TIMESCALE_CHANGE"
|
|
50
|
+
|
|
51
|
+
beat: StandardImport.Beat
|
|
52
|
+
timescale: StandardImport.Timescale
|
sonolus/script/math.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
from sonolus.backend.ops import Op
|
|
4
|
+
from sonolus.script.internal.native import native_function
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@native_function(Op.Sin)
|
|
8
|
+
def sin(x: float) -> float:
|
|
9
|
+
return math.sin(x)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@native_function(Op.Cos)
|
|
13
|
+
def cos(x: float) -> float:
|
|
14
|
+
return math.cos(x)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@native_function(Op.Tan)
|
|
18
|
+
def tan(x: float) -> float:
|
|
19
|
+
return math.tan(x)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@native_function(Op.Arcsin)
|
|
23
|
+
def asin(x: float) -> float:
|
|
24
|
+
return math.asin(x)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@native_function(Op.Arccos)
|
|
28
|
+
def acos(x: float) -> float:
|
|
29
|
+
return math.acos(x)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@native_function(Op.Arctan)
|
|
33
|
+
def atan(x: float) -> float:
|
|
34
|
+
return math.atan(x)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@native_function(Op.Arctan2)
|
|
38
|
+
def atan2(y: float, x: float) -> float:
|
|
39
|
+
return math.atan2(y, x)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@native_function(Op.Sinh)
|
|
43
|
+
def sinh(x: float) -> float:
|
|
44
|
+
return math.sinh(x)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@native_function(Op.Cosh)
|
|
48
|
+
def cosh(x: float) -> float:
|
|
49
|
+
return math.cosh(x)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@native_function(Op.Tanh)
|
|
53
|
+
def tanh(x: float) -> float:
|
|
54
|
+
return math.tanh(x)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@native_function(Op.Floor)
|
|
58
|
+
def floor(x: float) -> float:
|
|
59
|
+
return math.floor(x)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@native_function(Op.Ceil)
|
|
63
|
+
def ceil(x: float) -> float:
|
|
64
|
+
return math.ceil(x)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@native_function(Op.Trunc)
|
|
68
|
+
def trunc(x: float) -> float:
|
|
69
|
+
return math.trunc(x)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@native_function(Op.Round)
|
|
73
|
+
def _round(x: float) -> float:
|
|
74
|
+
return round(x)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
MATH_BUILTIN_IMPLS = {
|
|
78
|
+
id(math.sin): sin,
|
|
79
|
+
id(math.cos): cos,
|
|
80
|
+
id(math.tan): tan,
|
|
81
|
+
id(math.asin): asin,
|
|
82
|
+
id(math.acos): acos,
|
|
83
|
+
id(math.atan): atan,
|
|
84
|
+
id(math.atan2): atan2,
|
|
85
|
+
id(math.sinh): sinh,
|
|
86
|
+
id(math.cosh): cosh,
|
|
87
|
+
id(math.tanh): tanh,
|
|
88
|
+
id(math.floor): floor,
|
|
89
|
+
id(math.ceil): ceil,
|
|
90
|
+
id(math.trunc): trunc,
|
|
91
|
+
id(round): _round,
|
|
92
|
+
}
|