sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__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 (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
sonolus/script/bucket.py CHANGED
@@ -1,191 +1,253 @@
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
+ """The window for judging the accuracy of a hit.
21
+
22
+ Usage:
23
+ ```
24
+ JudgmentWindow(perfect: Interval, great: Interval, good: Interval)
25
+ ```
26
+ """
27
+
28
+ perfect: Interval
29
+ """Interval for a perfect hit."""
30
+
31
+ great: Interval
32
+ """Interval for a great hit."""
33
+
34
+ good: Interval
35
+ """Interval for a good hit."""
36
+
37
+ def update(
38
+ self,
39
+ perfect: Interval | None = None,
40
+ great: Interval | None = None,
41
+ good: Interval | None = None,
42
+ ):
43
+ """Update the window with the given intervals.
44
+
45
+ Args:
46
+ perfect: The interval for a perfect hit.
47
+ great: The interval for a great hit.
48
+ good: The interval for a good hit.
49
+ """
50
+ if perfect is not None:
51
+ self.perfect = perfect
52
+ if great is not None:
53
+ self.great = great
54
+ if good is not None:
55
+ self.good = good
56
+
57
+ def judge(self, actual: float, target: float) -> Judgment:
58
+ """Judge the accuracy of a hit.
59
+
60
+ Args:
61
+ actual: The actual time of the hit.
62
+ target: The target time of the hit.
63
+
64
+ Returns:
65
+ The judgment of the hit.
66
+ """
67
+ return _judge(
68
+ actual,
69
+ target,
70
+ *self.perfect.tuple,
71
+ *self.great.tuple,
72
+ *self.good.tuple,
73
+ )
74
+
75
+ def __mul__(self, other: float | int) -> JudgmentWindow:
76
+ """Multiply the intervals by a scalar."""
77
+ return JudgmentWindow(
78
+ self.perfect * other,
79
+ self.great * other,
80
+ self.good * other,
81
+ )
82
+
83
+
84
+ class Judgment(IntEnum):
85
+ """The judgment of a hit."""
86
+
87
+ MISS = 0
88
+ PERFECT = 1
89
+ GREAT = 2
90
+ GOOD = 3
91
+
92
+
93
+ @native_function(Op.Judge)
94
+ def _judge(
95
+ actual: float,
96
+ target: float,
97
+ perfect_min: float,
98
+ perfect_max: float,
99
+ great_min: float,
100
+ great_max: float,
101
+ good_min: float,
102
+ good_max: float,
103
+ ) -> Judgment:
104
+ diff = actual - target
105
+ if perfect_min <= diff <= perfect_max:
106
+ return Judgment.PERFECT
107
+ if great_min <= diff <= great_max:
108
+ return Judgment.GREAT
109
+ if good_min <= diff <= good_max:
110
+ return Judgment.GOOD
111
+ return Judgment.MISS
112
+
113
+
114
+ class Bucket(Record):
115
+ """A bucket for entity judgment results.
116
+
117
+ Usage:
118
+ ```Bucket(id: int)```
119
+ """
120
+
121
+ id: int
122
+ """Bucket ID."""
123
+
124
+ @property
125
+ @meta_fn
126
+ def window(self) -> JudgmentWindow:
127
+ """The judgment window of the bucket."""
128
+ if not ctx():
129
+ raise RuntimeError("Bucket window access outside of compilation")
130
+ match ctx().global_state.mode:
131
+ case Mode.PLAY:
132
+ return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
133
+ case Mode.WATCH:
134
+ return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
135
+ case _:
136
+ raise RuntimeError("Invalid mode for bucket window access")
137
+
138
+ @window.setter
139
+ @meta_fn
140
+ def window(self, value: JudgmentWindow):
141
+ if not ctx():
142
+ raise RuntimeError("Bucket window access outside of compilation")
143
+ self.window.update(value.perfect, value.great, value.good)
144
+
145
+
146
+ @dataclass
147
+ class _BucketSprite:
148
+ id: int
149
+ fallback_id: int | None
150
+ x: int
151
+ y: int
152
+ w: int
153
+ h: int
154
+ rotation: float
155
+
156
+ def to_dict(self):
157
+ results = {
158
+ "id": self.id,
159
+ "x": self.x,
160
+ "y": self.y,
161
+ "w": self.w,
162
+ "h": self.h,
163
+ "rotation": self.rotation,
164
+ }
165
+ if self.fallback_id is not None:
166
+ results["fallbackId"] = self.fallback_id
167
+ return results
168
+
169
+
170
+ @dataclass
171
+ class _BucketInfo:
172
+ sprites: list[_BucketSprite]
173
+ unit: str | None = None
174
+
175
+ def to_dict(self):
176
+ results = {
177
+ "sprites": [sprite.to_dict() for sprite in self.sprites],
178
+ }
179
+ if self.unit is not None:
180
+ results["unit"] = self.unit
181
+ return results
182
+
183
+
184
+ def bucket_sprite(
185
+ *,
186
+ sprite: Sprite,
187
+ fallback_sprite: Sprite | None = None,
188
+ x: int,
189
+ y: int,
190
+ w: int,
191
+ h: int,
192
+ rotation: float = 0,
193
+ ) -> _BucketSprite:
194
+ """Define a sprite for a bucket."""
195
+ return _BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
196
+
197
+
198
+ def bucket(*, sprites: list[_BucketSprite], unit: str | None = None) -> Any:
199
+ """Define a bucket with the given sprites and unit."""
200
+ return _BucketInfo(sprites, unit)
201
+
202
+
203
+ type Buckets = NewType("Buckets", Any)
204
+
205
+
206
+ @dataclass_transform()
207
+ def buckets[T](cls: type[T]) -> T | Buckets:
208
+ """Decorator to define a buckets class.
209
+
210
+ Usage:
211
+ ```python
212
+ @buckets
213
+ class Buckets:
214
+ note: Bucket = bucket(
215
+ sprites=[
216
+ bucket_sprite(
217
+ sprite=Skin.note,
218
+ x=0,
219
+ y=0,
220
+ w=2,
221
+ h=2,
222
+ )
223
+ ],
224
+ unit=StandardText.MILLISECOND_UNIT,
225
+ )
226
+ ```
227
+ """
228
+ if len(cls.__bases__) != 1:
229
+ raise ValueError("Buckets class must not inherit from any class (except object)")
230
+ instance = cls()
231
+ bucket_info = []
232
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
233
+ if get_origin(annotation) is not Annotated:
234
+ raise TypeError(f"Invalid annotation for buckets: {annotation}")
235
+ annotation_type = annotation.__args__[0]
236
+ annotation_values = annotation.__metadata__
237
+ if annotation_type is not Bucket:
238
+ raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
239
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], _BucketInfo):
240
+ raise TypeError(
241
+ f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
242
+ )
243
+ info = annotation_values[0]
244
+ bucket_info.append(info)
245
+ setattr(instance, name, Bucket(i))
246
+ instance._buckets_ = bucket_info
247
+ instance._is_comptime_value_ = True
248
+ return instance
249
+
250
+
251
+ @buckets
252
+ class EmptyBuckets:
253
+ pass