sonolus.py 0.10.6__py3-none-any.whl → 0.10.7__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/script/containers.py +255 -1
- sonolus/script/internal/builtin_impls.py +6 -0
- sonolus/script/vec.py +21 -0
- {sonolus_py-0.10.6.dist-info → sonolus_py-0.10.7.dist-info}/METADATA +1 -1
- {sonolus_py-0.10.6.dist-info → sonolus_py-0.10.7.dist-info}/RECORD +8 -8
- {sonolus_py-0.10.6.dist-info → sonolus_py-0.10.7.dist-info}/WHEEL +0 -0
- {sonolus_py-0.10.6.dist-info → sonolus_py-0.10.7.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.10.6.dist-info → sonolus_py-0.10.7.dist-info}/licenses/LICENSE +0 -0
sonolus/script/containers.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any, Protocol, Self
|
|
4
5
|
|
|
6
|
+
from sonolus.script.archetype import AnyArchetype, EntityRef
|
|
5
7
|
from sonolus.script.array import Array
|
|
6
8
|
from sonolus.script.array_like import ArrayLike, get_positive_index
|
|
7
9
|
from sonolus.script.debug import error
|
|
@@ -643,3 +645,255 @@ class _ArrayMapEntryIterator[K, V, Capacity](Record, SonolusIterator):
|
|
|
643
645
|
self._index += 1
|
|
644
646
|
return Some(result)
|
|
645
647
|
return Nothing
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
class _LinkedListNodeRef[TKey, TValue](Protocol):
|
|
651
|
+
def get_value(self) -> TValue: ...
|
|
652
|
+
|
|
653
|
+
def get_next(self) -> Self: ...
|
|
654
|
+
|
|
655
|
+
def set_next(self, next_node: Self): ...
|
|
656
|
+
|
|
657
|
+
def set_prev(self, prev_node: Self):
|
|
658
|
+
# No-op for singly linked lists
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
def is_present(self) -> bool: ...
|
|
662
|
+
|
|
663
|
+
def set(self, other: Self): ...
|
|
664
|
+
|
|
665
|
+
def copy(self) -> Self: ...
|
|
666
|
+
|
|
667
|
+
def empty(self) -> Self: ...
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _merge_linked_list_nodes[TNode: _LinkedListNodeRef](
|
|
671
|
+
a: TNode,
|
|
672
|
+
b: TNode,
|
|
673
|
+
) -> TNode:
|
|
674
|
+
head = a.empty()
|
|
675
|
+
tail = a.empty()
|
|
676
|
+
left = a.copy()
|
|
677
|
+
right = b.copy()
|
|
678
|
+
|
|
679
|
+
while left.is_present() and right.is_present():
|
|
680
|
+
if left.get_value() <= right.get_value():
|
|
681
|
+
if not head.is_present():
|
|
682
|
+
head.set(left)
|
|
683
|
+
tail.set(left)
|
|
684
|
+
else:
|
|
685
|
+
tail.set_next(left)
|
|
686
|
+
tail.set(left)
|
|
687
|
+
left.set(left.get_next())
|
|
688
|
+
else:
|
|
689
|
+
if not head.is_present():
|
|
690
|
+
head.set(right)
|
|
691
|
+
tail.set(right)
|
|
692
|
+
else:
|
|
693
|
+
tail.set_next(right)
|
|
694
|
+
tail.set(right)
|
|
695
|
+
right.set(right.get_next())
|
|
696
|
+
|
|
697
|
+
while left.is_present():
|
|
698
|
+
if not head.is_present():
|
|
699
|
+
head.set(left)
|
|
700
|
+
tail.set(left)
|
|
701
|
+
else:
|
|
702
|
+
tail.set_next(left)
|
|
703
|
+
tail.set(left)
|
|
704
|
+
left.set(left.get_next())
|
|
705
|
+
|
|
706
|
+
while right.is_present():
|
|
707
|
+
if not head.is_present():
|
|
708
|
+
head.set(right)
|
|
709
|
+
tail.set(right)
|
|
710
|
+
else:
|
|
711
|
+
tail.set_next(right)
|
|
712
|
+
tail.set(right)
|
|
713
|
+
right.set(right.get_next())
|
|
714
|
+
|
|
715
|
+
if tail.is_present():
|
|
716
|
+
tail.set_next(a.empty())
|
|
717
|
+
|
|
718
|
+
return head
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _merge_sort_linked_list_nodes[TNode: _LinkedListNodeRef](
|
|
722
|
+
head: TNode,
|
|
723
|
+
) -> TNode:
|
|
724
|
+
# Calculate length
|
|
725
|
+
length = 0
|
|
726
|
+
node = head.copy()
|
|
727
|
+
while node.is_present():
|
|
728
|
+
length += 1
|
|
729
|
+
node.set(node.get_next())
|
|
730
|
+
|
|
731
|
+
# Trivial case
|
|
732
|
+
if length <= 1:
|
|
733
|
+
return head
|
|
734
|
+
|
|
735
|
+
# Bottom-up merge sort: start with sublists of size 1, then 2, 4, 8, etc.
|
|
736
|
+
size = 1
|
|
737
|
+
while size < length:
|
|
738
|
+
current = head.copy()
|
|
739
|
+
new_head = head.empty()
|
|
740
|
+
new_tail = head.empty()
|
|
741
|
+
|
|
742
|
+
# Process all pairs of sublists of the current size
|
|
743
|
+
while current.is_present():
|
|
744
|
+
# Extract the first sublist
|
|
745
|
+
left = current.copy()
|
|
746
|
+
prev = current.empty()
|
|
747
|
+
i = 0
|
|
748
|
+
while i < size and current.is_present():
|
|
749
|
+
prev.set(current)
|
|
750
|
+
current.set(current.get_next())
|
|
751
|
+
i += 1
|
|
752
|
+
if prev.is_present():
|
|
753
|
+
prev.set_next(prev.empty())
|
|
754
|
+
|
|
755
|
+
# We've made it to the end without a second sublist to merge, so just attach it to the end
|
|
756
|
+
if not current.is_present():
|
|
757
|
+
# Since size < length, we know a full iteration must have happened already, so new_tail is valid
|
|
758
|
+
new_tail.set_next(left)
|
|
759
|
+
break
|
|
760
|
+
|
|
761
|
+
# Extract the second sublist
|
|
762
|
+
right = current.copy()
|
|
763
|
+
prev = current.empty()
|
|
764
|
+
i = 0
|
|
765
|
+
while i < size and current.is_present():
|
|
766
|
+
prev.set(current)
|
|
767
|
+
current.set(current.get_next())
|
|
768
|
+
i += 1
|
|
769
|
+
if prev.is_present():
|
|
770
|
+
prev.set_next(prev.empty())
|
|
771
|
+
|
|
772
|
+
merged = _merge_linked_list_nodes(left, right)
|
|
773
|
+
|
|
774
|
+
# Append the merged result
|
|
775
|
+
if not new_head.is_present():
|
|
776
|
+
new_head.set(merged)
|
|
777
|
+
new_tail.set(merged)
|
|
778
|
+
else:
|
|
779
|
+
new_tail.set_next(merged)
|
|
780
|
+
|
|
781
|
+
# Move tail to the end of the merged section
|
|
782
|
+
while new_tail.get_next().is_present():
|
|
783
|
+
new_tail.set(new_tail.get_next())
|
|
784
|
+
|
|
785
|
+
# Update head for the next iteration
|
|
786
|
+
head.set(new_head)
|
|
787
|
+
size *= 2
|
|
788
|
+
|
|
789
|
+
return head
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class _EntityNodeRef[Archetype, GetValue, GetNextRef, GetPrevRef](Record):
|
|
793
|
+
index: int
|
|
794
|
+
|
|
795
|
+
def get_value(self) -> Any:
|
|
796
|
+
return self.get_value_fn(self.archetype.at(self.index))
|
|
797
|
+
|
|
798
|
+
def get_next(self) -> _EntityNodeRef:
|
|
799
|
+
next_ref = self.get_next_ref_fn(self.archetype.at(self.index))
|
|
800
|
+
return self.with_index(next_ref.index)
|
|
801
|
+
|
|
802
|
+
def set_next(self, next_node: _EntityNodeRef):
|
|
803
|
+
entity = self.archetype.at(self.index)
|
|
804
|
+
next_ref = self.get_next_ref_fn(entity)
|
|
805
|
+
next_ref.index = next_node.index
|
|
806
|
+
|
|
807
|
+
def set_prev(self, prev_node: _EntityNodeRef):
|
|
808
|
+
if self.get_prev_ref_fn is not None:
|
|
809
|
+
entity = self.archetype.at(self.index)
|
|
810
|
+
prev_ref = self.get_prev_ref_fn(entity)
|
|
811
|
+
prev_ref.index = prev_node.index
|
|
812
|
+
|
|
813
|
+
def is_present(self) -> bool:
|
|
814
|
+
return self.index > 0
|
|
815
|
+
|
|
816
|
+
def set(self, other: _EntityNodeRef):
|
|
817
|
+
self.index = other.index
|
|
818
|
+
|
|
819
|
+
def copy(self) -> _EntityNodeRef:
|
|
820
|
+
return self.with_index(self.index)
|
|
821
|
+
|
|
822
|
+
def empty(self) -> _EntityNodeRef:
|
|
823
|
+
return self.with_index(0)
|
|
824
|
+
|
|
825
|
+
def with_index(self, index: int) -> _EntityNodeRef:
|
|
826
|
+
return _EntityNodeRef[
|
|
827
|
+
self.archetype,
|
|
828
|
+
self.get_value_fn,
|
|
829
|
+
self.get_next_ref_fn,
|
|
830
|
+
self.get_prev_ref_fn,
|
|
831
|
+
](index)
|
|
832
|
+
|
|
833
|
+
@property
|
|
834
|
+
def archetype(self):
|
|
835
|
+
return self.type_var_value(Archetype)
|
|
836
|
+
|
|
837
|
+
@property
|
|
838
|
+
def get_value_fn(self):
|
|
839
|
+
return self.type_var_value(GetValue)
|
|
840
|
+
|
|
841
|
+
@property
|
|
842
|
+
def get_next_ref_fn(self):
|
|
843
|
+
return self.type_var_value(GetNextRef)
|
|
844
|
+
|
|
845
|
+
@property
|
|
846
|
+
def get_prev_ref_fn(self):
|
|
847
|
+
return self.type_var_value(GetPrevRef)
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def sort_linked_entities[T: AnyArchetype](
|
|
851
|
+
head_ref: EntityRef[T],
|
|
852
|
+
/,
|
|
853
|
+
*,
|
|
854
|
+
get_value: Callable[[T], Any],
|
|
855
|
+
get_next_ref: Callable[[T], EntityRef[T]],
|
|
856
|
+
get_prev_ref: Callable[[T], EntityRef[T]] | None = None,
|
|
857
|
+
) -> EntityRef[T]:
|
|
858
|
+
"""Sort a linked list of entities using merge sort.
|
|
859
|
+
|
|
860
|
+
If get_prev_ref is provided, the backward links will be updated as well.
|
|
861
|
+
|
|
862
|
+
Usage:
|
|
863
|
+
```python
|
|
864
|
+
class MyArchetype(PlayArchetype):
|
|
865
|
+
sort_key: int
|
|
866
|
+
next: EntityRef[MyArchetype]
|
|
867
|
+
|
|
868
|
+
def sort_my_archetype(head: EntityRef[MyArchetype]) -> EntityRef[MyArchetype]:
|
|
869
|
+
return sort_linked_entities(
|
|
870
|
+
head,
|
|
871
|
+
get_value=lambda e: e.sort_key,
|
|
872
|
+
get_next_ref=lambda e: e.next,
|
|
873
|
+
)
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
head_ref: A reference to the head of the linked list.
|
|
878
|
+
get_value: A function that takes an entity and returns the value to sort by.
|
|
879
|
+
get_next_ref: A function that takes an entity and returns a reference to the next entity.
|
|
880
|
+
get_prev_ref: An optional function that takes an entity and returns a reference to the previous entity.
|
|
881
|
+
|
|
882
|
+
Returns:
|
|
883
|
+
A reference to the head of the sorted linked list.
|
|
884
|
+
"""
|
|
885
|
+
archetype = head_ref.archetype()
|
|
886
|
+
|
|
887
|
+
sorted_head_index = _merge_sort_linked_list_nodes(
|
|
888
|
+
_EntityNodeRef[archetype, get_value, get_next_ref, get_prev_ref](head_ref.index)
|
|
889
|
+
).index
|
|
890
|
+
|
|
891
|
+
if get_prev_ref is not None:
|
|
892
|
+
current_ref = _EntityNodeRef[archetype, get_value, get_next_ref, get_prev_ref](sorted_head_index)
|
|
893
|
+
prev_ref = current_ref.empty()
|
|
894
|
+
while current_ref.is_present():
|
|
895
|
+
current_ref.set_prev(prev_ref)
|
|
896
|
+
prev_ref.set(current_ref)
|
|
897
|
+
current_ref.set(current_ref.get_next())
|
|
898
|
+
|
|
899
|
+
return EntityRef[archetype](sorted_head_index)
|
|
@@ -388,6 +388,11 @@ def _super(*args):
|
|
|
388
388
|
return super(*(arg._as_py_() if arg._is_py_() else arg for arg in args))
|
|
389
389
|
|
|
390
390
|
|
|
391
|
+
@meta_fn
|
|
392
|
+
def _type(value):
|
|
393
|
+
return type(value)
|
|
394
|
+
|
|
395
|
+
|
|
391
396
|
@meta_fn
|
|
392
397
|
def _assert_never(arg: Never, /):
|
|
393
398
|
error("Expected code to be unreachable")
|
|
@@ -416,6 +421,7 @@ BUILTIN_IMPLS = {
|
|
|
416
421
|
id(range): Range,
|
|
417
422
|
id(reversed): _reversed,
|
|
418
423
|
id(super): _super,
|
|
424
|
+
id(type): _type,
|
|
419
425
|
id(zip): _zip,
|
|
420
426
|
id(assert_never): _assert_never,
|
|
421
427
|
**MATH_BUILTIN_IMPLS, # Includes round
|
sonolus/script/vec.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from math import pi
|
|
4
|
+
|
|
3
5
|
from sonolus.script.array import Array
|
|
4
6
|
from sonolus.script.array_like import ArrayLike
|
|
5
7
|
from sonolus.script.debug import assert_true
|
|
@@ -285,3 +287,22 @@ def pnpoly(vertices: ArrayLike[Vec2] | tuple[Vec2, ...], test: Vec2) -> bool:
|
|
|
285
287
|
j = i
|
|
286
288
|
i += 1
|
|
287
289
|
return c
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def angle_diff(a: float, b: float, /) -> float:
|
|
293
|
+
"""Return the smallest absolute difference between two angles in radians.
|
|
294
|
+
|
|
295
|
+
The result is in the range [0, π].
|
|
296
|
+
"""
|
|
297
|
+
return abs((a - b + pi) % (2 * pi) - pi)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def signed_angle_diff(a: float, b: float, /) -> float:
|
|
301
|
+
"""Return the signed smallest difference between two angles in radians.
|
|
302
|
+
|
|
303
|
+
The result is in the range [-π, π). A positive result means a is counter-clockwise from b.
|
|
304
|
+
A negative result means a is clockwise from b.
|
|
305
|
+
|
|
306
|
+
If the two angles are exactly opposite, the result will be -π, but this should not be relied upon.
|
|
307
|
+
"""
|
|
308
|
+
return (a - b + pi) % (2 * pi) - pi
|
|
@@ -39,7 +39,7 @@ sonolus/script/archetype.py,sha256=ck_LR8z0ipVq3T9b735VwvQI2mxVUyjHylr4BFagXT8,4
|
|
|
39
39
|
sonolus/script/array.py,sha256=EbrNwl_WuJ0JjjkX0s_VJNXWqvYdm_ljTbyrDEMLGUY,13348
|
|
40
40
|
sonolus/script/array_like.py,sha256=E6S4TW2muXgcyVkhUASQVt7JSYUkpvdJPgHz6YiSHNo,14708
|
|
41
41
|
sonolus/script/bucket.py,sha256=yIod3DgX7Hv7RLe-4Cn81FcydvbkbdMt26FzpRj7oUI,7794
|
|
42
|
-
sonolus/script/containers.py,sha256=
|
|
42
|
+
sonolus/script/containers.py,sha256=SnLXflwlX47EMQmTWnSzm9NYCi-as8lWnKzgCIK0PmM,26940
|
|
43
43
|
sonolus/script/debug.py,sha256=yYg6EZt3NUOUeph1pu_5cA_2lxs8SZ91v76eOC1Sw-8,7747
|
|
44
44
|
sonolus/script/easing.py,sha256=2FUJI_nfp990P_armCcRqHm2329O985glJAhSC6tnxs,11379
|
|
45
45
|
sonolus/script/effect.py,sha256=SfJxSNF3RlPCRXnkt62ZlWhCXw3mmmRCsoMsvTErUP0,7960
|
|
@@ -67,9 +67,9 @@ sonolus/script/timing.py,sha256=DklMvuxcFg3MzXsecUo6Yhdk7pScOJ7STwXvAiTvLKM,3067
|
|
|
67
67
|
sonolus/script/transform.py,sha256=4aS7-NNzX0v9KMXZ4gIGOaU1Cd-ok7DO_OvIBca0mGU,21418
|
|
68
68
|
sonolus/script/ui.py,sha256=DYPGWIjHj1IFPxW1zaEuIUQx0b32FJPXtiwCvrtJ6oo,7528
|
|
69
69
|
sonolus/script/values.py,sha256=6iJG6h4IDlbcK8FH4GENSHOQc7C_7fCGa34wM80qToA,1629
|
|
70
|
-
sonolus/script/vec.py,sha256=
|
|
70
|
+
sonolus/script/vec.py,sha256=LueAHrx5pKouHvLaO1nMbl5Plh3UgPn8J7UdJPHWFCM,8840
|
|
71
71
|
sonolus/script/internal/__init__.py,sha256=T6rzLoiOUaiSQtaHMZ88SNO-ijSjSSv33TKtUwu-Ms8,136
|
|
72
|
-
sonolus/script/internal/builtin_impls.py,sha256=
|
|
72
|
+
sonolus/script/internal/builtin_impls.py,sha256=b4gt54JyFoM9mhtxrhfCR8w2u38h4sJ3bzdOB0usNEs,13445
|
|
73
73
|
sonolus/script/internal/callbacks.py,sha256=vWzJG8uiJoEtsNnbeZPqOHogCwoLpz2D1MnHY2wVV8s,2801
|
|
74
74
|
sonolus/script/internal/constant.py,sha256=3ycbGkDJVUwcrCZ96vLjAoAARgsvaqDM8rJ_YCrLrvo,4289
|
|
75
75
|
sonolus/script/internal/context.py,sha256=56pPjiPy8ZaxY3t5iEufsOMEj6BSy31G-5SoYqS6tPo,19694
|
|
@@ -87,8 +87,8 @@ sonolus/script/internal/simulation_context.py,sha256=LGxLTvxbqBIhoe1R-SfwGajNIDw
|
|
|
87
87
|
sonolus/script/internal/transient.py,sha256=y2AWABqF1aoaP6H4_2u4MMpNioC4OsZQCtPyNI0txqo,1634
|
|
88
88
|
sonolus/script/internal/tuple_impl.py,sha256=DPNdmmRmupU8Ah4_XKq6-PdT336l4nt15_uCJKQGkkk,3587
|
|
89
89
|
sonolus/script/internal/value.py,sha256=OngrCdmY_h6mV2Zgwqhuo4eYFad0kTk6263UAxctZcY,6963
|
|
90
|
-
sonolus_py-0.10.
|
|
91
|
-
sonolus_py-0.10.
|
|
92
|
-
sonolus_py-0.10.
|
|
93
|
-
sonolus_py-0.10.
|
|
94
|
-
sonolus_py-0.10.
|
|
90
|
+
sonolus_py-0.10.7.dist-info/METADATA,sha256=sXdmgTuEdNIRF2McqWR7H1c_l-uolUuow8Nt3-ZhYpY,554
|
|
91
|
+
sonolus_py-0.10.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
92
|
+
sonolus_py-0.10.7.dist-info/entry_points.txt,sha256=oTYspY_b7SA8TptEMTDxh4-Aj-ZVPnYC9f1lqH6s9G4,54
|
|
93
|
+
sonolus_py-0.10.7.dist-info/licenses/LICENSE,sha256=JEKpqVhQYfEc7zg3Mj462sKbKYmO1K7WmvX1qvg9IJk,1067
|
|
94
|
+
sonolus_py-0.10.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|