sonolus.py 0.1.2__py3-none-any.whl → 0.1.4__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.

Files changed (72) hide show
  1. sonolus/backend/allocate.py +125 -51
  2. sonolus/backend/blocks.py +756 -756
  3. sonolus/backend/coalesce.py +85 -0
  4. sonolus/backend/constant_evaluation.py +374 -0
  5. sonolus/backend/dead_code.py +80 -0
  6. sonolus/backend/dominance.py +111 -0
  7. sonolus/backend/excepthook.py +37 -37
  8. sonolus/backend/finalize.py +69 -69
  9. sonolus/backend/flow.py +121 -92
  10. sonolus/backend/inlining.py +150 -0
  11. sonolus/backend/ir.py +5 -3
  12. sonolus/backend/liveness.py +173 -0
  13. sonolus/backend/mode.py +24 -24
  14. sonolus/backend/node.py +40 -40
  15. sonolus/backend/ops.py +197 -197
  16. sonolus/backend/optimize.py +37 -9
  17. sonolus/backend/passes.py +52 -6
  18. sonolus/backend/simplify.py +47 -30
  19. sonolus/backend/ssa.py +187 -0
  20. sonolus/backend/utils.py +48 -48
  21. sonolus/backend/visitor.py +892 -880
  22. sonolus/build/cli.py +7 -1
  23. sonolus/build/compile.py +88 -90
  24. sonolus/build/engine.py +55 -5
  25. sonolus/build/level.py +24 -23
  26. sonolus/build/node.py +43 -43
  27. sonolus/script/archetype.py +23 -6
  28. sonolus/script/array.py +2 -2
  29. sonolus/script/bucket.py +191 -191
  30. sonolus/script/callbacks.py +127 -115
  31. sonolus/script/comptime.py +1 -1
  32. sonolus/script/containers.py +23 -0
  33. sonolus/script/debug.py +19 -3
  34. sonolus/script/easing.py +323 -0
  35. sonolus/script/effect.py +131 -131
  36. sonolus/script/engine.py +37 -1
  37. sonolus/script/globals.py +269 -269
  38. sonolus/script/graphics.py +200 -150
  39. sonolus/script/instruction.py +151 -0
  40. sonolus/script/internal/__init__.py +5 -5
  41. sonolus/script/internal/builtin_impls.py +144 -144
  42. sonolus/script/internal/context.py +12 -4
  43. sonolus/script/internal/descriptor.py +17 -17
  44. sonolus/script/internal/introspection.py +14 -14
  45. sonolus/script/internal/native.py +40 -38
  46. sonolus/script/internal/value.py +3 -3
  47. sonolus/script/interval.py +120 -112
  48. sonolus/script/iterator.py +214 -211
  49. sonolus/script/math.py +30 -1
  50. sonolus/script/num.py +1 -1
  51. sonolus/script/options.py +191 -191
  52. sonolus/script/particle.py +157 -157
  53. sonolus/script/pointer.py +30 -30
  54. sonolus/script/{preview.py → print.py} +81 -81
  55. sonolus/script/random.py +14 -0
  56. sonolus/script/range.py +58 -58
  57. sonolus/script/record.py +3 -3
  58. sonolus/script/runtime.py +45 -6
  59. sonolus/script/sprite.py +333 -333
  60. sonolus/script/text.py +407 -407
  61. sonolus/script/timing.py +42 -42
  62. sonolus/script/transform.py +77 -23
  63. sonolus/script/ui.py +160 -160
  64. sonolus/script/vec.py +81 -72
  65. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/METADATA +1 -2
  66. sonolus_py-0.1.4.dist-info/RECORD +84 -0
  67. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/WHEEL +1 -1
  68. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/licenses/LICENSE +21 -21
  69. sonolus/build/defaults.py +0 -32
  70. sonolus/script/icon.py +0 -73
  71. sonolus_py-0.1.2.dist-info/RECORD +0 -76
  72. {sonolus_py-0.1.2.dist-info → sonolus_py-0.1.4.dist-info}/entry_points.txt +0 -0
sonolus/script/bucket.py CHANGED
@@ -1,191 +1,191 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from enum import IntEnum
5
- from typing import Annotated, Any, NewType, dataclass_transform, get_origin
6
-
7
- from sonolus.backend.mode import Mode
8
- from sonolus.backend.ops import Op
9
- from sonolus.script.internal.context import ctx
10
- from sonolus.script.internal.impl import meta_fn
11
- from sonolus.script.internal.introspection import get_field_specifiers
12
- from sonolus.script.internal.native import native_function
13
- from sonolus.script.interval import Interval
14
- from sonolus.script.pointer import deref
15
- from sonolus.script.record import Record
16
- from sonolus.script.sprite import Sprite
17
-
18
-
19
- class JudgmentWindow(Record):
20
- perfect: Interval
21
- great: Interval
22
- good: Interval
23
-
24
- def update(
25
- self,
26
- perfect: Interval | None = None,
27
- great: Interval | None = None,
28
- good: Interval | None = None,
29
- ):
30
- if perfect is not None:
31
- self.perfect = perfect
32
- if great is not None:
33
- self.great = great
34
- if good is not None:
35
- self.good = good
36
-
37
- def judge(self, actual: float, target: float) -> Judgment:
38
- return _judge(
39
- actual,
40
- target,
41
- *self.perfect.tuple,
42
- *self.great.tuple,
43
- *self.good.tuple,
44
- )
45
-
46
- def __mul__(self, other: float | int) -> JudgmentWindow:
47
- return JudgmentWindow(
48
- self.perfect * other,
49
- self.great * other,
50
- self.good * other,
51
- )
52
-
53
-
54
- class Judgment(IntEnum):
55
- MISS = 0
56
- PERFECT = 1
57
- GREAT = 2
58
- GOOD = 3
59
-
60
-
61
- @native_function(Op.Judge)
62
- def _judge(
63
- actual: float,
64
- target: float,
65
- perfect_min: float,
66
- perfect_max: float,
67
- great_min: float,
68
- great_max: float,
69
- good_min: float,
70
- good_max: float,
71
- ) -> Judgment:
72
- diff = actual - target
73
- if perfect_min <= diff <= perfect_max:
74
- return Judgment.PERFECT
75
- if great_min <= diff <= great_max:
76
- return Judgment.GREAT
77
- if good_min <= diff <= good_max:
78
- return Judgment.GOOD
79
- return Judgment.MISS
80
-
81
-
82
- class Bucket(Record):
83
- id: int
84
-
85
- @property
86
- @meta_fn
87
- def window(self) -> JudgmentWindow:
88
- if not ctx():
89
- raise RuntimeError("Bucket window access outside of compilation")
90
- match ctx().global_state.mode:
91
- case Mode.PLAY:
92
- return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
93
- case Mode.WATCH:
94
- return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
95
- case _:
96
- raise RuntimeError("Invalid mode for bucket window access")
97
-
98
- @window.setter
99
- @meta_fn
100
- def window(self, value: JudgmentWindow):
101
- if not ctx():
102
- raise RuntimeError("Bucket window access outside of compilation")
103
- self.window.update(value.perfect, value.great, value.good)
104
-
105
-
106
- @dataclass
107
- class BucketSprite:
108
- id: int
109
- fallback_id: int | None
110
- x: int
111
- y: int
112
- w: int
113
- h: int
114
- rotation: float
115
-
116
- def to_dict(self):
117
- results = {
118
- "id": self.id,
119
- "x": self.x,
120
- "y": self.y,
121
- "w": self.w,
122
- "h": self.h,
123
- "rotation": self.rotation,
124
- }
125
- if self.fallback_id is not None:
126
- results["fallbackId"] = self.fallback_id
127
- return results
128
-
129
-
130
- @dataclass
131
- class BucketInfo:
132
- sprites: list[BucketSprite]
133
- unit: str | None = None
134
-
135
- def to_dict(self):
136
- results = {
137
- "sprites": [sprite.to_dict() for sprite in self.sprites],
138
- }
139
- if self.unit is not None:
140
- results["unit"] = self.unit
141
- return results
142
-
143
-
144
- def bucket_sprite(
145
- *,
146
- sprite: Sprite,
147
- fallback_sprite: Sprite | None = None,
148
- x: int,
149
- y: int,
150
- w: int,
151
- h: int,
152
- rotation: float = 0,
153
- ) -> BucketSprite:
154
- return BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
155
-
156
-
157
- def bucket(*, sprites: list[BucketSprite], unit: str | None = None) -> Any:
158
- return BucketInfo(sprites, unit)
159
-
160
-
161
- type Buckets = NewType("Buckets", Any)
162
-
163
-
164
- @dataclass_transform()
165
- def buckets[T](cls: type[T]) -> T | Buckets:
166
- if len(cls.__bases__) != 1:
167
- raise ValueError("Buckets class must not inherit from any class (except object)")
168
- instance = cls()
169
- bucket_info = []
170
- for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
171
- if get_origin(annotation) is not Annotated:
172
- raise TypeError(f"Invalid annotation for buckets: {annotation}")
173
- annotation_type = annotation.__args__[0]
174
- annotation_values = annotation.__metadata__
175
- if annotation_type is not Bucket:
176
- raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
177
- if len(annotation_values) != 1 or not isinstance(annotation_values[0], BucketInfo):
178
- raise TypeError(
179
- f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
180
- )
181
- info = annotation_values[0]
182
- bucket_info.append(info)
183
- setattr(instance, name, Bucket(i))
184
- instance._buckets_ = bucket_info
185
- instance._is_comptime_value_ = True
186
- return instance
187
-
188
-
189
- @buckets
190
- class EmptyBuckets:
191
- pass
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import IntEnum
5
+ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
6
+
7
+ from sonolus.backend.mode import Mode
8
+ from sonolus.backend.ops import Op
9
+ from sonolus.script.internal.context import ctx
10
+ from sonolus.script.internal.impl import meta_fn
11
+ from sonolus.script.internal.introspection import get_field_specifiers
12
+ from sonolus.script.internal.native import native_function
13
+ from sonolus.script.interval import Interval
14
+ from sonolus.script.pointer import deref
15
+ from sonolus.script.record import Record
16
+ from sonolus.script.sprite import Sprite
17
+
18
+
19
+ class JudgmentWindow(Record):
20
+ perfect: Interval
21
+ great: Interval
22
+ good: Interval
23
+
24
+ def update(
25
+ self,
26
+ perfect: Interval | None = None,
27
+ great: Interval | None = None,
28
+ good: Interval | None = None,
29
+ ):
30
+ if perfect is not None:
31
+ self.perfect = perfect
32
+ if great is not None:
33
+ self.great = great
34
+ if good is not None:
35
+ self.good = good
36
+
37
+ def judge(self, actual: float, target: float) -> Judgment:
38
+ return _judge(
39
+ actual,
40
+ target,
41
+ *self.perfect.tuple,
42
+ *self.great.tuple,
43
+ *self.good.tuple,
44
+ )
45
+
46
+ def __mul__(self, other: float | int) -> JudgmentWindow:
47
+ return JudgmentWindow(
48
+ self.perfect * other,
49
+ self.great * other,
50
+ self.good * other,
51
+ )
52
+
53
+
54
+ class Judgment(IntEnum):
55
+ MISS = 0
56
+ PERFECT = 1
57
+ GREAT = 2
58
+ GOOD = 3
59
+
60
+
61
+ @native_function(Op.Judge)
62
+ def _judge(
63
+ actual: float,
64
+ target: float,
65
+ perfect_min: float,
66
+ perfect_max: float,
67
+ great_min: float,
68
+ great_max: float,
69
+ good_min: float,
70
+ good_max: float,
71
+ ) -> Judgment:
72
+ diff = actual - target
73
+ if perfect_min <= diff <= perfect_max:
74
+ return Judgment.PERFECT
75
+ if great_min <= diff <= great_max:
76
+ return Judgment.GREAT
77
+ if good_min <= diff <= good_max:
78
+ return Judgment.GOOD
79
+ return Judgment.MISS
80
+
81
+
82
+ class Bucket(Record):
83
+ id: int
84
+
85
+ @property
86
+ @meta_fn
87
+ def window(self) -> JudgmentWindow:
88
+ if not ctx():
89
+ raise RuntimeError("Bucket window access outside of compilation")
90
+ match ctx().global_state.mode:
91
+ case Mode.PLAY:
92
+ return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
93
+ case Mode.WATCH:
94
+ return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
95
+ case _:
96
+ raise RuntimeError("Invalid mode for bucket window access")
97
+
98
+ @window.setter
99
+ @meta_fn
100
+ def window(self, value: JudgmentWindow):
101
+ if not ctx():
102
+ raise RuntimeError("Bucket window access outside of compilation")
103
+ self.window.update(value.perfect, value.great, value.good)
104
+
105
+
106
+ @dataclass
107
+ class BucketSprite:
108
+ id: int
109
+ fallback_id: int | None
110
+ x: int
111
+ y: int
112
+ w: int
113
+ h: int
114
+ rotation: float
115
+
116
+ def to_dict(self):
117
+ results = {
118
+ "id": self.id,
119
+ "x": self.x,
120
+ "y": self.y,
121
+ "w": self.w,
122
+ "h": self.h,
123
+ "rotation": self.rotation,
124
+ }
125
+ if self.fallback_id is not None:
126
+ results["fallbackId"] = self.fallback_id
127
+ return results
128
+
129
+
130
+ @dataclass
131
+ class BucketInfo:
132
+ sprites: list[BucketSprite]
133
+ unit: str | None = None
134
+
135
+ def to_dict(self):
136
+ results = {
137
+ "sprites": [sprite.to_dict() for sprite in self.sprites],
138
+ }
139
+ if self.unit is not None:
140
+ results["unit"] = self.unit
141
+ return results
142
+
143
+
144
+ def bucket_sprite(
145
+ *,
146
+ sprite: Sprite,
147
+ fallback_sprite: Sprite | None = None,
148
+ x: int,
149
+ y: int,
150
+ w: int,
151
+ h: int,
152
+ rotation: float = 0,
153
+ ) -> BucketSprite:
154
+ return BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
155
+
156
+
157
+ def bucket(*, sprites: list[BucketSprite], unit: str | None = None) -> Any:
158
+ return BucketInfo(sprites, unit)
159
+
160
+
161
+ type Buckets = NewType("Buckets", Any)
162
+
163
+
164
+ @dataclass_transform()
165
+ def buckets[T](cls: type[T]) -> T | Buckets:
166
+ if len(cls.__bases__) != 1:
167
+ raise ValueError("Buckets class must not inherit from any class (except object)")
168
+ instance = cls()
169
+ bucket_info = []
170
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
171
+ if get_origin(annotation) is not Annotated:
172
+ raise TypeError(f"Invalid annotation for buckets: {annotation}")
173
+ annotation_type = annotation.__args__[0]
174
+ annotation_values = annotation.__metadata__
175
+ if annotation_type is not Bucket:
176
+ raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
177
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], BucketInfo):
178
+ raise TypeError(
179
+ f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
180
+ )
181
+ info = annotation_values[0]
182
+ bucket_info.append(info)
183
+ setattr(instance, name, Bucket(i))
184
+ instance._buckets_ = bucket_info
185
+ instance._is_comptime_value_ = True
186
+ return instance
187
+
188
+
189
+ @buckets
190
+ class EmptyBuckets:
191
+ pass
@@ -1,115 +1,127 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass
5
- class CallbackInfo:
6
- name: str
7
- py_name: str
8
- supports_order: bool
9
- returns_value: bool
10
-
11
-
12
- preprocess_callback = CallbackInfo(
13
- name="preprocess",
14
- py_name="preprocess",
15
- supports_order=True,
16
- returns_value=False,
17
- )
18
- spawn_order_callback = CallbackInfo(
19
- name="spawnOrder",
20
- py_name="spawn_order",
21
- supports_order=True,
22
- returns_value=True,
23
- )
24
- should_spawn_callback = CallbackInfo(
25
- name="shouldSpawn",
26
- py_name="should_spawn",
27
- supports_order=False,
28
- returns_value=True,
29
- )
30
- initialize_callback = CallbackInfo(
31
- name="initialize",
32
- py_name="initialize",
33
- supports_order=False,
34
- returns_value=False,
35
- )
36
- update_sequential_callback = CallbackInfo(
37
- name="updateSequential",
38
- py_name="update_sequential",
39
- supports_order=True,
40
- returns_value=False,
41
- )
42
- touch_callback = CallbackInfo(
43
- name="touch",
44
- py_name="touch",
45
- supports_order=True,
46
- returns_value=False,
47
- )
48
- update_parallel_callback = CallbackInfo(
49
- name="updateParallel",
50
- py_name="update_parallel",
51
- supports_order=False,
52
- returns_value=False,
53
- )
54
- terminate_callback = CallbackInfo(
55
- name="terminate",
56
- py_name="terminate",
57
- supports_order=False,
58
- returns_value=False,
59
- )
60
- spawn_time_callback = CallbackInfo(
61
- name="spawnTime",
62
- py_name="spawn_time",
63
- supports_order=False,
64
- returns_value=True,
65
- )
66
- despawn_time_callback = CallbackInfo(
67
- name="despawnTime",
68
- py_name="despawn_time",
69
- supports_order=False,
70
- returns_value=True,
71
- )
72
- update_spawn_callback = CallbackInfo(
73
- name="updateSpawn",
74
- py_name="update_spawn",
75
- supports_order=False,
76
- returns_value=True,
77
- )
78
- render_callback = CallbackInfo(
79
- name="render",
80
- py_name="render",
81
- supports_order=False,
82
- returns_value=False,
83
- )
84
-
85
-
86
- def _by_name(*callbacks: CallbackInfo) -> dict[str, CallbackInfo]:
87
- return {cb.py_name: cb for cb in callbacks}
88
-
89
-
90
- PLAY_CALLBACKS = _by_name(
91
- preprocess_callback,
92
- spawn_order_callback,
93
- should_spawn_callback,
94
- initialize_callback,
95
- update_sequential_callback,
96
- touch_callback,
97
- update_parallel_callback,
98
- terminate_callback,
99
- )
100
- WATCH_ARCHETYPE_CALLBACKS = _by_name(
101
- preprocess_callback,
102
- spawn_time_callback,
103
- despawn_time_callback,
104
- initialize_callback,
105
- update_sequential_callback,
106
- update_parallel_callback,
107
- terminate_callback,
108
- )
109
- WATCH_GLOBAL_CALLBACKS = _by_name(
110
- update_spawn_callback,
111
- )
112
- PREVIEW_CALLBACKS = _by_name(
113
- preprocess_callback,
114
- render_callback,
115
- )
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class CallbackInfo:
6
+ name: str
7
+ py_name: str
8
+ supports_order: bool
9
+ returns_value: bool
10
+
11
+
12
+ preprocess_callback = CallbackInfo(
13
+ name="preprocess",
14
+ py_name="preprocess",
15
+ supports_order=True,
16
+ returns_value=False,
17
+ )
18
+ spawn_order_callback = CallbackInfo(
19
+ name="spawnOrder",
20
+ py_name="spawn_order",
21
+ supports_order=True,
22
+ returns_value=True,
23
+ )
24
+ should_spawn_callback = CallbackInfo(
25
+ name="shouldSpawn",
26
+ py_name="should_spawn",
27
+ supports_order=False,
28
+ returns_value=True,
29
+ )
30
+ initialize_callback = CallbackInfo(
31
+ name="initialize",
32
+ py_name="initialize",
33
+ supports_order=False,
34
+ returns_value=False,
35
+ )
36
+ update_sequential_callback = CallbackInfo(
37
+ name="updateSequential",
38
+ py_name="update_sequential",
39
+ supports_order=True,
40
+ returns_value=False,
41
+ )
42
+ touch_callback = CallbackInfo(
43
+ name="touch",
44
+ py_name="touch",
45
+ supports_order=True,
46
+ returns_value=False,
47
+ )
48
+ update_parallel_callback = CallbackInfo(
49
+ name="updateParallel",
50
+ py_name="update_parallel",
51
+ supports_order=False,
52
+ returns_value=False,
53
+ )
54
+ terminate_callback = CallbackInfo(
55
+ name="terminate",
56
+ py_name="terminate",
57
+ supports_order=False,
58
+ returns_value=False,
59
+ )
60
+ spawn_time_callback = CallbackInfo(
61
+ name="spawnTime",
62
+ py_name="spawn_time",
63
+ supports_order=False,
64
+ returns_value=True,
65
+ )
66
+ despawn_time_callback = CallbackInfo(
67
+ name="despawnTime",
68
+ py_name="despawn_time",
69
+ supports_order=False,
70
+ returns_value=True,
71
+ )
72
+ update_spawn_callback = CallbackInfo(
73
+ name="updateSpawn",
74
+ py_name="update_spawn",
75
+ supports_order=False,
76
+ returns_value=True,
77
+ )
78
+ render_callback = CallbackInfo(
79
+ name="render",
80
+ py_name="render",
81
+ supports_order=False,
82
+ returns_value=False,
83
+ )
84
+ navigate_callback = CallbackInfo(
85
+ name="navigate",
86
+ py_name="navigate",
87
+ supports_order=False,
88
+ returns_value=False,
89
+ )
90
+ update_callback = CallbackInfo(
91
+ name="update",
92
+ py_name="update",
93
+ supports_order=False,
94
+ returns_value=False,
95
+ )
96
+
97
+
98
+ def _by_name(*callbacks: CallbackInfo) -> dict[str, CallbackInfo]:
99
+ return {cb.py_name: cb for cb in callbacks}
100
+
101
+
102
+ PLAY_CALLBACKS = _by_name(
103
+ preprocess_callback,
104
+ spawn_order_callback,
105
+ should_spawn_callback,
106
+ initialize_callback,
107
+ update_sequential_callback,
108
+ touch_callback,
109
+ update_parallel_callback,
110
+ terminate_callback,
111
+ )
112
+ WATCH_ARCHETYPE_CALLBACKS = _by_name(
113
+ preprocess_callback,
114
+ spawn_time_callback,
115
+ despawn_time_callback,
116
+ initialize_callback,
117
+ update_sequential_callback,
118
+ update_parallel_callback,
119
+ terminate_callback,
120
+ )
121
+ WATCH_GLOBAL_CALLBACKS = _by_name(
122
+ update_spawn_callback,
123
+ )
124
+ PREVIEW_CALLBACKS = _by_name(
125
+ preprocess_callback,
126
+ render_callback,
127
+ )
@@ -69,7 +69,7 @@ class _Comptime[T, V](GenericValue):
69
69
  def _from_list_(cls, values: Iterable[float | BlockPlace]) -> Self:
70
70
  return cls._instance
71
71
 
72
- def _to_list_(self) -> list[float | BlockPlace]:
72
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
73
73
  return []
74
74
 
75
75
  @classmethod
@@ -12,6 +12,26 @@ class Pair[T, U](Record):
12
12
  first: T
13
13
  second: U
14
14
 
15
+ def __lt__(self, other):
16
+ if self.first == other.first:
17
+ return self.second < other.second
18
+ return self.first < other.first
19
+
20
+ def __le__(self, other):
21
+ if self.first == other.first:
22
+ return self.second <= other.second
23
+ return self.first <= other.first
24
+
25
+ def __gt__(self, other):
26
+ if self.first == other.first:
27
+ return self.second > other.second
28
+ return self.first > other.first
29
+
30
+ def __ge__(self, other):
31
+ if self.first == other.first:
32
+ return self.second >= other.second
33
+ return self.first >= other.first
34
+
15
35
 
16
36
  class VarArray[T, Capacity](Record, ArrayLike[T]):
17
37
  _size: int
@@ -170,6 +190,9 @@ class ArrayMap[K, V, Capacity](Record):
170
190
  def items(self) -> SonolusIterator[tuple[K, V]]:
171
191
  return ArrayMapEntryIterator(self, 0)
172
192
 
193
+ def __iter__(self):
194
+ return self.keys()
195
+
173
196
  def __getitem__(self, key: K) -> V:
174
197
  for i in Range(self._size):
175
198
  entry = self._array[i]