sonolus.py 0.4.0__py3-none-any.whl → 0.4.2__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/utils.py +35 -40
- sonolus/backend/visitor.py +40 -6
- sonolus/build/cli.py +3 -3
- sonolus/build/compile.py +16 -1
- sonolus/script/archetype.py +21 -12
- sonolus/script/array.py +1 -1
- sonolus/script/containers.py +2 -2
- sonolus/script/easing.py +0 -1
- sonolus/script/effect.py +4 -2
- sonolus/script/globals.py +5 -2
- sonolus/script/internal/context.py +7 -1
- sonolus/script/internal/native.py +1 -1
- sonolus/script/iterator.py +18 -1
- sonolus/script/level.py +2 -2
- sonolus/script/maybe.py +114 -9
- sonolus/script/num.py +12 -12
- sonolus/script/project.py +1 -1
- sonolus/script/record.py +36 -36
- sonolus/script/sprite.py +1 -1
- sonolus/script/stream.py +6 -5
- {sonolus_py-0.4.0.dist-info → sonolus_py-0.4.2.dist-info}/METADATA +1 -1
- {sonolus_py-0.4.0.dist-info → sonolus_py-0.4.2.dist-info}/RECORD +25 -25
- {sonolus_py-0.4.0.dist-info → sonolus_py-0.4.2.dist-info}/WHEEL +0 -0
- {sonolus_py-0.4.0.dist-info → sonolus_py-0.4.2.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.4.0.dist-info → sonolus_py-0.4.2.dist-info}/licenses/LICENSE +0 -0
sonolus/backend/utils.py
CHANGED
|
@@ -23,29 +23,48 @@ def get_tree_from_file(file: str | Path) -> ast.Module:
|
|
|
23
23
|
class FindFunction(ast.NodeVisitor):
|
|
24
24
|
def __init__(self, line):
|
|
25
25
|
self.line = line
|
|
26
|
-
self.
|
|
26
|
+
self.results: list[ast.FunctionDef | ast.Lambda] = []
|
|
27
|
+
self.current_fn = None
|
|
27
28
|
|
|
28
29
|
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self.generic_visit(node)
|
|
30
|
+
self.results.append(node)
|
|
31
|
+
outer_fn = self.current_fn
|
|
32
|
+
self.current_fn = node
|
|
33
|
+
self.generic_visit(node)
|
|
34
|
+
self.current_fn = outer_fn
|
|
35
35
|
|
|
36
36
|
def visit_Lambda(self, node: ast.Lambda):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self.generic_visit(node)
|
|
37
|
+
self.results.append(node)
|
|
38
|
+
outer_fn = self.current_fn
|
|
39
|
+
self.current_fn = node
|
|
40
|
+
self.generic_visit(node)
|
|
41
|
+
self.current_fn = outer_fn
|
|
43
42
|
|
|
43
|
+
# Visitors have high overhead, so we detect generators here rather than in a separate pass.
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
|
|
45
|
+
def visit_Yield(self, node):
|
|
46
|
+
self.current_fn.has_yield = True
|
|
47
|
+
|
|
48
|
+
def visit_YieldFrom(self, node):
|
|
49
|
+
self.current_fn.has_yield = True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cache
|
|
53
|
+
def get_functions(tree: ast.Module) -> list[ast.FunctionDef | ast.Lambda]:
|
|
54
|
+
visitor = FindFunction(0)
|
|
47
55
|
visitor.visit(tree)
|
|
48
|
-
return visitor.
|
|
56
|
+
return visitor.results
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def find_function(tree: ast.Module, line: int):
|
|
60
|
+
for node in get_functions(tree):
|
|
61
|
+
if node.lineno == line or (
|
|
62
|
+
isinstance(node, ast.FunctionDef)
|
|
63
|
+
and node.decorator_list
|
|
64
|
+
and (node.decorator_list[-1].end_lineno <= line <= node.lineno)
|
|
65
|
+
):
|
|
66
|
+
return node
|
|
67
|
+
raise ValueError("Function not found")
|
|
49
68
|
|
|
50
69
|
|
|
51
70
|
class ScanWrites(ast.NodeVisitor):
|
|
@@ -61,27 +80,3 @@ def scan_writes(node: ast.AST) -> set[str]:
|
|
|
61
80
|
visitor = ScanWrites()
|
|
62
81
|
visitor.visit(node)
|
|
63
82
|
return set(visitor.writes)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class HasDirectYield(ast.NodeVisitor):
|
|
67
|
-
def __init__(self):
|
|
68
|
-
self.started = False
|
|
69
|
-
self.has_yield = False
|
|
70
|
-
|
|
71
|
-
def visit_Yield(self, node: ast.Yield):
|
|
72
|
-
self.has_yield = True
|
|
73
|
-
|
|
74
|
-
def visit_YieldFrom(self, node: ast.YieldFrom):
|
|
75
|
-
self.has_yield = True
|
|
76
|
-
|
|
77
|
-
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
78
|
-
if self.started:
|
|
79
|
-
return
|
|
80
|
-
self.started = True
|
|
81
|
-
self.generic_visit(node)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def has_yield(node: ast.AST) -> bool:
|
|
85
|
-
visitor = HasDirectYield()
|
|
86
|
-
visitor.visit(node)
|
|
87
|
-
return visitor.has_yield
|
sonolus/backend/visitor.py
CHANGED
|
@@ -11,7 +11,7 @@ from types import FunctionType, MethodType, MethodWrapperType
|
|
|
11
11
|
from typing import Any, Never
|
|
12
12
|
|
|
13
13
|
from sonolus.backend.excepthook import install_excepthook
|
|
14
|
-
from sonolus.backend.utils import get_function,
|
|
14
|
+
from sonolus.backend.utils import get_function, scan_writes
|
|
15
15
|
from sonolus.script.debug import assert_true
|
|
16
16
|
from sonolus.script.internal.builtin_impls import BUILTIN_IMPLS, _bool, _float, _int, _len
|
|
17
17
|
from sonolus.script.internal.constant import ConstantValue
|
|
@@ -39,6 +39,8 @@ def compile_and_call[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.k
|
|
|
39
39
|
def compile_and_call_at_definition[**P, R](fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
40
40
|
if not ctx():
|
|
41
41
|
return fn(*args, **kwargs)
|
|
42
|
+
if ctx().no_eval:
|
|
43
|
+
return compile_and_call(fn, *args, **kwargs)
|
|
42
44
|
source_file, node = get_function(fn)
|
|
43
45
|
location_args = {
|
|
44
46
|
"lineno": node.lineno,
|
|
@@ -261,11 +263,26 @@ class Visitor(ast.NodeVisitor):
|
|
|
261
263
|
result = self.visit(body)
|
|
262
264
|
ctx().scope.set_value("$return", result)
|
|
263
265
|
case ast.GeneratorExp(elt=elt, generators=generators):
|
|
264
|
-
|
|
266
|
+
first_generator = generators[0]
|
|
267
|
+
iterable = self.visit(first_generator.iter)
|
|
268
|
+
if isinstance(iterable, TupleImpl):
|
|
269
|
+
initial_iterator = iterable
|
|
270
|
+
else:
|
|
271
|
+
if not hasattr(iterable, "__iter__"):
|
|
272
|
+
raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
|
|
273
|
+
initial_iterator = self.handle_call(first_generator.iter, iterable.__iter__)
|
|
274
|
+
if not isinstance(initial_iterator, SonolusIterator):
|
|
275
|
+
raise ValueError("Unsupported iterator")
|
|
276
|
+
# The initial iterator is evaluated eagerly in Python
|
|
277
|
+
before_ctx = ctx().branch_with_scope(None, before_ctx.scope.copy())
|
|
278
|
+
start_ctx = before_ctx.branch_with_scope(None, Scope())
|
|
279
|
+
set_ctx(start_ctx)
|
|
280
|
+
self.construct_genexpr(generators, elt, initial_iterator)
|
|
265
281
|
ctx().scope.set_value("$return", validate_value(None))
|
|
266
282
|
case _:
|
|
267
283
|
raise NotImplementedError("Unsupported syntax")
|
|
268
|
-
|
|
284
|
+
# has_yield is set by the find_function visitor
|
|
285
|
+
if getattr(node, "has_yield", False) or isinstance(node, ast.GeneratorExp):
|
|
269
286
|
return_ctx = Context.meet([*self.return_ctxs, ctx()])
|
|
270
287
|
result_binding = return_ctx.scope.get_binding("$return")
|
|
271
288
|
if not isinstance(result_binding, ValueBinding):
|
|
@@ -323,7 +340,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
323
340
|
set_ctx(after_ctx.branch_with_scope(None, before_ctx.scope.copy()))
|
|
324
341
|
return result_binding.value
|
|
325
342
|
|
|
326
|
-
def construct_genexpr(
|
|
343
|
+
def construct_genexpr(
|
|
344
|
+
self, generators: Iterable[ast.comprehension], elt: ast.expr, initial_iterator: Value | None = None
|
|
345
|
+
):
|
|
327
346
|
if not generators:
|
|
328
347
|
# Note that there may effectively be multiple yields in an expression since
|
|
329
348
|
# tuples are unrolled.
|
|
@@ -335,14 +354,22 @@ class Visitor(ast.NodeVisitor):
|
|
|
335
354
|
set_ctx(resume_ctx)
|
|
336
355
|
return
|
|
337
356
|
generator, *others = generators
|
|
338
|
-
|
|
357
|
+
if initial_iterator is not None:
|
|
358
|
+
iterable = initial_iterator
|
|
359
|
+
else:
|
|
360
|
+
iterable = self.visit(generator.iter)
|
|
339
361
|
if isinstance(iterable, TupleImpl):
|
|
340
362
|
for value in iterable.value:
|
|
341
363
|
set_ctx(ctx().branch(None))
|
|
342
364
|
self.handle_assign(generator.target, validate_value(value))
|
|
343
365
|
self.construct_genexpr(others, elt)
|
|
344
366
|
else:
|
|
345
|
-
|
|
367
|
+
if initial_iterator is not None:
|
|
368
|
+
iterator = initial_iterator
|
|
369
|
+
else:
|
|
370
|
+
if not hasattr(iterable, "__iter__"):
|
|
371
|
+
raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
|
|
372
|
+
iterator = self.handle_call(generator.iter, iterable.__iter__)
|
|
346
373
|
if not isinstance(iterator, SonolusIterator):
|
|
347
374
|
raise ValueError("Unsupported iterator")
|
|
348
375
|
header_ctx = ctx().branch(None)
|
|
@@ -493,6 +520,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
493
520
|
if break_ctxs:
|
|
494
521
|
set_ctx(Context.meet([*break_ctxs, ctx()]))
|
|
495
522
|
return
|
|
523
|
+
if not hasattr(iterable, "__iter__"):
|
|
524
|
+
raise TypeError(f"Object of type '{type(iterable).__name__}' is not iterable")
|
|
496
525
|
iterator = self.handle_call(node, iterable.__iter__)
|
|
497
526
|
if not isinstance(iterator, SonolusIterator):
|
|
498
527
|
raise ValueError("Unsupported iterator")
|
|
@@ -980,6 +1009,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
980
1009
|
self.resume_ctxs.append(resume_ctx)
|
|
981
1010
|
set_ctx(resume_ctx)
|
|
982
1011
|
return validate_value(None)
|
|
1012
|
+
if not hasattr(value, "__iter__"):
|
|
1013
|
+
raise TypeError(f"Object of type '{type(value).__name__}' is not iterable")
|
|
983
1014
|
iterator = self.handle_call(node, value.__iter__)
|
|
984
1015
|
if not isinstance(iterator, SonolusIterator):
|
|
985
1016
|
raise ValueError("Expected a SonolusIterator")
|
|
@@ -1395,6 +1426,9 @@ class Visitor(ast.NodeVisitor):
|
|
|
1395
1426
|
self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
|
|
1396
1427
|
) -> R:
|
|
1397
1428
|
"""Executes the given function at the given node for a better traceback."""
|
|
1429
|
+
if ctx().no_eval:
|
|
1430
|
+
return fn(*args, **kwargs)
|
|
1431
|
+
|
|
1398
1432
|
location_args = {
|
|
1399
1433
|
"lineno": node.lineno,
|
|
1400
1434
|
"col_offset": node.col_offset,
|
sonolus/build/cli.py
CHANGED
|
@@ -166,13 +166,13 @@ def main():
|
|
|
166
166
|
def add_common_arguments(parser):
|
|
167
167
|
optimization_group = parser.add_mutually_exclusive_group()
|
|
168
168
|
optimization_group.add_argument(
|
|
169
|
-
"-
|
|
169
|
+
"-O0", "--optimize-minimal", action="store_true", help="Use minimal optimization passes"
|
|
170
170
|
)
|
|
171
171
|
optimization_group.add_argument(
|
|
172
|
-
"-
|
|
172
|
+
"-O1", "--optimize-fast", action="store_true", help="Use fast optimization passes"
|
|
173
173
|
)
|
|
174
174
|
optimization_group.add_argument(
|
|
175
|
-
"-
|
|
175
|
+
"-O2", "--optimize-standard", action="store_true", help="Use standard optimization passes"
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
build_components = parser.add_argument_group("build components")
|
sonolus/build/compile.py
CHANGED
|
@@ -21,6 +21,7 @@ from sonolus.script.internal.context import (
|
|
|
21
21
|
ctx,
|
|
22
22
|
using_ctx,
|
|
23
23
|
)
|
|
24
|
+
from sonolus.script.internal.error import CompilationError
|
|
24
25
|
from sonolus.script.num import _is_num
|
|
25
26
|
|
|
26
27
|
|
|
@@ -140,7 +141,21 @@ def callback_to_cfg(
|
|
|
140
141
|
name: str,
|
|
141
142
|
archetype: type[_BaseArchetype] | None = None,
|
|
142
143
|
) -> BasicBlock:
|
|
143
|
-
|
|
144
|
+
try:
|
|
145
|
+
# Default to no_eval=True for performance unless there's an error.
|
|
146
|
+
return _callback_to_cfg(global_state, callback, name, archetype, no_eval=True)
|
|
147
|
+
except CompilationError:
|
|
148
|
+
return _callback_to_cfg(global_state, callback, name, archetype, no_eval=False)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _callback_to_cfg(
|
|
152
|
+
global_state: GlobalContextState,
|
|
153
|
+
callback: Callable,
|
|
154
|
+
name: str,
|
|
155
|
+
archetype: type[_BaseArchetype] | None,
|
|
156
|
+
no_eval: bool,
|
|
157
|
+
) -> BasicBlock:
|
|
158
|
+
callback_state = CallbackContextState(name, no_eval=no_eval)
|
|
144
159
|
context = Context(global_state, callback_state)
|
|
145
160
|
with using_ctx(context):
|
|
146
161
|
if archetype is not None:
|
sonolus/script/archetype.py
CHANGED
|
@@ -190,7 +190,8 @@ def imported(*, name: str | None = None) -> Any:
|
|
|
190
190
|
|
|
191
191
|
In watch mode, data may also be loaded from a corresponding exported field in play mode.
|
|
192
192
|
|
|
193
|
-
Imported fields may only be updated in the `preprocess`
|
|
193
|
+
Imported fields may only be updated in the [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess]
|
|
194
|
+
callback, and are read-only in other callbacks.
|
|
194
195
|
|
|
195
196
|
Usage:
|
|
196
197
|
```python
|
|
@@ -205,10 +206,12 @@ def imported(*, name: str | None = None) -> Any:
|
|
|
205
206
|
def entity_data() -> Any:
|
|
206
207
|
"""Declare a field as entity data.
|
|
207
208
|
|
|
208
|
-
Entity data is accessible from other entities, but may only be updated in the
|
|
209
|
+
Entity data is accessible from other entities, but may only be updated in the
|
|
210
|
+
[`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess] callback
|
|
209
211
|
and is read-only in other callbacks.
|
|
210
212
|
|
|
211
|
-
It functions like `imported` and shares the same underlying storage,
|
|
213
|
+
It functions like [`imported`][sonolus.script.archetype.imported] and shares the same underlying storage,
|
|
214
|
+
except that it is not loaded from a level.
|
|
212
215
|
|
|
213
216
|
Usage:
|
|
214
217
|
```python
|
|
@@ -242,7 +245,8 @@ def entity_memory() -> Any:
|
|
|
242
245
|
Entity memory is private to the entity and is not accessible from other entities. It may be read or updated in any
|
|
243
246
|
callback associated with the entity.
|
|
244
247
|
|
|
245
|
-
Entity memory fields may also be set when an entity is spawned using the
|
|
248
|
+
Entity memory fields may also be set when an entity is spawned using the
|
|
249
|
+
[`spawn()`][sonolus.script.archetype.PlayArchetype.spawn] method.
|
|
246
250
|
|
|
247
251
|
Usage:
|
|
248
252
|
```python
|
|
@@ -260,7 +264,9 @@ def shared_memory() -> Any:
|
|
|
260
264
|
Shared memory is accessible from other entities.
|
|
261
265
|
|
|
262
266
|
Shared memory may be read in any callback, but may only be updated by sequential callbacks
|
|
263
|
-
(`preprocess
|
|
267
|
+
([`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess],
|
|
268
|
+
[`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential],
|
|
269
|
+
and [`touch`][sonolus.script.archetype.PlayArchetype.touch]).
|
|
264
270
|
|
|
265
271
|
Usage:
|
|
266
272
|
```python
|
|
@@ -688,7 +694,7 @@ class PlayArchetype(_BaseArchetype):
|
|
|
688
694
|
def spawn_order(self) -> float:
|
|
689
695
|
"""Return the spawn order of the entity.
|
|
690
696
|
|
|
691
|
-
Runs when the level is loaded after `preprocess
|
|
697
|
+
Runs when the level is loaded after [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess].
|
|
692
698
|
"""
|
|
693
699
|
|
|
694
700
|
def should_spawn(self) -> bool:
|
|
@@ -709,13 +715,15 @@ class PlayArchetype(_BaseArchetype):
|
|
|
709
715
|
Runs first each frame.
|
|
710
716
|
|
|
711
717
|
This is where logic affecting shared memory should be placed.
|
|
712
|
-
Other logic should be placed in
|
|
718
|
+
Other logic should typically be placed in
|
|
719
|
+
[`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel]
|
|
720
|
+
for better performance.
|
|
713
721
|
"""
|
|
714
722
|
|
|
715
723
|
def update_parallel(self):
|
|
716
724
|
"""Perform parallel actions for this frame.
|
|
717
725
|
|
|
718
|
-
Runs after `touch` each frame.
|
|
726
|
+
Runs after [`touch`][sonolus.script.archetype.PlayArchetype.touch] each frame.
|
|
719
727
|
|
|
720
728
|
This is where most gameplay logic should be placed.
|
|
721
729
|
"""
|
|
@@ -723,7 +731,7 @@ class PlayArchetype(_BaseArchetype):
|
|
|
723
731
|
def touch(self):
|
|
724
732
|
"""Handle user input.
|
|
725
733
|
|
|
726
|
-
Runs after `update_sequential` each frame.
|
|
734
|
+
Runs after [`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential] each frame.
|
|
727
735
|
"""
|
|
728
736
|
|
|
729
737
|
def terminate(self):
|
|
@@ -863,13 +871,14 @@ class WatchArchetype(_BaseArchetype):
|
|
|
863
871
|
Runs first each frame.
|
|
864
872
|
|
|
865
873
|
This is where logic affecting shared memory should be placed.
|
|
866
|
-
Other logic should be placed in
|
|
874
|
+
Other logic should typically be placed in
|
|
875
|
+
[`update_parallel`][sonolus.script.archetype.PlayArchetype.update_parallel] for better performance.
|
|
867
876
|
"""
|
|
868
877
|
|
|
869
878
|
def update_parallel(self):
|
|
870
879
|
"""Parallel update callback.
|
|
871
880
|
|
|
872
|
-
Runs after `touch` each frame.
|
|
881
|
+
Runs after [`touch`][sonolus.script.archetype.PlayArchetype.touch] each frame.
|
|
873
882
|
|
|
874
883
|
This is where most gameplay logic should be placed.
|
|
875
884
|
"""
|
|
@@ -1094,7 +1103,7 @@ class WatchEntityInput(Record):
|
|
|
1094
1103
|
class EntityRef[A: _BaseArchetype](Record):
|
|
1095
1104
|
"""Reference to another entity.
|
|
1096
1105
|
|
|
1097
|
-
May be used with `Any` to reference an unknown archetype.
|
|
1106
|
+
May be used with `typing.Any` to reference an unknown archetype.
|
|
1098
1107
|
|
|
1099
1108
|
Usage:
|
|
1100
1109
|
```python
|
sonolus/script/array.py
CHANGED
|
@@ -16,7 +16,7 @@ from sonolus.script.internal.value import BackingSource, DataValue, Value
|
|
|
16
16
|
from sonolus.script.num import Num
|
|
17
17
|
|
|
18
18
|
Dim = Literal
|
|
19
|
-
"""Shorthand for `Literal` intended for use in array dimensions for type checker compatibility."""
|
|
19
|
+
"""Shorthand for `typing.Literal` intended for use in array dimensions for type checker compatibility."""
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class ArrayMeta(ABCMeta):
|
sonolus/script/containers.py
CHANGED
|
@@ -33,7 +33,7 @@ class Box[T](Record):
|
|
|
33
33
|
x: T = ...
|
|
34
34
|
y: T = ...
|
|
35
35
|
box = Box(x)
|
|
36
|
-
box.value = y # Works regardless of whether x is a Num or
|
|
36
|
+
box.value = y # Works regardless of whether x is a Num, array, or record
|
|
37
37
|
```
|
|
38
38
|
"""
|
|
39
39
|
|
|
@@ -337,7 +337,7 @@ class ArrayPointer[T](Record, ArrayLike[T]):
|
|
|
337
337
|
raise TypeError("ArrayPointer values cannot be accessed outside of a context")
|
|
338
338
|
return _deref(
|
|
339
339
|
# Allows a compile time constant block so we can warn based on callback read/write access
|
|
340
|
-
(self.
|
|
340
|
+
(self._value_["block"]._is_py_() and self._value_["block"]._as_py_()) or self.block,
|
|
341
341
|
self.offset + Num._accept_(item) * Num._accept_(self.element_type()._size_()),
|
|
342
342
|
self.element_type(),
|
|
343
343
|
)
|
sonolus/script/easing.py
CHANGED
sonolus/script/effect.py
CHANGED
|
@@ -38,7 +38,8 @@ class Effect(Record):
|
|
|
38
38
|
def schedule(self, time: float, distance: float = 0) -> None:
|
|
39
39
|
"""Schedule the effect clip to play at a specific time.
|
|
40
40
|
|
|
41
|
-
This is not suitable for real-time effects such as responses to user input.
|
|
41
|
+
This is not suitable for real-time effects such as responses to user input.
|
|
42
|
+
Use [`play`][sonolus.script.effect.Effect.play] instead.
|
|
42
43
|
|
|
43
44
|
This may be called in preprocess to schedule effects upfront.
|
|
44
45
|
|
|
@@ -61,7 +62,8 @@ class Effect(Record):
|
|
|
61
62
|
def schedule_loop(self, start_time: float) -> ScheduledLoopedEffectHandle:
|
|
62
63
|
"""Schedule the effect clip to play in a loop until stopped.
|
|
63
64
|
|
|
64
|
-
This is not suitable for real-time effects such as responses to user input.
|
|
65
|
+
This is not suitable for real-time effects such as responses to user input.
|
|
66
|
+
Use [`loop`][sonolus.script.effect.Effect.loop] instead.
|
|
65
67
|
|
|
66
68
|
Returns:
|
|
67
69
|
A handle to stop the loop.
|
sonolus/script/globals.py
CHANGED
|
@@ -235,7 +235,10 @@ def _tutorial_instruction[T](cls: type[T]) -> T:
|
|
|
235
235
|
def level_memory[T](cls: type[T]) -> T:
|
|
236
236
|
"""Define level memory.
|
|
237
237
|
|
|
238
|
-
Level memory may be modified during gameplay in sequential callbacks
|
|
238
|
+
Level memory may be modified during gameplay in sequential callbacks
|
|
239
|
+
([`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess],
|
|
240
|
+
[`update_sequential`][sonolus.script.archetype.PlayArchetype.update_sequential],
|
|
241
|
+
[`touch`][sonolus.script.archetype.PlayArchetype.touch]).
|
|
239
242
|
|
|
240
243
|
Usage:
|
|
241
244
|
```python
|
|
@@ -265,7 +268,7 @@ def level_memory[T](cls: type[T]) -> T:
|
|
|
265
268
|
def level_data[T](cls: type[T]) -> T:
|
|
266
269
|
"""Define level data.
|
|
267
270
|
|
|
268
|
-
Level data may only be modified during
|
|
271
|
+
Level data may only be modified during [`preprocess`][sonolus.script.archetype.PlayArchetype.preprocess].
|
|
269
272
|
|
|
270
273
|
Usage:
|
|
271
274
|
```python
|
|
@@ -61,10 +61,12 @@ class GlobalContextState:
|
|
|
61
61
|
class CallbackContextState:
|
|
62
62
|
callback: str
|
|
63
63
|
used_names: dict[str, int]
|
|
64
|
+
no_eval: bool
|
|
64
65
|
|
|
65
|
-
def __init__(self, callback: str):
|
|
66
|
+
def __init__(self, callback: str, no_eval: bool = False):
|
|
66
67
|
self.callback = callback
|
|
67
68
|
self.used_names = {}
|
|
69
|
+
self.no_eval = no_eval
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
class Context:
|
|
@@ -111,6 +113,10 @@ class Context:
|
|
|
111
113
|
def used_names(self) -> dict[str, int]:
|
|
112
114
|
return self.callback_state.used_names
|
|
113
115
|
|
|
116
|
+
@property
|
|
117
|
+
def no_eval(self) -> bool:
|
|
118
|
+
return self.callback_state.no_eval
|
|
119
|
+
|
|
114
120
|
def check_readable(self, place: BlockPlace):
|
|
115
121
|
if debug_config().unchecked_reads:
|
|
116
122
|
return
|
|
@@ -32,7 +32,7 @@ def native_function[**P, R](op: Op) -> Callable[[Callable[P, R]], Callable[P, R]
|
|
|
32
32
|
if ctx():
|
|
33
33
|
bound_args = signature.bind(*args)
|
|
34
34
|
bound_args.apply_defaults()
|
|
35
|
-
return native_call(op, *
|
|
35
|
+
return native_call(op, *bound_args.args)
|
|
36
36
|
return fn(*args) # type: ignore
|
|
37
37
|
|
|
38
38
|
return wrapper
|
sonolus/script/iterator.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterator
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
6
|
+
from sonolus.script.internal.context import ctx
|
|
5
7
|
from sonolus.script.internal.impl import meta_fn
|
|
6
8
|
from sonolus.script.maybe import Maybe, Nothing, Some
|
|
7
9
|
from sonolus.script.record import Record
|
|
@@ -12,7 +14,8 @@ class SonolusIterator[T]:
|
|
|
12
14
|
|
|
13
15
|
This class is used to define custom iterators that can be used in Sonolus.py.
|
|
14
16
|
|
|
15
|
-
Inheritors must implement the `next` method,
|
|
17
|
+
Inheritors must implement the [`next`][sonolus.script.iterator.SonolusIterator.next] method,
|
|
18
|
+
which should return a [`Maybe[T]`][sonolus.script.maybe.Maybe].
|
|
16
19
|
|
|
17
20
|
Usage:
|
|
18
21
|
```python
|
|
@@ -26,6 +29,7 @@ class SonolusIterator[T]:
|
|
|
26
29
|
|
|
27
30
|
@meta_fn
|
|
28
31
|
def next(self) -> Maybe[T]:
|
|
32
|
+
"""Return the next item from the iterator as a [`Maybe`][sonolus.script.maybe.Maybe]."""
|
|
29
33
|
raise NotImplementedError
|
|
30
34
|
|
|
31
35
|
def __next__(self) -> T:
|
|
@@ -114,3 +118,16 @@ class _FilteringIterator[T, Fn](Record, SonolusIterator):
|
|
|
114
118
|
inside = value.get_unsafe()
|
|
115
119
|
if self.fn(inside):
|
|
116
120
|
return Some(inside)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@meta_fn
|
|
124
|
+
def maybe_next[T](iterator: Iterator[T]) -> Maybe[T]:
|
|
125
|
+
"""Get the next item from an iterator as a [`Maybe`][sonolus.script.maybe.Maybe]."""
|
|
126
|
+
from sonolus.backend.visitor import compile_and_call
|
|
127
|
+
|
|
128
|
+
if not isinstance(iterator, SonolusIterator):
|
|
129
|
+
raise TypeError("Iterator must be an instance of SonolusIterator.")
|
|
130
|
+
if ctx():
|
|
131
|
+
return compile_and_call(iterator.next)
|
|
132
|
+
else:
|
|
133
|
+
return iterator.next()
|
sonolus/script/level.py
CHANGED
|
@@ -145,7 +145,7 @@ class Level:
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
type EntityListArg = list[PlayArchetype |
|
|
148
|
+
type EntityListArg = list[list[PlayArchetype] | PlayArchetype] | PlayArchetype
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def flatten_entities(entities: EntityListArg) -> Iterator[PlayArchetype]:
|
|
@@ -175,7 +175,7 @@ class LevelData:
|
|
|
175
175
|
bgm_offset: float
|
|
176
176
|
entities: list[PlayArchetype]
|
|
177
177
|
|
|
178
|
-
def __init__(self, bgm_offset: float, entities:
|
|
178
|
+
def __init__(self, bgm_offset: float, entities: EntityListArg) -> None:
|
|
179
179
|
self.bgm_offset = bgm_offset
|
|
180
180
|
self.entities = [*flatten_entities(entities)]
|
|
181
181
|
|
sonolus/script/maybe.py
CHANGED
|
@@ -4,19 +4,21 @@ from collections.abc import Callable
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from sonolus.script.internal.context import ctx
|
|
7
|
-
from sonolus.script.internal.impl import meta_fn
|
|
7
|
+
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
8
8
|
from sonolus.script.internal.transient import TransientValue
|
|
9
9
|
from sonolus.script.internal.value import Value
|
|
10
10
|
from sonolus.script.num import Num
|
|
11
|
+
from sonolus.script.values import copy, zeros
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Maybe[T](TransientValue):
|
|
14
15
|
"""A type that either has a value or is empty.
|
|
15
16
|
|
|
16
|
-
Maybe has special behavior when returned from a function: it may be returned from
|
|
17
|
-
in a function, provided that all but one return statement returns the literal
|
|
17
|
+
`Maybe` has special behavior when returned from a function: unlike records and arrays, it may be returned from
|
|
18
|
+
multiple places in a function, provided that all but one return statement returns the literal
|
|
19
|
+
[`Nothing`][sonolus.script.maybe.Nothing].
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Storing values of this type in a Record, Array, or Archetype is not supported.
|
|
20
22
|
|
|
21
23
|
Usage:
|
|
22
24
|
```python
|
|
@@ -38,13 +40,16 @@ class Maybe[T](TransientValue):
|
|
|
38
40
|
|
|
39
41
|
def __init__(self, *, present: bool, value: T):
|
|
40
42
|
self._present = Num._accept_(present)
|
|
41
|
-
self._value = value
|
|
43
|
+
self._value = validate_value(value)
|
|
42
44
|
|
|
43
45
|
@property
|
|
44
46
|
@meta_fn
|
|
45
47
|
def is_some(self) -> bool:
|
|
46
48
|
"""Check if the value is present."""
|
|
47
49
|
if ctx():
|
|
50
|
+
if self._present._is_py_():
|
|
51
|
+
# Makes this a compile time constant.
|
|
52
|
+
return self._present
|
|
48
53
|
return self._present._get_readonly_()
|
|
49
54
|
else:
|
|
50
55
|
return self._present._as_py_()
|
|
@@ -61,13 +66,106 @@ class Maybe[T](TransientValue):
|
|
|
61
66
|
|
|
62
67
|
@meta_fn
|
|
63
68
|
def get_unsafe(self) -> T:
|
|
64
|
-
|
|
69
|
+
if ctx():
|
|
70
|
+
return self._value
|
|
71
|
+
else:
|
|
72
|
+
return self._value._as_py_()
|
|
65
73
|
|
|
66
74
|
def map[R](self, fn: Callable[[T], R], /) -> Maybe[R]:
|
|
75
|
+
"""Map the contained value to a new value using the provided function.
|
|
76
|
+
|
|
77
|
+
If the value is not present, returns [`Nothing`][sonolus.script.maybe.Nothing].
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
fn: A function that takes the contained value and returns a new value.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A [`Maybe`][sonolus.script.maybe.Maybe] instance containing the result of the function if the value
|
|
84
|
+
is present, otherwise [`Nothing`][sonolus.script.maybe.Nothing].
|
|
85
|
+
"""
|
|
67
86
|
if self.is_some:
|
|
68
87
|
return Some(fn(self.get_unsafe()))
|
|
69
88
|
return Nothing
|
|
70
89
|
|
|
90
|
+
def flat_map[R](self, fn: Callable[[T], Maybe[R]], /) -> Maybe[R]:
|
|
91
|
+
"""Flat map the contained value to a new [`Maybe`][sonolus.script.maybe.Maybe] using the provided function.
|
|
92
|
+
|
|
93
|
+
If the value is not present, returns [`Nothing`][sonolus.script.maybe.Nothing].
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
fn: A function that takes the contained value and returns a new [`Maybe`][sonolus.script.maybe.Maybe].
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A [`Maybe`][sonolus.script.maybe.Maybe] instance containing the result of the function if the value
|
|
100
|
+
is present, otherwise [`Nothing`][sonolus.script.maybe.Nothing].
|
|
101
|
+
"""
|
|
102
|
+
if self.is_some:
|
|
103
|
+
return fn(self.get_unsafe())
|
|
104
|
+
return Nothing
|
|
105
|
+
|
|
106
|
+
def or_default(self, default: T) -> T:
|
|
107
|
+
"""Return a copy of the contained value if present, otherwise return a copy of the given default value.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
default: The default value to return if the contained value is not present.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A copy of the contained value if present, otherwise a copy of the default value.
|
|
114
|
+
"""
|
|
115
|
+
result = _box(copy(default))
|
|
116
|
+
if self.is_some:
|
|
117
|
+
result.value = self.get_unsafe()
|
|
118
|
+
return result.value
|
|
119
|
+
|
|
120
|
+
@meta_fn
|
|
121
|
+
def or_else(self, fn: Callable[[], T], /) -> T:
|
|
122
|
+
"""Return a copy of the contained value if present, otherwise return a copy of the result of the given function.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
fn: A function that returns a value to use if the contained value is not present.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A copy of the contained value if present, otherwise a copy of the result of calling the function.
|
|
129
|
+
"""
|
|
130
|
+
from sonolus.backend.visitor import compile_and_call
|
|
131
|
+
|
|
132
|
+
if ctx():
|
|
133
|
+
if self.is_some._is_py_(): # type: ignore
|
|
134
|
+
if self.is_some._as_py_(): # type: ignore
|
|
135
|
+
return copy(self.get_unsafe())
|
|
136
|
+
else:
|
|
137
|
+
return copy(compile_and_call(fn))
|
|
138
|
+
else:
|
|
139
|
+
return compile_and_call(self._or_else, fn)
|
|
140
|
+
elif self.is_some:
|
|
141
|
+
return copy(self.get_unsafe())
|
|
142
|
+
else:
|
|
143
|
+
return copy(fn())
|
|
144
|
+
|
|
145
|
+
def _or_else(self, fn: Callable[[], T], /) -> T:
|
|
146
|
+
result = _box(zeros(self.contained_type))
|
|
147
|
+
if self.is_some:
|
|
148
|
+
result.value = self.get_unsafe()
|
|
149
|
+
else:
|
|
150
|
+
result.value = fn()
|
|
151
|
+
return result.value
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def tuple(self) -> tuple[bool, T]:
|
|
155
|
+
"""Return whether the value is present and a copy of the contained value if present as a tuple.
|
|
156
|
+
|
|
157
|
+
If the value is not present, the tuple will contain `False` and a zero initialized value of the contained type.
|
|
158
|
+
"""
|
|
159
|
+
result_value = _box(zeros(self.contained_type))
|
|
160
|
+
if self.is_some:
|
|
161
|
+
result_value.value = self.get_unsafe()
|
|
162
|
+
return self.is_some, result_value.value
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
@meta_fn
|
|
166
|
+
def contained_type(self):
|
|
167
|
+
return type(self._value)
|
|
168
|
+
|
|
71
169
|
@classmethod
|
|
72
170
|
def _accepts_(cls, value: Any) -> bool:
|
|
73
171
|
return isinstance(value, cls)
|
|
@@ -121,13 +219,13 @@ class Maybe[T](TransientValue):
|
|
|
121
219
|
|
|
122
220
|
|
|
123
221
|
def Some[T](value: T) -> Maybe[T]: # noqa: N802
|
|
124
|
-
"""Create a `Maybe` instance with a value.
|
|
222
|
+
"""Create a [`Maybe`][sonolus.script.maybe.Maybe] instance with a value.
|
|
125
223
|
|
|
126
224
|
Args:
|
|
127
225
|
value: The contained value.
|
|
128
226
|
|
|
129
227
|
Returns:
|
|
130
|
-
A `Maybe` instance that contains the provided value.
|
|
228
|
+
A [`Maybe`][sonolus.script.maybe.Maybe] instance that contains the provided value.
|
|
131
229
|
"""
|
|
132
230
|
return Maybe(present=True, value=value)
|
|
133
231
|
|
|
@@ -136,4 +234,11 @@ Nothing: Maybe[Any] = Maybe(present=False, value=None) # type: ignore
|
|
|
136
234
|
|
|
137
235
|
# Note: has to come after the definition to hide the definition in the docs.
|
|
138
236
|
Nothing: Maybe[Any]
|
|
139
|
-
"""The empty `Maybe` instance."""
|
|
237
|
+
"""The empty [`Maybe`][sonolus.script.maybe.Maybe] instance."""
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@meta_fn
|
|
241
|
+
def _box(value):
|
|
242
|
+
from sonolus.script.containers import Box
|
|
243
|
+
|
|
244
|
+
return Box(value)
|
sonolus/script/num.py
CHANGED
|
@@ -453,14 +453,14 @@ if TYPE_CHECKING:
|
|
|
453
453
|
from typing import Protocol
|
|
454
454
|
|
|
455
455
|
@runtime_checkable
|
|
456
|
-
class Num
|
|
457
|
-
def __add__(self, other:
|
|
458
|
-
def __sub__(self, other:
|
|
459
|
-
def __mul__(self, other:
|
|
460
|
-
def __truediv__(self, other:
|
|
461
|
-
def __floordiv__(self, other:
|
|
462
|
-
def __mod__(self, other:
|
|
463
|
-
def __pow__(self, other:
|
|
456
|
+
class Num(Protocol, int, bool, float):
|
|
457
|
+
def __add__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
458
|
+
def __sub__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
459
|
+
def __mul__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
460
|
+
def __truediv__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
461
|
+
def __floordiv__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
462
|
+
def __mod__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
463
|
+
def __pow__(self, other: Any, /) -> Num | int | bool | float: ...
|
|
464
464
|
|
|
465
465
|
def __neg__(self, /) -> Num | int | bool | float: ...
|
|
466
466
|
def __pos__(self, /) -> Num | int | bool | float: ...
|
|
@@ -468,10 +468,10 @@ if TYPE_CHECKING:
|
|
|
468
468
|
|
|
469
469
|
def __eq__(self, other: Any, /) -> bool: ...
|
|
470
470
|
def __ne__(self, other: Any, /) -> bool: ...
|
|
471
|
-
def __lt__(self, other:
|
|
472
|
-
def __le__(self, other:
|
|
473
|
-
def __gt__(self, other:
|
|
474
|
-
def __ge__(self, other:
|
|
471
|
+
def __lt__(self, other: Any, /) -> bool: ...
|
|
472
|
+
def __le__(self, other: Any, /) -> bool: ...
|
|
473
|
+
def __gt__(self, other: Any, /) -> bool: ...
|
|
474
|
+
def __ge__(self, other: Any, /) -> bool: ...
|
|
475
475
|
|
|
476
476
|
def __hash__(self, /) -> int: ...
|
|
477
477
|
|
sonolus/script/project.py
CHANGED
|
@@ -41,7 +41,7 @@ class Project:
|
|
|
41
41
|
self._levels = None
|
|
42
42
|
self.resources = Path(resources or "resources")
|
|
43
43
|
|
|
44
|
-
def with_levels(self, levels:
|
|
44
|
+
def with_levels(self, levels: Iterable[Level] | Callable[[], Iterable[Level]] | None) -> Project:
|
|
45
45
|
"""Create a new project with the specified levels.
|
|
46
46
|
|
|
47
47
|
Args:
|
sonolus/script/record.py
CHANGED
|
@@ -62,13 +62,13 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
62
62
|
```
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
_value_: dict[str, Value]
|
|
66
|
+
_fields_: ClassVar[list[_RecordField] | None] = None
|
|
67
|
+
_constructor_signature_: ClassVar[inspect.Signature]
|
|
68
68
|
|
|
69
69
|
@classmethod
|
|
70
70
|
def _validate_type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
71
|
-
if cls.
|
|
71
|
+
if cls._fields_ is None:
|
|
72
72
|
raise TypeError("Base Record class cannot have type arguments")
|
|
73
73
|
return super()._validate_type_args_(args)
|
|
74
74
|
|
|
@@ -80,16 +80,16 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
80
80
|
if is_parameterizing:
|
|
81
81
|
fields = []
|
|
82
82
|
offset = 0
|
|
83
|
-
for generic_field in cls.
|
|
83
|
+
for generic_field in cls._fields_:
|
|
84
84
|
resolved_type = validate_and_resolve_type(generic_field.type, cls._type_vars_to_args_)
|
|
85
85
|
resolved_type = validate_concrete_type(resolved_type)
|
|
86
86
|
field = _RecordField(generic_field.name, resolved_type, generic_field.index, offset)
|
|
87
87
|
fields.append(field)
|
|
88
88
|
setattr(cls, field.name, field)
|
|
89
89
|
offset += resolved_type._size_()
|
|
90
|
-
cls.
|
|
90
|
+
cls._fields_ = fields
|
|
91
91
|
return
|
|
92
|
-
is_inheriting_from_existing_record_class = cls.
|
|
92
|
+
is_inheriting_from_existing_record_class = cls._fields_ is not None
|
|
93
93
|
if is_inheriting_from_existing_record_class and not is_parameterizing:
|
|
94
94
|
# The main reason this is disallowed is that subclasses wouldn't be substitutable for their parent classes
|
|
95
95
|
# Assignment of a subclass instance to a variable of the parent class would either be disallowed or would
|
|
@@ -124,8 +124,8 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
cls._parameterized_ = {}
|
|
127
|
-
cls.
|
|
128
|
-
cls.
|
|
127
|
+
cls._fields_ = fields
|
|
128
|
+
cls._constructor_signature_ = inspect.Signature(params)
|
|
129
129
|
|
|
130
130
|
_add_inplace_ops(cls)
|
|
131
131
|
|
|
@@ -139,13 +139,13 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
139
139
|
|
|
140
140
|
def __new__(cls, *args, **kwargs):
|
|
141
141
|
# We override __new__ to allow changing to the parameterized version
|
|
142
|
-
if cls.
|
|
142
|
+
if cls._constructor_signature_ is None:
|
|
143
143
|
raise TypeError(f"Cannot instantiate {cls.__name__}")
|
|
144
|
-
bound = cls.
|
|
144
|
+
bound = cls._constructor_signature_.bind(*args, **kwargs)
|
|
145
145
|
bound.apply_defaults()
|
|
146
146
|
values = {}
|
|
147
147
|
type_vars = {}
|
|
148
|
-
for field in cls.
|
|
148
|
+
for field in cls._fields_:
|
|
149
149
|
value = bound.arguments[field.name]
|
|
150
150
|
value = accept_and_infer_types(field.type, value, type_vars)
|
|
151
151
|
values[field.name] = value._get_()
|
|
@@ -158,7 +158,7 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
158
158
|
else:
|
|
159
159
|
parameterized = cls[type_args]
|
|
160
160
|
result: cls = object.__new__(parameterized) # type: ignore
|
|
161
|
-
result.
|
|
161
|
+
result._value_ = values
|
|
162
162
|
return result
|
|
163
163
|
|
|
164
164
|
def __init__(self, *args, **kwargs):
|
|
@@ -168,12 +168,12 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
168
168
|
@classmethod
|
|
169
169
|
def _raw(cls, **kwargs) -> Self:
|
|
170
170
|
result = object.__new__(cls)
|
|
171
|
-
result.
|
|
171
|
+
result._value_ = kwargs
|
|
172
172
|
return result
|
|
173
173
|
|
|
174
174
|
@classmethod
|
|
175
175
|
def _size_(cls) -> int:
|
|
176
|
-
return sum(field.type._size_() for field in cls.
|
|
176
|
+
return sum(field.type._size_() for field in cls._fields_)
|
|
177
177
|
|
|
178
178
|
@classmethod
|
|
179
179
|
def _is_value_type_(cls) -> bool:
|
|
@@ -182,18 +182,18 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
182
182
|
@classmethod
|
|
183
183
|
def _from_backing_source_(cls, source: BackingSource) -> Self:
|
|
184
184
|
result = object.__new__(cls)
|
|
185
|
-
result.
|
|
185
|
+
result._value_ = {
|
|
186
186
|
field.name: field.type._from_backing_source_(
|
|
187
187
|
lambda offset, field_offset=field.offset: source((Num(offset) + Num(field_offset)).ir()) # type: ignore
|
|
188
188
|
)
|
|
189
|
-
for field in cls.
|
|
189
|
+
for field in cls._fields_
|
|
190
190
|
}
|
|
191
191
|
return result
|
|
192
192
|
|
|
193
193
|
@classmethod
|
|
194
194
|
def _from_place_(cls, place: BlockPlace) -> Self:
|
|
195
195
|
result = object.__new__(cls)
|
|
196
|
-
result.
|
|
196
|
+
result._value_ = {field.name: field.type._from_place_(place.add_offset(field.offset)) for field in cls._fields_}
|
|
197
197
|
return result
|
|
198
198
|
|
|
199
199
|
@classmethod
|
|
@@ -207,7 +207,7 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
207
207
|
return value
|
|
208
208
|
|
|
209
209
|
def _is_py_(self) -> bool:
|
|
210
|
-
return all(value._is_py_() for value in self.
|
|
210
|
+
return all(value._is_py_() for value in self._value_.values())
|
|
211
211
|
|
|
212
212
|
def _as_py_(self) -> Self:
|
|
213
213
|
if not self._is_py_():
|
|
@@ -217,18 +217,18 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
217
217
|
@classmethod
|
|
218
218
|
def _from_list_(cls, values: Iterable[DataValue]) -> Self:
|
|
219
219
|
iterator = iter(values)
|
|
220
|
-
return cls(**{field.name: field.type._from_list_(iterator) for field in cls.
|
|
220
|
+
return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields_})
|
|
221
221
|
|
|
222
222
|
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
|
|
223
223
|
result = []
|
|
224
|
-
for field in self.
|
|
225
|
-
result.extend(self.
|
|
224
|
+
for field in self._fields_:
|
|
225
|
+
result.extend(self._value_[field.name]._to_list_(level_refs))
|
|
226
226
|
return result
|
|
227
227
|
|
|
228
228
|
@classmethod
|
|
229
229
|
def _flat_keys_(cls, prefix: str) -> list[str]:
|
|
230
230
|
result = []
|
|
231
|
-
for field in cls.
|
|
231
|
+
for field in cls._fields_:
|
|
232
232
|
result.extend(field.type._flat_keys_(f"{prefix}.{field.name}"))
|
|
233
233
|
return result
|
|
234
234
|
|
|
@@ -240,33 +240,33 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
240
240
|
|
|
241
241
|
def _copy_from_(self, value: Any):
|
|
242
242
|
value = self._accept_(value)
|
|
243
|
-
for field in self.
|
|
243
|
+
for field in self._fields_:
|
|
244
244
|
field.__set__(self, field.__get__(value))
|
|
245
245
|
|
|
246
246
|
def _copy_(self) -> Self:
|
|
247
|
-
return type(self)._raw(**{field.name: self.
|
|
247
|
+
return type(self)._raw(**{field.name: self._value_[field.name]._copy_() for field in self._fields_})
|
|
248
248
|
|
|
249
249
|
@classmethod
|
|
250
250
|
def _alloc_(cls) -> Self:
|
|
251
251
|
# Compared to using the constructor, this avoids unnecessary _get_ calls
|
|
252
252
|
result = object.__new__(cls)
|
|
253
|
-
result.
|
|
253
|
+
result._value_ = {field.name: field.type._alloc_() for field in cls._fields_}
|
|
254
254
|
return result
|
|
255
255
|
|
|
256
256
|
@classmethod
|
|
257
257
|
def _zero_(cls) -> Self:
|
|
258
258
|
result = object.__new__(cls)
|
|
259
|
-
result.
|
|
259
|
+
result._value_ = {field.name: field.type._zero_() for field in cls._fields_}
|
|
260
260
|
return result
|
|
261
261
|
|
|
262
262
|
def __str__(self):
|
|
263
263
|
return f"{self.__class__.__name__}({
|
|
264
|
-
', '.join(f'{field.name}={field.get_internal(self)}' for field in self.
|
|
264
|
+
', '.join(f'{field.name}={field.get_internal(self)}' for field in self._fields_)
|
|
265
265
|
})"
|
|
266
266
|
|
|
267
267
|
def __repr__(self):
|
|
268
268
|
return f"{self.__class__.__name__}({
|
|
269
|
-
', '.join(f'{field.name}={field.get_internal(self)!r}' for field in self.
|
|
269
|
+
', '.join(f'{field.name}={field.get_internal(self)!r}' for field in self._fields_)
|
|
270
270
|
})"
|
|
271
271
|
|
|
272
272
|
@meta_fn
|
|
@@ -274,7 +274,7 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
274
274
|
if not isinstance(other, type(self)):
|
|
275
275
|
return False
|
|
276
276
|
result: Num = Num._accept_(True)
|
|
277
|
-
for field in self.
|
|
277
|
+
for field in self._fields_:
|
|
278
278
|
result = result.and_(field.__get__(self) == field.__get__(other))
|
|
279
279
|
return result
|
|
280
280
|
|
|
@@ -283,12 +283,12 @@ class Record(GenericValue, metaclass=RecordMeta):
|
|
|
283
283
|
if not isinstance(other, type(self)):
|
|
284
284
|
return True
|
|
285
285
|
result: Num = Num._accept_(False)
|
|
286
|
-
for field in self.
|
|
286
|
+
for field in self._fields_:
|
|
287
287
|
result = result.or_(field.__get__(self) != field.__get__(other))
|
|
288
288
|
return result
|
|
289
289
|
|
|
290
290
|
def __hash__(self):
|
|
291
|
-
return hash(tuple(field.__get__(self) for field in self.
|
|
291
|
+
return hash(tuple(field.__get__(self) for field in self._fields_))
|
|
292
292
|
|
|
293
293
|
@meta_fn
|
|
294
294
|
def __pos__(self) -> Self:
|
|
@@ -317,12 +317,12 @@ class _RecordField(SonolusDescriptor):
|
|
|
317
317
|
self.offset = offset
|
|
318
318
|
|
|
319
319
|
def get_internal(self, instance: Record) -> Value:
|
|
320
|
-
return instance.
|
|
320
|
+
return instance._value_[self.name]
|
|
321
321
|
|
|
322
322
|
def __get__(self, instance: Record | None, owner=None):
|
|
323
323
|
if instance is None:
|
|
324
324
|
return self
|
|
325
|
-
result = instance.
|
|
325
|
+
result = instance._value_[self.name]._get_readonly_()
|
|
326
326
|
if ctx():
|
|
327
327
|
return result
|
|
328
328
|
else:
|
|
@@ -331,9 +331,9 @@ class _RecordField(SonolusDescriptor):
|
|
|
331
331
|
def __set__(self, instance: Record, value):
|
|
332
332
|
value = self.type._accept_(value)
|
|
333
333
|
if self.type._is_value_type_():
|
|
334
|
-
instance.
|
|
334
|
+
instance._value_[self.name]._set_(value)
|
|
335
335
|
else:
|
|
336
|
-
instance.
|
|
336
|
+
instance._value_[self.name]._copy_from_(value)
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
_ops_to_inplace_ops = {
|
sonolus/script/sprite.py
CHANGED
sonolus/script/stream.py
CHANGED
|
@@ -63,7 +63,8 @@ class _StreamDataField(SonolusDescriptor):
|
|
|
63
63
|
def streams[T](cls: type[T]) -> T:
|
|
64
64
|
"""Decorator to define streams and stream groups.
|
|
65
65
|
|
|
66
|
-
Streams and stream groups are declared by annotating class attributes with
|
|
66
|
+
Streams and stream groups are declared by annotating class attributes with
|
|
67
|
+
[`Stream`][sonolus.script.stream.Stream] or [`StreamGroup`][sonolus.script.stream.StreamGroup].
|
|
67
68
|
|
|
68
69
|
Other types are also supported in the form of data fields. They may be used to store additional data to export from
|
|
69
70
|
Play to Watch mode.
|
|
@@ -180,8 +181,8 @@ class _SparseStreamBacking(BackingValue):
|
|
|
180
181
|
class Stream[T](Record):
|
|
181
182
|
"""Represents a stream.
|
|
182
183
|
|
|
183
|
-
Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups rather than
|
|
184
|
-
|
|
184
|
+
Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups, rather than
|
|
185
|
+
creating instances of this class directly.
|
|
185
186
|
|
|
186
187
|
If used directly, it is important that streams do not overlap. No other streams should have an offset in
|
|
187
188
|
`range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
|
|
@@ -285,12 +286,12 @@ class Stream[T](Record):
|
|
|
285
286
|
return previous_key < key
|
|
286
287
|
|
|
287
288
|
def next_key_inclusive(self, key: int | float) -> int | float:
|
|
288
|
-
"""Like `next_key
|
|
289
|
+
"""Like [`next_key`][sonolus.script.stream.Stream.next_key], but returns the key itself if it is in the stream."""
|
|
289
290
|
_check_can_read_stream()
|
|
290
291
|
return key if key in self else self.next_key(key)
|
|
291
292
|
|
|
292
293
|
def previous_key_inclusive(self, key: int | float) -> int | float:
|
|
293
|
-
"""Like `previous_key
|
|
294
|
+
"""Like [`previous_key`][sonolus.script.stream.Stream.previous_key], but returns the key itself if it is in the stream."""
|
|
294
295
|
_check_can_read_stream()
|
|
295
296
|
return key if key in self else self.previous_key(key)
|
|
296
297
|
|
|
@@ -10,8 +10,8 @@ sonolus/backend/mode.py,sha256=NkcPZJm8dn83LX35uP24MtQOCnfRDFZ280dHeEEfauE,613
|
|
|
10
10
|
sonolus/backend/node.py,sha256=eEzPP14jzWJp2xrZCAaPlNtokxdoqg0bSM7xQiwx1j8,1254
|
|
11
11
|
sonolus/backend/ops.py,sha256=5weB_vIxbkwCSJuzYZyKUk7vVXsSIEDJYRlvE-2ke8A,10572
|
|
12
12
|
sonolus/backend/place.py,sha256=7qwV732hZ4WP-9GNN8FQSEKssPJZELip1wLXTWfop7Y,4717
|
|
13
|
-
sonolus/backend/utils.py,sha256=
|
|
14
|
-
sonolus/backend/visitor.py,sha256=
|
|
13
|
+
sonolus/backend/utils.py,sha256=DDgYUVHh4h_eSY65v2KcxUaLSBYGYS6oHar5gTdV7QU,2356
|
|
14
|
+
sonolus/backend/visitor.py,sha256=6QjQ9tYOCP2a8-8WPgm8EB-hzwV5pcwLap3eDDrFG8I,62364
|
|
15
15
|
sonolus/backend/optimize/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
sonolus/backend/optimize/allocate.py,sha256=CuumoMphkpQlGRNeKLHT4FBGE0XVj5pwhfNdrqiLFSs,7535
|
|
17
17
|
sonolus/backend/optimize/constant_evaluation.py,sha256=OyjlgHIT6oPKCyBtNzEpo1nWYjr-_mwYUo8FrNV4eO4,21331
|
|
@@ -26,41 +26,41 @@ sonolus/backend/optimize/passes.py,sha256=YyFKy6qCwcR_Ua2_SXpcBODfvBbm_ygVYcqloO
|
|
|
26
26
|
sonolus/backend/optimize/simplify.py,sha256=RDNVTKfC7ByRyxY5z30_ShimOAKth_pKlVFV_36pDG4,14082
|
|
27
27
|
sonolus/backend/optimize/ssa.py,sha256=raQO0furQQRPYb8iIBKfNrJlj-_5wqtI4EWNfLZ8QFo,10834
|
|
28
28
|
sonolus/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
sonolus/build/cli.py,sha256
|
|
29
|
+
sonolus/build/cli.py,sha256=-_lTN8zT7nQB2lySM8itAEPVutcEQI-TJ13BcPIfGb4,10113
|
|
30
30
|
sonolus/build/collection.py,sha256=kOVnpQC_KHAsyTM4nAplSh6QE16CgO-H6PP1GItWm78,12187
|
|
31
|
-
sonolus/build/compile.py,sha256=
|
|
31
|
+
sonolus/build/compile.py,sha256=B7w6Uqa4HlZPNklNhhjJoq9ncXMVXLCYabGLxtxWGV4,6521
|
|
32
32
|
sonolus/build/engine.py,sha256=zUl0KfRygqNhIM8BABNJkKG-0zXFwcYwck-5hJy59yk,13338
|
|
33
33
|
sonolus/build/level.py,sha256=yXsQtnabkJK0vuVmZ_Wr1jx37jFLgInCS7lTlXjkv9Q,706
|
|
34
34
|
sonolus/build/node.py,sha256=gnX71RYDUOK_gYMpinQi-bLWO4csqcfiG5gFmhxzSec,1330
|
|
35
35
|
sonolus/build/project.py,sha256=mv2OuzgIDG-E9G4SIRiUDL5XiPCd1i81wVl1p7itFHA,6329
|
|
36
36
|
sonolus/script/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
sonolus/script/archetype.py,sha256=
|
|
38
|
-
sonolus/script/array.py,sha256=
|
|
37
|
+
sonolus/script/archetype.py,sha256=HJeKJ_vj7xUYaCAmNdGYO45rhFMSLMwOLyvpKjulfVY,42418
|
|
38
|
+
sonolus/script/array.py,sha256=9uOUHZIDMyMT9q3APcXJWXWt97yG-AZoRlxwrvSY6SU,12367
|
|
39
39
|
sonolus/script/array_like.py,sha256=jFOfXkniTLrIK4ER6HO_tUyKn_TvwjyM4B3SDd9cUS8,9678
|
|
40
40
|
sonolus/script/bucket.py,sha256=5VU-Bh7qYifl6NcgZFCo7eAEgi3iGTX-PUTmXiPCJaQ,7535
|
|
41
|
-
sonolus/script/containers.py,sha256=
|
|
41
|
+
sonolus/script/containers.py,sha256=WX0qRnr6gNtn3C0q7MwMyrzvY-C0Bd1tzLGxSkUwL9U,18680
|
|
42
42
|
sonolus/script/debug.py,sha256=_Hg1cXQJ8fBXMiwhmoPb2X9CKcQ8QO26WNa59K518og,4305
|
|
43
|
-
sonolus/script/easing.py,sha256=
|
|
44
|
-
sonolus/script/effect.py,sha256=
|
|
43
|
+
sonolus/script/easing.py,sha256=txf0OPFP5v7cOFDvJKCyKLK-d2uKIOu56ntLEHfD9QI,11377
|
|
44
|
+
sonolus/script/effect.py,sha256=mhl58IgHUl7GI6kDdwK6l_KWVXYWxxpAcfOjSkdrLj4,5944
|
|
45
45
|
sonolus/script/engine.py,sha256=etI9dJsQ7V9YZICVNZg54WqpLijPxG8eTPHiV-_EiG8,10687
|
|
46
|
-
sonolus/script/globals.py,sha256=
|
|
46
|
+
sonolus/script/globals.py,sha256=USwh_TuyZh1qORPVLk6x9qe3ca9iLhf5gnqT773gVvY,10007
|
|
47
47
|
sonolus/script/instruction.py,sha256=iBjY7nCNDT3w0SBJKlix3Z-85e7eE2qKeHp6C2Nq7KA,6753
|
|
48
48
|
sonolus/script/interval.py,sha256=dj6F2wn5uP6I6_mcZn-wIREgRUQbsLzhvhzB0oEyAdU,11290
|
|
49
|
-
sonolus/script/iterator.py,sha256=
|
|
50
|
-
sonolus/script/level.py,sha256=
|
|
51
|
-
sonolus/script/maybe.py,sha256=
|
|
49
|
+
sonolus/script/iterator.py,sha256=_ICY_yX7FG0Zbgs3NhVnaIBdVDpAeXjxJ_CQtq30l7Y,3774
|
|
50
|
+
sonolus/script/level.py,sha256=vnotMbdr_4-MJUsTXMbvWiw2MlMjMHme3q0XRdNFXRg,6349
|
|
51
|
+
sonolus/script/maybe.py,sha256=VYvTWgEfPzoXqI3i3zXhc4dz0pWBVoHmW8FtWH0GQvM,8194
|
|
52
52
|
sonolus/script/metadata.py,sha256=ttRK27eojHf3So50KQJ-8yj3udZoN1bli5iD-knaeLw,753
|
|
53
|
-
sonolus/script/num.py,sha256=
|
|
53
|
+
sonolus/script/num.py,sha256=924kWWZusW7oaWuvtQzdAMzkb4ZItWSJwNj3W9XrqZU,16041
|
|
54
54
|
sonolus/script/options.py,sha256=KlOud4QOf_lW1o6avKXbkjcMCDPkhLcEwt5PW7ZCH3s,9435
|
|
55
55
|
sonolus/script/particle.py,sha256=XczhwTJvU3dMOXXTxJI_5Mskro2LgVlNgrCSwYreO0Q,8369
|
|
56
56
|
sonolus/script/pointer.py,sha256=FoOfyD93r0G5d_2BaKfeOT9SqkOP3hq6sqtOs_Rb0c8,1511
|
|
57
57
|
sonolus/script/printing.py,sha256=mNYu9QWiacBBGZrnePZQMVwbbguoelUps9GiOK_aVRU,2096
|
|
58
|
-
sonolus/script/project.py,sha256=
|
|
58
|
+
sonolus/script/project.py,sha256=2XVUXcW49iiTfljvcFuYqtFzqhQIRvD7H7OwH1Mm98w,4009
|
|
59
59
|
sonolus/script/quad.py,sha256=XoAjaUqR60zIrC_CwheZs7HwS-DRS58yUmlj9GIjX7k,11179
|
|
60
|
-
sonolus/script/record.py,sha256
|
|
60
|
+
sonolus/script/record.py,sha256=-Ff60wBoF1v4-MJWzCNI9n5K3os6WphswZpdTBezeRs,12713
|
|
61
61
|
sonolus/script/runtime.py,sha256=rJZM_KbKmnwpjhDEpR0DrM6EMSEu46apIErWA_pfLJA,33321
|
|
62
|
-
sonolus/script/sprite.py,sha256=
|
|
63
|
-
sonolus/script/stream.py,sha256=
|
|
62
|
+
sonolus/script/sprite.py,sha256=pgFQvzWB-uy9MsOfV8QabXKjnNgx5vN6md9uNYztHco,16297
|
|
63
|
+
sonolus/script/stream.py,sha256=q3uJUfUP9fx0Go7qXQopl9DMh_RJkT-96p5WlAo4x0M,24693
|
|
64
64
|
sonolus/script/text.py,sha256=wxujIgKYcCfl2AD2_Im8g3vh0lDEHYwTSRZg9wsBPEU,13402
|
|
65
65
|
sonolus/script/timing.py,sha256=ZR0ypV2PIoDCMHHGOMfCeezStCsBQdzomdqaz5VKex0,2981
|
|
66
66
|
sonolus/script/transform.py,sha256=w5mr7hTuNYU0eTAdnN_wTVibaQa0mZrkl-W-kgewJxQ,21345
|
|
@@ -71,7 +71,7 @@ sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtU
|
|
|
71
71
|
sonolus/script/internal/builtin_impls.py,sha256=vzyaSTsxoZoE6a-aBBHn4aIfjGxxYzojEnOog3-HETI,12686
|
|
72
72
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
73
73
|
sonolus/script/internal/constant.py,sha256=3ycbGkDJVUwcrCZ96vLjAoAARgsvaqDM8rJ_YCrLrvo,4289
|
|
74
|
-
sonolus/script/internal/context.py,sha256=
|
|
74
|
+
sonolus/script/internal/context.py,sha256=qn8xp5BB1C3IaUS8jsSUfkMUJgPzeaIV7K4FY9mHHQo,16903
|
|
75
75
|
sonolus/script/internal/descriptor.py,sha256=XRFey-EjiAm_--KsNl-8N0Mi_iyQwlPh68gDp0pKf3E,392
|
|
76
76
|
sonolus/script/internal/dict_impl.py,sha256=alu_wKGSk1kZajNf64qbe7t71shEzD4N5xNIATH8Swo,1885
|
|
77
77
|
sonolus/script/internal/error.py,sha256=ZNnsvQVQAnFKzcvsm6-sste2lo-tP5pPI8sD7XlAZWc,490
|
|
@@ -79,15 +79,15 @@ sonolus/script/internal/generic.py,sha256=F0-cCiRNGTaUJvYlpmkiOsU3Xge_XjoBpBwBhH
|
|
|
79
79
|
sonolus/script/internal/impl.py,sha256=I3IlPZZZtXVJb044cpULg5FDYUjiLtAucnsA6w_0xUk,3233
|
|
80
80
|
sonolus/script/internal/introspection.py,sha256=MfdXo3GFHNZT2-vAKuvsE54V-5TOfbg4vU9dBI6sLqo,1032
|
|
81
81
|
sonolus/script/internal/math_impls.py,sha256=nHSLgA7Tcx7jY1p07mYBCeSRmVx713bwdNayCIcaXSE,2652
|
|
82
|
-
sonolus/script/internal/native.py,sha256=
|
|
82
|
+
sonolus/script/internal/native.py,sha256=DQxmzxgLG_UsLpXhIEtBdO7eIeDFprU78UBDC4OZzw0,1597
|
|
83
83
|
sonolus/script/internal/random.py,sha256=6Ku5edRcDUh7rtqEEYCJz0BQavw69RALsVHS25z50pI,1695
|
|
84
84
|
sonolus/script/internal/range.py,sha256=YeqB1TPh7JdvW6kDuA5tpQL5psVxYQjepBZs7RNcP5Y,3426
|
|
85
85
|
sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDwIJMVsHle0kvzd500,4818
|
|
86
86
|
sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
|
|
87
87
|
sonolus/script/internal/tuple_impl.py,sha256=DPNdmmRmupU8Ah4_XKq6-PdT336l4nt15_uCJKQGkkk,3587
|
|
88
88
|
sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
|
|
89
|
-
sonolus_py-0.4.
|
|
90
|
-
sonolus_py-0.4.
|
|
91
|
-
sonolus_py-0.4.
|
|
92
|
-
sonolus_py-0.4.
|
|
93
|
-
sonolus_py-0.4.
|
|
89
|
+
sonolus_py-0.4.2.dist-info/METADATA,sha256=rV2d-Y0jxrhlOQfcrYRAVAKiuobluI5omhDztt5mmLo,302
|
|
90
|
+
sonolus_py-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
91
|
+
sonolus_py-0.4.2.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
|
|
92
|
+
sonolus_py-0.4.2.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
|
|
93
|
+
sonolus_py-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|