sonolus.py 0.1.8__tar.gz → 0.1.9__tar.gz
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.
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/PKG-INFO +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/pyproject.toml +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/finalize.py +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/copy_coalesce.py +16 -7
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/visitor.py +32 -9
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/compile.py +2 -3
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/level.py +2 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/archetype.py +61 -27
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/array.py +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/bucket.py +26 -8
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/constant.py +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/introspection.py +8 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/transient.py +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/value.py +4 -2
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/level.py +20 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/num.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/record.py +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_flow.py +16 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_interval.py +2 -2
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_quad.py +17 -33
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/uv.lock +1 -1
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/.github/workflows/publish.yaml +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/.gitignore +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/.python-version +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/LICENSE +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/README.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/doc_stubs/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/doc_stubs/builtins.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/doc_stubs/math.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/doc_stubs/num.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/doc_stubs/random.pyi +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/CNAME +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/builtins.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/cli.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/constructs.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/project.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/resources.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/concepts/types.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/builtins.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/index.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/math.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/random.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.archetype.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.array.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.array_like.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.bucket.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.containers.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.debug.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.easing.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.effect.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.engine.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.globals.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.instruction.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.interval.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.iterator.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.level.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.num.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.options.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.particle.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.print.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.project.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.quad.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.record.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.runtime.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.sprite.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.text.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.timing.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.transform.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.ui.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.values.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/docs/reference/sonolus.script.vec.md +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/mkdocs.yml +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/generate.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/runtimes/Functions.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/runtimes/Level/Play/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/blocks.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/excepthook.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/interpret.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/ir.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/mode.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/node.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/ops.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/allocate.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/constant_evaluation.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/dead_code.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/dominance.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/flow.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/inlining.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/liveness.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/optimize.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/passes.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/simplify.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/optimize/ssa.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/place.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/backend/utils.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/cli.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/collection.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/engine.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/node.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/build/project.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/py.typed +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/array_like.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/containers.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/debug.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/easing.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/effect.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/engine.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/globals.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/instruction.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/builtin_impls.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/callbacks.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/context.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/descriptor.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/dict_impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/error.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/generic.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/math_impls.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/native.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/random.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/range.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/internal/tuple_impl.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/interval.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/iterator.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/options.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/particle.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/pointer.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/print.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/project.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/quad.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/runtime.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/sprite.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/text.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/timing.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/transform.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/ui.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/values.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/sonolus/script/vec.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/__init__.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/conftest.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_array.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_array_map.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_assert.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_dict.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_functions.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_helpers.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_match.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_num.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_operator.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_random.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_range.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_record.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_transform.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_tuple.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_var_array.py +0 -0
- {sonolus_py-0.1.8 → sonolus_py-0.1.9}/tests/script/test_vec.py +0 -0
|
@@ -42,6 +42,7 @@ def cfg_to_engine_node(entry: BasicBlock):
|
|
|
42
42
|
for cond, target in targets.items():
|
|
43
43
|
if cond is None:
|
|
44
44
|
default = block_indexes[target]
|
|
45
|
+
continue
|
|
45
46
|
args.append(ConstantNode(value=cond))
|
|
46
47
|
args.append(ConstantNode(value=block_indexes[target]))
|
|
47
48
|
args.append(ConstantNode(value=default))
|
|
@@ -55,7 +56,7 @@ def ir_to_engine_node(stmt) -> EngineNode:
|
|
|
55
56
|
match stmt:
|
|
56
57
|
case int() | float():
|
|
57
58
|
return ConstantNode(value=float(stmt))
|
|
58
|
-
case IRConst(value=value):
|
|
59
|
+
case IRConst(value=int(value) | float(value)):
|
|
59
60
|
return ConstantNode(value=value)
|
|
60
61
|
case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
|
|
61
62
|
return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
|
|
@@ -39,20 +39,29 @@ class CopyCoalesce(CompilerPass):
|
|
|
39
39
|
interference = self.get_interference(entry)
|
|
40
40
|
copies = self.get_copies(entry)
|
|
41
41
|
|
|
42
|
-
mapping = {}
|
|
42
|
+
mapping: dict[TempBlock, set[TempBlock]] = {}
|
|
43
43
|
|
|
44
44
|
for target, sources in copies.items():
|
|
45
45
|
for source in sources:
|
|
46
|
-
if source in mapping:
|
|
47
|
-
continue
|
|
48
46
|
if source in interference.get(target, set()):
|
|
49
47
|
continue
|
|
50
|
-
|
|
48
|
+
combined_mapping = mapping.get(target, {target}) | mapping.get(source, {source})
|
|
51
49
|
combined_interference = interference.get(target, set()) | interference.get(source, set())
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
for place in combined_mapping:
|
|
51
|
+
mapping[place] = combined_mapping
|
|
52
|
+
interference[place] = combined_interference
|
|
53
|
+
for place in combined_interference:
|
|
54
|
+
interference[place].update(combined_mapping)
|
|
55
|
+
|
|
56
|
+
canonical_mapping = {}
|
|
57
|
+
for place, group in mapping.items():
|
|
58
|
+
if place in canonical_mapping:
|
|
59
|
+
continue
|
|
60
|
+
canonical = min(group)
|
|
61
|
+
for member in group:
|
|
62
|
+
canonical_mapping[member] = canonical
|
|
54
63
|
|
|
55
|
-
return
|
|
64
|
+
return canonical_mapping
|
|
56
65
|
|
|
57
66
|
def get_interference(self, entry: BasicBlock) -> dict[TempBlock, set[TempBlock]]:
|
|
58
67
|
result = {}
|
|
@@ -335,6 +335,8 @@ class Visitor(ast.NodeVisitor):
|
|
|
335
335
|
has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
|
|
336
336
|
if has_next._is_py_() and not has_next._as_py_():
|
|
337
337
|
# The loop will never run, continue after evaluating the condition
|
|
338
|
+
self.loop_head_ctxs.pop()
|
|
339
|
+
self.break_ctxs.pop()
|
|
338
340
|
for stmt in node.orelse:
|
|
339
341
|
self.visit(stmt)
|
|
340
342
|
return
|
|
@@ -348,13 +350,14 @@ class Visitor(ast.NodeVisitor):
|
|
|
348
350
|
self.visit(stmt)
|
|
349
351
|
ctx().branch_to_loop_header(header_ctx)
|
|
350
352
|
|
|
353
|
+
self.loop_head_ctxs.pop()
|
|
354
|
+
break_ctxs = self.break_ctxs.pop()
|
|
355
|
+
|
|
351
356
|
set_ctx(else_ctx)
|
|
352
357
|
for stmt in node.orelse:
|
|
353
358
|
self.visit(stmt)
|
|
354
359
|
else_end_ctx = ctx()
|
|
355
360
|
|
|
356
|
-
self.loop_head_ctxs.pop()
|
|
357
|
-
break_ctxs = self.break_ctxs.pop()
|
|
358
361
|
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
359
362
|
set_ctx(after_ctx)
|
|
360
363
|
|
|
@@ -365,11 +368,30 @@ class Visitor(ast.NodeVisitor):
|
|
|
365
368
|
self.break_ctxs.append([])
|
|
366
369
|
set_ctx(header_ctx)
|
|
367
370
|
test = self.ensure_boolean_num(self.visit(node.test))
|
|
368
|
-
if test._is_py_()
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
if test._is_py_():
|
|
372
|
+
if test._as_py_():
|
|
373
|
+
# The loop will run until a break / return
|
|
374
|
+
body_ctx = ctx().branch(None)
|
|
375
|
+
set_ctx(body_ctx)
|
|
376
|
+
for stmt in node.body:
|
|
377
|
+
self.visit(stmt)
|
|
378
|
+
ctx().branch_to_loop_header(header_ctx)
|
|
379
|
+
|
|
380
|
+
self.loop_head_ctxs.pop()
|
|
381
|
+
break_ctxs = self.break_ctxs.pop()
|
|
382
|
+
|
|
383
|
+
# Skip the else block
|
|
384
|
+
|
|
385
|
+
after_ctx = Context.meet([ctx().into_dead(), *break_ctxs])
|
|
386
|
+
set_ctx(after_ctx)
|
|
387
|
+
return
|
|
388
|
+
else:
|
|
389
|
+
# The loop will never run, continue after evaluating the condition
|
|
390
|
+
self.loop_head_ctxs.pop()
|
|
391
|
+
self.break_ctxs.pop()
|
|
392
|
+
for stmt in node.orelse:
|
|
393
|
+
self.visit(stmt)
|
|
394
|
+
return
|
|
373
395
|
ctx().test = test.ir()
|
|
374
396
|
body_ctx = ctx().branch(None)
|
|
375
397
|
else_ctx = ctx().branch(0)
|
|
@@ -379,13 +401,14 @@ class Visitor(ast.NodeVisitor):
|
|
|
379
401
|
self.visit(stmt)
|
|
380
402
|
ctx().branch_to_loop_header(header_ctx)
|
|
381
403
|
|
|
404
|
+
self.loop_head_ctxs.pop()
|
|
405
|
+
break_ctxs = self.break_ctxs.pop()
|
|
406
|
+
|
|
382
407
|
set_ctx(else_ctx)
|
|
383
408
|
for stmt in node.orelse:
|
|
384
409
|
self.visit(stmt)
|
|
385
410
|
else_end_ctx = ctx()
|
|
386
411
|
|
|
387
|
-
self.loop_head_ctxs.pop()
|
|
388
|
-
break_ctxs = self.break_ctxs.pop()
|
|
389
412
|
after_ctx = Context.meet([else_end_ctx, *break_ctxs])
|
|
390
413
|
set_ctx(after_ctx)
|
|
391
414
|
|
|
@@ -36,15 +36,14 @@ def compile_mode(
|
|
|
36
36
|
if archetypes is not None:
|
|
37
37
|
archetype_entries = []
|
|
38
38
|
for archetype in archetypes:
|
|
39
|
+
archetype._init_fields()
|
|
39
40
|
archetype_data = {
|
|
40
41
|
"name": archetype.name,
|
|
41
42
|
"hasInput": archetype.is_scored,
|
|
42
43
|
"imports": [{"name": name, "index": index} for name, index in archetype._imported_keys_.items()],
|
|
43
44
|
}
|
|
44
45
|
if mode == Mode.PLAY:
|
|
45
|
-
archetype_data["exports"] = [
|
|
46
|
-
{"name": name, "index": index} for name, index in archetype._exported_keys_.items()
|
|
47
|
-
]
|
|
46
|
+
archetype_data["exports"] = [*archetype._exported_keys_]
|
|
48
47
|
for cb_name, cb_info in archetype._supported_callbacks_.items():
|
|
49
48
|
cb = getattr(archetype, cb_name)
|
|
50
49
|
if cb in archetype._default_callbacks_:
|
|
@@ -11,11 +11,12 @@ def package_level_data(
|
|
|
11
11
|
def build_level_data(
|
|
12
12
|
level_data: LevelData,
|
|
13
13
|
):
|
|
14
|
-
level_refs = {entity: i for i, entity in enumerate(level_data.entities)}
|
|
14
|
+
level_refs = {entity: f"{i}_{entity.name}" for i, entity in enumerate(level_data.entities)}
|
|
15
15
|
return {
|
|
16
16
|
"bgmOffset": level_data.bgm_offset,
|
|
17
17
|
"entities": [
|
|
18
18
|
{
|
|
19
|
+
"name": level_refs[entity],
|
|
19
20
|
"archetype": entity.name,
|
|
20
21
|
"data": entity._level_data_entries(level_refs),
|
|
21
22
|
}
|
|
@@ -62,7 +62,9 @@ class _ArchetypeField(SonolusDescriptor):
|
|
|
62
62
|
result = _deref(ctx().blocks.EntityData, self.offset, self.type)
|
|
63
63
|
case _ArchetypeReferenceData(index=index):
|
|
64
64
|
result = _deref(
|
|
65
|
-
ctx().blocks.EntityDataArray,
|
|
65
|
+
ctx().blocks.EntityDataArray,
|
|
66
|
+
Num._accept_(self.offset) + index * _ENTITY_DATA_SIZE,
|
|
67
|
+
self.type,
|
|
66
68
|
)
|
|
67
69
|
case _ArchetypeLevelData(values=values):
|
|
68
70
|
result = values[self.name]
|
|
@@ -108,7 +110,9 @@ class _ArchetypeField(SonolusDescriptor):
|
|
|
108
110
|
target = _deref(ctx().blocks.EntityData, self.offset, self.type)
|
|
109
111
|
case _ArchetypeReferenceData(index=index):
|
|
110
112
|
target = _deref(
|
|
111
|
-
ctx().blocks.EntityDataArray,
|
|
113
|
+
ctx().blocks.EntityDataArray,
|
|
114
|
+
Num._accept_(self.offset) + index * _ENTITY_DATA_SIZE,
|
|
115
|
+
self.type,
|
|
112
116
|
)
|
|
113
117
|
case _ArchetypeLevelData(values=values):
|
|
114
118
|
target = values[self.name]
|
|
@@ -119,7 +123,7 @@ class _ArchetypeField(SonolusDescriptor):
|
|
|
119
123
|
raise TypeError(f"Expected {self.type}, got {type(value)}")
|
|
120
124
|
for k, v in value._to_flat_dict_(self.data_name).items():
|
|
121
125
|
index = instance._exported_keys_[k]
|
|
122
|
-
ctx().add_statements(IRInstr(Op.ExportValue, [IRConst(index), Num
|
|
126
|
+
ctx().add_statements(IRInstr(Op.ExportValue, [IRConst(index), Num(v).ir()]))
|
|
123
127
|
return
|
|
124
128
|
case _ArchetypeReferenceData():
|
|
125
129
|
raise RuntimeError("Exported fields of other entities are not accessible")
|
|
@@ -342,6 +346,7 @@ class _BaseArchetype:
|
|
|
342
346
|
is_scored: ClassVar[bool] = False
|
|
343
347
|
|
|
344
348
|
def __init__(self, *args, **kwargs):
|
|
349
|
+
self._init_fields()
|
|
345
350
|
if ctx():
|
|
346
351
|
raise RuntimeError("The Archetype constructor is only for defining level data")
|
|
347
352
|
bound = self._data_constructor_signature_.bind_partial(*args, **kwargs)
|
|
@@ -354,10 +359,12 @@ class _BaseArchetype:
|
|
|
354
359
|
|
|
355
360
|
@classmethod
|
|
356
361
|
def _new(cls):
|
|
362
|
+
cls._init_fields()
|
|
357
363
|
return object.__new__(cls)
|
|
358
364
|
|
|
359
365
|
@classmethod
|
|
360
366
|
def _for_compilation(cls):
|
|
367
|
+
cls._init_fields()
|
|
361
368
|
result = cls._new()
|
|
362
369
|
result._data_ = _ArchetypeSelfData()
|
|
363
370
|
return result
|
|
@@ -369,6 +376,13 @@ class _BaseArchetype:
|
|
|
369
376
|
result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
|
|
370
377
|
return result
|
|
371
378
|
|
|
379
|
+
@classmethod
|
|
380
|
+
@meta_fn
|
|
381
|
+
def is_at(cls, index: Num) -> bool:
|
|
382
|
+
if not ctx():
|
|
383
|
+
raise RuntimeError("is_at is only available during compilation")
|
|
384
|
+
return entity_info_at(index).archetype_id == cls.id()
|
|
385
|
+
|
|
372
386
|
@classmethod
|
|
373
387
|
@meta_fn
|
|
374
388
|
def id(cls):
|
|
@@ -396,6 +410,7 @@ class _BaseArchetype:
|
|
|
396
410
|
Args:
|
|
397
411
|
**kwargs: Entity memory values to inject by field name as defined in the Archetype.
|
|
398
412
|
"""
|
|
413
|
+
cls._init_fields()
|
|
399
414
|
if not ctx():
|
|
400
415
|
raise RuntimeError("Spawn is only allowed within a callback")
|
|
401
416
|
archetype_id = cls.id()
|
|
@@ -408,16 +423,21 @@ class _BaseArchetype:
|
|
|
408
423
|
|
|
409
424
|
@classmethod
|
|
410
425
|
def schema(cls) -> ArchetypeSchema:
|
|
426
|
+
cls._init_fields()
|
|
411
427
|
return {"name": cls.name or "unnamed", "fields": list(cls._imported_fields_)}
|
|
412
428
|
|
|
413
|
-
def _level_data_entries(self, level_refs: dict[Any,
|
|
429
|
+
def _level_data_entries(self, level_refs: dict[Any, str] | None = None):
|
|
430
|
+
self._init_fields()
|
|
414
431
|
if not isinstance(self._data_, _ArchetypeLevelData):
|
|
415
432
|
raise RuntimeError("Entity is not level data")
|
|
416
433
|
entries = []
|
|
417
434
|
for name, value in self._data_.values.items():
|
|
418
435
|
field_info = self._imported_fields_.get(name)
|
|
419
436
|
for k, v in value._to_flat_dict_(field_info.data_name, level_refs).items():
|
|
420
|
-
|
|
437
|
+
if isinstance(v, str):
|
|
438
|
+
entries.append({"name": k, "ref": v})
|
|
439
|
+
else:
|
|
440
|
+
entries.append({"name": k, "value": v})
|
|
421
441
|
return entries
|
|
422
442
|
|
|
423
443
|
def __init_subclass__(cls, **kwargs):
|
|
@@ -430,7 +450,22 @@ class _BaseArchetype:
|
|
|
430
450
|
raise TypeError("Cannot subclass Archetypes")
|
|
431
451
|
if cls.name is None:
|
|
432
452
|
cls.name = cls.__name__
|
|
433
|
-
|
|
453
|
+
cls._callbacks_ = []
|
|
454
|
+
for name in cls._supported_callbacks_:
|
|
455
|
+
cb = getattr(cls, name)
|
|
456
|
+
if cb in cls._default_callbacks_:
|
|
457
|
+
continue
|
|
458
|
+
cls._callbacks_.append(cb)
|
|
459
|
+
cls._field_init_done = False
|
|
460
|
+
|
|
461
|
+
@classmethod
|
|
462
|
+
def _init_fields(cls):
|
|
463
|
+
if cls._field_init_done:
|
|
464
|
+
return
|
|
465
|
+
cls._field_init_done = True
|
|
466
|
+
field_specifiers = get_field_specifiers(
|
|
467
|
+
cls, skip={"name", "is_scored", "_callbacks_", "_field_init_done"}
|
|
468
|
+
).items()
|
|
434
469
|
cls._imported_fields_ = {}
|
|
435
470
|
cls._exported_fields_ = {}
|
|
436
471
|
cls._memory_fields_ = {}
|
|
@@ -506,12 +541,6 @@ class _BaseArchetype:
|
|
|
506
541
|
cls._spawn_signature_ = inspect.Signature(
|
|
507
542
|
[inspect.Parameter(name, inspect.Parameter.POSITIONAL_OR_KEYWORD) for name in cls._memory_fields_]
|
|
508
543
|
)
|
|
509
|
-
cls._callbacks_ = []
|
|
510
|
-
for name in cls._supported_callbacks_:
|
|
511
|
-
cb = getattr(cls, name)
|
|
512
|
-
if cb in cls._default_callbacks_:
|
|
513
|
-
continue
|
|
514
|
-
cls._callbacks_.append(cb)
|
|
515
544
|
|
|
516
545
|
|
|
517
546
|
class PlayArchetype(_BaseArchetype):
|
|
@@ -796,20 +825,6 @@ class WatchArchetype(_BaseArchetype):
|
|
|
796
825
|
case _:
|
|
797
826
|
raise RuntimeError("Result is only accessible from the entity itself")
|
|
798
827
|
|
|
799
|
-
@property
|
|
800
|
-
def target_time(self) -> float:
|
|
801
|
-
"""The target time of this entity.
|
|
802
|
-
|
|
803
|
-
Only meaningful for scored entities. Determines when combo and score are updated.
|
|
804
|
-
|
|
805
|
-
Alias of `result.target_time`.
|
|
806
|
-
"""
|
|
807
|
-
return self.result.target_time
|
|
808
|
-
|
|
809
|
-
@target_time.setter
|
|
810
|
-
def target_time(self, value: float):
|
|
811
|
-
self.result.target_time = value
|
|
812
|
-
|
|
813
828
|
|
|
814
829
|
class PreviewArchetype(_BaseArchetype):
|
|
815
830
|
"""Base class for preview mode archetypes.
|
|
@@ -967,10 +982,16 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
967
982
|
def archetype(cls) -> type[A]:
|
|
968
983
|
return cls.type_var_value(A)
|
|
969
984
|
|
|
985
|
+
def with_archetype(self, archetype: type[A]) -> EntityRef[A]:
|
|
986
|
+
return EntityRef[archetype](index=self.index)
|
|
987
|
+
|
|
970
988
|
def get(self) -> A:
|
|
971
989
|
return self.archetype().at(self.index)
|
|
972
990
|
|
|
973
|
-
def
|
|
991
|
+
def is_valid(self) -> bool:
|
|
992
|
+
return self.index >= 0 and self.archetype().is_at(self.index)
|
|
993
|
+
|
|
994
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
974
995
|
ref = getattr(self, "_ref_", None)
|
|
975
996
|
if ref is None:
|
|
976
997
|
return [self.index]
|
|
@@ -979,6 +1000,19 @@ class EntityRef[A: _BaseArchetype](Record):
|
|
|
979
1000
|
raise KeyError("Reference to entity not in level data")
|
|
980
1001
|
return [level_refs[ref]]
|
|
981
1002
|
|
|
1003
|
+
@classmethod
|
|
1004
|
+
def _accepts_(cls, value: Any) -> bool:
|
|
1005
|
+
return super()._accepts_(value) or (cls._type_args_ and cls.archetype() is Any and isinstance(value, EntityRef))
|
|
1006
|
+
|
|
1007
|
+
@classmethod
|
|
1008
|
+
def _accept_(cls, value: Any) -> Self:
|
|
1009
|
+
if not cls._accepts_(value):
|
|
1010
|
+
raise TypeError(f"Expected {cls}, got {type(value)}")
|
|
1011
|
+
result = value.with_archetype(cls.archetype())
|
|
1012
|
+
if hasattr(value, "_ref_"):
|
|
1013
|
+
result._ref_ = value._ref_
|
|
1014
|
+
return result
|
|
1015
|
+
|
|
982
1016
|
|
|
983
1017
|
class StandardArchetypeName(StrEnum):
|
|
984
1018
|
"""Standard archetype names."""
|
|
@@ -111,7 +111,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
111
111
|
iterator = iter(values)
|
|
112
112
|
return cls(*(cls.element_type()._from_list_(iterator) for _ in range(cls.size())))
|
|
113
113
|
|
|
114
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
114
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
115
115
|
match self._value:
|
|
116
116
|
case list():
|
|
117
117
|
return [entry for value in self._value for entry in value._to_list_(level_refs)]
|
|
@@ -80,6 +80,24 @@ class JudgmentWindow(Record):
|
|
|
80
80
|
self.good * other,
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
+
def __add__(self, other: float | int) -> JudgmentWindow:
|
|
84
|
+
"""Add a scalar to the intervals."""
|
|
85
|
+
return JudgmentWindow(
|
|
86
|
+
self.perfect + other,
|
|
87
|
+
self.great + other,
|
|
88
|
+
self.good + other,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def start(self) -> float:
|
|
93
|
+
"""The start time of the good interval."""
|
|
94
|
+
return self.good.start
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def end(self) -> float:
|
|
98
|
+
"""The end time of the good interval."""
|
|
99
|
+
return self.good.end
|
|
100
|
+
|
|
83
101
|
|
|
84
102
|
class Judgment(IntEnum):
|
|
85
103
|
"""The judgment of a hit."""
|
|
@@ -147,10 +165,10 @@ class Bucket(Record):
|
|
|
147
165
|
class _BucketSprite:
|
|
148
166
|
id: int
|
|
149
167
|
fallback_id: int | None
|
|
150
|
-
x:
|
|
151
|
-
y:
|
|
152
|
-
w:
|
|
153
|
-
h:
|
|
168
|
+
x: float
|
|
169
|
+
y: float
|
|
170
|
+
w: float
|
|
171
|
+
h: float
|
|
154
172
|
rotation: float
|
|
155
173
|
|
|
156
174
|
def to_dict(self):
|
|
@@ -185,10 +203,10 @@ def bucket_sprite(
|
|
|
185
203
|
*,
|
|
186
204
|
sprite: Sprite,
|
|
187
205
|
fallback_sprite: Sprite | None = None,
|
|
188
|
-
x:
|
|
189
|
-
y:
|
|
190
|
-
w:
|
|
191
|
-
h:
|
|
206
|
+
x: float,
|
|
207
|
+
y: float,
|
|
208
|
+
w: float,
|
|
209
|
+
h: float,
|
|
192
210
|
rotation: float = 0,
|
|
193
211
|
) -> _BucketSprite:
|
|
194
212
|
"""Define a sprite for a bucket."""
|
|
@@ -97,7 +97,7 @@ class ConstantValue(Value):
|
|
|
97
97
|
def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
|
|
98
98
|
return cls()
|
|
99
99
|
|
|
100
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
100
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
101
101
|
return []
|
|
102
102
|
|
|
103
103
|
@classmethod
|
|
@@ -12,6 +12,13 @@ def get_field_specifiers(cls, *, skip: set[str] = frozenset(), globals=None, loc
|
|
|
12
12
|
if class_value is not _missing and key not in skip:
|
|
13
13
|
results[key] = Annotated[value, class_value]
|
|
14
14
|
for key, value in cls.__dict__.items():
|
|
15
|
-
if
|
|
15
|
+
if (
|
|
16
|
+
key not in results
|
|
17
|
+
and key not in skip
|
|
18
|
+
and not key.startswith("__")
|
|
19
|
+
and not callable(value)
|
|
20
|
+
and not hasattr(value, "__func__")
|
|
21
|
+
and not isinstance(value, property)
|
|
22
|
+
):
|
|
16
23
|
raise ValueError(f"Missing annotation for {cls.__name__}.{key}")
|
|
17
24
|
return results
|
|
@@ -26,7 +26,7 @@ class TransientValue(Value):
|
|
|
26
26
|
def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
|
|
27
27
|
raise TypeError(f"{cls.__name__} cannot be constructed from list")
|
|
28
28
|
|
|
29
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
29
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
30
30
|
raise TypeError(f"{type(self).__name__} cannot be deconstructed to list")
|
|
31
31
|
|
|
32
32
|
@classmethod
|
|
@@ -66,7 +66,7 @@ class Value:
|
|
|
66
66
|
raise NotImplementedError
|
|
67
67
|
|
|
68
68
|
@abstractmethod
|
|
69
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
69
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
70
70
|
"""Converts this value to a list of floats."""
|
|
71
71
|
raise NotImplementedError
|
|
72
72
|
|
|
@@ -76,7 +76,9 @@ class Value:
|
|
|
76
76
|
"""Returns the keys to a flat representation of this value."""
|
|
77
77
|
raise NotImplementedError
|
|
78
78
|
|
|
79
|
-
def _to_flat_dict_(
|
|
79
|
+
def _to_flat_dict_(
|
|
80
|
+
self, prefix: str, level_refs: dict[Any, str] | None = None
|
|
81
|
+
) -> dict[str, float | str | BlockPlace]:
|
|
80
82
|
"""Converts this value to a flat dictionary."""
|
|
81
83
|
return dict(zip(self._flat_keys_(prefix), self._to_list_(level_refs), strict=False))
|
|
82
84
|
|
|
@@ -42,6 +42,25 @@ class Level:
|
|
|
42
42
|
self.data = data
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
type EntityListArg = list[PlayArchetype | EntityListArg]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def flatten_entities(entities: EntityListArg):
|
|
49
|
+
"""Flatten a list of entities.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
entities: The list of entities.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The flattened list of entities.
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(entities, list):
|
|
58
|
+
for entity in entities:
|
|
59
|
+
yield from flatten_entities(entity)
|
|
60
|
+
else:
|
|
61
|
+
yield entities
|
|
62
|
+
|
|
63
|
+
|
|
45
64
|
class LevelData:
|
|
46
65
|
"""The data of a Sonolus level.
|
|
47
66
|
|
|
@@ -55,7 +74,7 @@ class LevelData:
|
|
|
55
74
|
|
|
56
75
|
def __init__(self, bgm_offset: float, entities: list[PlayArchetype]) -> None:
|
|
57
76
|
self.bgm_offset = bgm_offset
|
|
58
|
-
self.entities = entities
|
|
77
|
+
self.entities = [*flatten_entities(entities)]
|
|
59
78
|
|
|
60
79
|
|
|
61
80
|
class BpmChange(PlayArchetype):
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import operator
|
|
5
5
|
from collections.abc import Callable, Iterable
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Self, final, runtime_checkable
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Self, TypeGuard, final, runtime_checkable
|
|
7
7
|
|
|
8
8
|
from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
|
|
9
9
|
from sonolus.backend.ops import Op
|
|
@@ -92,7 +92,7 @@ class _Num(Value, metaclass=_NumMeta):
|
|
|
92
92
|
value = next(iter(values))
|
|
93
93
|
return Num(value)
|
|
94
94
|
|
|
95
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
95
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
96
96
|
return [self.data]
|
|
97
97
|
|
|
98
98
|
@classmethod
|
|
@@ -186,7 +186,7 @@ class Record(GenericValue):
|
|
|
186
186
|
iterator = iter(values)
|
|
187
187
|
return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
|
|
188
188
|
|
|
189
|
-
def _to_list_(self, level_refs: dict[Any,
|
|
189
|
+
def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[float | str | BlockPlace]:
|
|
190
190
|
result = []
|
|
191
191
|
for field in self._fields:
|
|
192
192
|
result.extend(self._value[field.name]._to_list_(level_refs))
|
|
@@ -879,3 +879,19 @@ def test_loop_with_aug_assign():
|
|
|
879
879
|
|
|
880
880
|
for _ in range(100):
|
|
881
881
|
validate_dual_run(fn)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def test_break_in_nested_for_else():
|
|
885
|
+
def fn():
|
|
886
|
+
for _ in range(2):
|
|
887
|
+
for _ in range(2):
|
|
888
|
+
debug_log(1)
|
|
889
|
+
else:
|
|
890
|
+
debug_log(2)
|
|
891
|
+
break
|
|
892
|
+
debug_log(3)
|
|
893
|
+
else:
|
|
894
|
+
debug_log(4)
|
|
895
|
+
debug_log(5)
|
|
896
|
+
|
|
897
|
+
validate_dual_run(fn)
|
|
@@ -4,8 +4,8 @@ from hypothesis import strategies as st
|
|
|
4
4
|
from sonolus.script.interval import Interval, remap, remap_clamped
|
|
5
5
|
from tests.script.conftest import implies, is_close, validate_dual_run
|
|
6
6
|
|
|
7
|
-
ints = st.integers(min_value=-
|
|
8
|
-
floats = st.floats(min_value=-
|
|
7
|
+
ints = st.integers(min_value=-99999, max_value=99999)
|
|
8
|
+
floats = st.floats(min_value=-99999, max_value=99999, allow_infinity=False, allow_nan=False)
|
|
9
9
|
divisor_floats = floats.filter(lambda x: abs(x) > 1e-6)
|
|
10
10
|
|
|
11
11
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# ruff: noqa: E741
|
|
2
2
|
import itertools
|
|
3
|
-
import math
|
|
4
3
|
from datetime import timedelta
|
|
5
4
|
|
|
6
5
|
from hypothesis import assume, given, settings
|
|
@@ -22,7 +21,7 @@ def vecs(draw):
|
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def _area(points: list[Vec2]) -> float:
|
|
25
|
-
return 0.5 * abs(sum(p.x * q.y - p.y * q.x for p, q in zip(points, points[1:] + points[:1], strict=
|
|
24
|
+
return 0.5 * abs(sum(p.x * q.y - p.y * q.x for p, q in zip(points, points[1:] + points[:1], strict=True)))
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
@st.composite
|
|
@@ -254,41 +253,26 @@ def test_rect_as_quad(rect):
|
|
|
254
253
|
|
|
255
254
|
|
|
256
255
|
@st.composite
|
|
257
|
-
def
|
|
258
|
-
"""Generate a point and whether it should be inside a given quad."""
|
|
256
|
+
def quad_and_point(draw):
|
|
259
257
|
quad = draw(quads())
|
|
258
|
+
min_x = min(quad.bl.x, quad.tl.x, quad.tr.x, quad.br.x)
|
|
259
|
+
max_x = max(quad.bl.x, quad.tl.x, quad.tr.x, quad.br.x)
|
|
260
|
+
min_y = min(quad.bl.y, quad.tl.y, quad.tr.y, quad.br.y)
|
|
261
|
+
max_y = max(quad.bl.y, quad.tl.y, quad.tr.y, quad.br.y)
|
|
262
|
+
x = draw(st.floats(min_value=min_x, max_value=max_x))
|
|
263
|
+
y = draw(st.floats(min_value=min_y, max_value=max_y))
|
|
264
|
+
point = Vec2(x, y)
|
|
265
|
+
return quad, point
|
|
260
266
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if is_inside:
|
|
264
|
-
margin = 1.001
|
|
265
|
-
a = draw(st.floats(min_value=0, max_value=1)) + margin
|
|
266
|
-
b = draw(st.floats(min_value=0, max_value=1)) + margin
|
|
267
|
-
c = draw(st.floats(min_value=0, max_value=1)) + margin
|
|
268
|
-
d = draw(st.floats(min_value=0, max_value=1)) + margin
|
|
269
|
-
total = a + b + c + d
|
|
270
|
-
a /= total
|
|
271
|
-
b /= total
|
|
272
|
-
c /= total
|
|
273
|
-
d /= total
|
|
274
|
-
# Not totally uniform, but good enough for testing
|
|
275
|
-
point = quad.bl * a + quad.tl * b + quad.tr * c + quad.br * d
|
|
276
|
-
else:
|
|
277
|
-
diam = max((quad.tl - quad.br).magnitude, (quad.tr - quad.bl).magnitude)
|
|
278
|
-
angle = draw(st.floats(min_value=0, max_value=2 * math.pi))
|
|
279
|
-
scale = draw(st.floats(min_value=1, max_value=2))
|
|
280
|
-
point = quad.center + Vec2(math.cos(angle), math.sin(angle)) * scale * (diam + 0.001)
|
|
281
|
-
|
|
282
|
-
return quad, point, is_inside
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
@given(quad_point_expected=points_with_expected_containment())
|
|
267
|
+
|
|
268
|
+
@given(quad_point=quad_and_point())
|
|
286
269
|
@settings(deadline=timedelta(seconds=2))
|
|
287
|
-
def test_quad_contains_point(
|
|
288
|
-
quad, point
|
|
270
|
+
def test_quad_contains_point(quad_point):
|
|
271
|
+
quad, point = quad_point
|
|
289
272
|
|
|
290
273
|
def fn():
|
|
291
274
|
return quad.contains_point(point)
|
|
292
275
|
|
|
293
|
-
|
|
294
|
-
|
|
276
|
+
# Don't have an easy way to validate the correct solution
|
|
277
|
+
# But this still checks that the compiled and Python versions are consistent
|
|
278
|
+
validate_dual_run(fn)
|