sonolus.py 0.8.0__py3-none-any.whl → 0.9.1__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/build/engine.py CHANGED
@@ -9,7 +9,7 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
 
11
11
  from sonolus.backend.mode import Mode
12
- from sonolus.build.compile import compile_mode
12
+ from sonolus.build.compile import CompileCache, compile_mode
13
13
  from sonolus.script.archetype import _BaseArchetype
14
14
  from sonolus.script.bucket import Buckets
15
15
  from sonolus.script.effect import Effects
@@ -68,7 +68,11 @@ def no_gil() -> bool:
68
68
  def package_engine(
69
69
  engine: EngineData,
70
70
  config: BuildConfig | None = None,
71
+ cache: CompileCache | None = None,
71
72
  ):
73
+ if cache is None:
74
+ cache = CompileCache()
75
+
72
76
  config = config or BuildConfig()
73
77
  rom = ReadOnlyMemory()
74
78
  configuration = build_engine_configuration(engine.options, engine.ui)
@@ -76,7 +80,8 @@ def package_engine(
76
80
  # process_cpu_count is available in Python 3.13+
77
81
  from os import process_cpu_count
78
82
 
79
- thread_pool = ThreadPoolExecutor(process_cpu_count() or 1)
83
+ # Need a worker for each mode (so +4) plus at least one to do work
84
+ thread_pool = ThreadPoolExecutor(4 + max(1, min(8, (process_cpu_count() or 1))))
80
85
  else:
81
86
  thread_pool = None
82
87
 
@@ -97,6 +102,7 @@ def package_engine(
97
102
  rom=rom,
98
103
  config=config,
99
104
  thread_pool=thread_pool,
105
+ cache=cache,
100
106
  ),
101
107
  "watch": thread_pool.submit(
102
108
  build_watch_mode,
@@ -109,6 +115,7 @@ def package_engine(
109
115
  update_spawn=watch_mode.update_spawn,
110
116
  config=config,
111
117
  thread_pool=thread_pool,
118
+ cache=cache,
112
119
  ),
113
120
  "preview": thread_pool.submit(
114
121
  build_preview_mode,
@@ -117,6 +124,7 @@ def package_engine(
117
124
  rom=rom,
118
125
  config=config,
119
126
  thread_pool=thread_pool,
127
+ cache=cache,
120
128
  ),
121
129
  "tutorial": thread_pool.submit(
122
130
  build_tutorial_mode,
@@ -131,6 +139,7 @@ def package_engine(
131
139
  rom=rom,
132
140
  config=config,
133
141
  thread_pool=thread_pool,
142
+ cache=cache,
134
143
  ),
135
144
  }
136
145
 
@@ -148,6 +157,7 @@ def package_engine(
148
157
  rom=rom,
149
158
  config=config,
150
159
  thread_pool=None,
160
+ cache=cache,
151
161
  )
152
162
  watch_data = build_watch_mode(
153
163
  archetypes=watch_mode.archetypes,
@@ -159,6 +169,7 @@ def package_engine(
159
169
  update_spawn=watch_mode.update_spawn,
160
170
  config=config,
161
171
  thread_pool=None,
172
+ cache=cache,
162
173
  )
163
174
  preview_data = build_preview_mode(
164
175
  archetypes=preview_mode.archetypes,
@@ -166,6 +177,7 @@ def package_engine(
166
177
  rom=rom,
167
178
  config=config,
168
179
  thread_pool=None,
180
+ cache=cache,
169
181
  )
170
182
  tutorial_data = build_tutorial_mode(
171
183
  skin=tutorial_mode.skin,
@@ -179,6 +191,7 @@ def package_engine(
179
191
  rom=rom,
180
192
  config=config,
181
193
  thread_pool=None,
194
+ cache=cache,
182
195
  )
183
196
 
184
197
  return PackagedEngine(
@@ -270,6 +283,7 @@ def build_play_mode(
270
283
  config: BuildConfig,
271
284
  thread_pool: Executor | None = None,
272
285
  validate_only: bool = False,
286
+ cache: CompileCache | None = None,
273
287
  ):
274
288
  return {
275
289
  **compile_mode(
@@ -280,6 +294,7 @@ def build_play_mode(
280
294
  passes=config.passes,
281
295
  thread_pool=thread_pool,
282
296
  validate_only=validate_only,
297
+ cache=cache,
283
298
  ),
284
299
  "skin": build_skin(skin),
285
300
  "effect": build_effects(effects),
@@ -299,6 +314,7 @@ def build_watch_mode(
299
314
  config: BuildConfig,
300
315
  thread_pool: Executor | None = None,
301
316
  validate_only: bool = False,
317
+ cache: CompileCache | None = None,
302
318
  ):
303
319
  return {
304
320
  **compile_mode(
@@ -309,6 +325,7 @@ def build_watch_mode(
309
325
  passes=config.passes,
310
326
  thread_pool=thread_pool,
311
327
  validate_only=validate_only,
328
+ cache=cache,
312
329
  ),
313
330
  "skin": build_skin(skin),
314
331
  "effect": build_effects(effects),
@@ -324,6 +341,7 @@ def build_preview_mode(
324
341
  config: BuildConfig,
325
342
  thread_pool: Executor | None = None,
326
343
  validate_only: bool = False,
344
+ cache: CompileCache | None = None,
327
345
  ):
328
346
  return {
329
347
  **compile_mode(
@@ -334,6 +352,7 @@ def build_preview_mode(
334
352
  passes=config.passes,
335
353
  thread_pool=thread_pool,
336
354
  validate_only=validate_only,
355
+ cache=cache,
337
356
  ),
338
357
  "skin": build_skin(skin),
339
358
  }
@@ -352,6 +371,7 @@ def build_tutorial_mode(
352
371
  config: BuildConfig,
353
372
  thread_pool: Executor | None = None,
354
373
  validate_only: bool = False,
374
+ cache: CompileCache | None = None,
355
375
  ):
356
376
  return {
357
377
  **compile_mode(
@@ -366,6 +386,7 @@ def build_tutorial_mode(
366
386
  passes=config.passes,
367
387
  thread_pool=thread_pool,
368
388
  validate_only=validate_only,
389
+ cache=cache,
369
390
  ),
370
391
  "skin": build_skin(skin),
371
392
  "effect": build_effects(effects),
sonolus/build/project.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import cast
4
4
 
5
5
  from sonolus.build.collection import Asset, Collection, Srl
6
+ from sonolus.build.compile import CompileCache
6
7
  from sonolus.build.engine import package_engine, unpackage_data
7
8
  from sonolus.build.level import package_level_data
8
9
  from sonolus.script.engine import Engine
@@ -19,7 +20,9 @@ BLANK_AUDIO = (
19
20
  )
20
21
 
21
22
 
22
- def build_project_to_collection(project: Project, config: BuildConfig | None):
23
+ def build_project_to_collection(
24
+ project: Project, config: BuildConfig | None, cache: CompileCache | None = None
25
+ ) -> Collection:
23
26
  collection = load_resources_files_to_collection(project.resources)
24
27
  for src_engine, converter in project.converters.items():
25
28
  if src_engine is None:
@@ -31,7 +34,7 @@ def build_project_to_collection(project: Project, config: BuildConfig | None):
31
34
  if config.override_resource_level_engines:
32
35
  for level in collection.categories.get("levels", {}).values():
33
36
  level["item"]["engine"] = project.engine.name
34
- add_engine_to_collection(collection, project, project.engine, config)
37
+ add_engine_to_collection(collection, project, project.engine, config, cache=cache)
35
38
  for level in project.levels:
36
39
  add_level_to_collection(collection, project, level)
37
40
  collection.name = f"{project.engine.name}"
@@ -63,8 +66,14 @@ def apply_converter_to_collection(
63
66
  level["data"] = new_data_srl
64
67
 
65
68
 
66
- def add_engine_to_collection(collection: Collection, project: Project, engine: Engine, config: BuildConfig | None):
67
- packaged_engine = package_engine(engine.data, config)
69
+ def add_engine_to_collection(
70
+ collection: Collection,
71
+ project: Project,
72
+ engine: Engine,
73
+ config: BuildConfig | None,
74
+ cache: CompileCache | None = None,
75
+ ):
76
+ packaged_engine = package_engine(engine.data, config, cache=cache)
68
77
  item = {
69
78
  "name": engine.name,
70
79
  "version": engine.version,
sonolus/script/array.py CHANGED
@@ -11,7 +11,7 @@ from sonolus.script.debug import assert_unreachable
11
11
  from sonolus.script.internal.context import ctx
12
12
  from sonolus.script.internal.error import InternalError
13
13
  from sonolus.script.internal.generic import GenericValue
14
- from sonolus.script.internal.impl import meta_fn, validate_value
14
+ from sonolus.script.internal.impl import meta_fn, perf_meta_fn, validate_value
15
15
  from sonolus.script.internal.value import BackingSource, DataValue, Value
16
16
  from sonolus.script.num import Num
17
17
 
@@ -188,6 +188,7 @@ class Array[T, Size](GenericValue, ArrayLike[T], metaclass=ArrayMeta):
188
188
  else:
189
189
  return cls._with_value([cls.element_type()._zero_() for _ in range(cls.size())])
190
190
 
191
+ @perf_meta_fn
191
192
  def __len__(self):
192
193
  return self.size()
193
194
 
sonolus/script/bucket.py CHANGED
@@ -7,7 +7,7 @@ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
7
7
  from sonolus.backend.mode import Mode
8
8
  from sonolus.backend.ops import Op
9
9
  from sonolus.script.internal.context import ctx
10
- from sonolus.script.internal.impl import meta_fn
10
+ from sonolus.script.internal.impl import meta_fn, perf_meta_fn
11
11
  from sonolus.script.internal.introspection import get_field_specifiers
12
12
  from sonolus.script.internal.native import native_function
13
13
  from sonolus.script.interval import Interval
@@ -72,20 +72,22 @@ class JudgmentWindow(Record):
72
72
  *self.good.tuple,
73
73
  )
74
74
 
75
+ @perf_meta_fn
75
76
  def __mul__(self, other: float | int) -> JudgmentWindow:
76
77
  """Multiply the intervals by a scalar."""
77
- return JudgmentWindow(
78
- self.perfect * other,
79
- self.great * other,
80
- self.good * other,
78
+ return JudgmentWindow._quick_construct(
79
+ perfect=self.perfect * other,
80
+ great=self.great * other,
81
+ good=self.good * other,
81
82
  )
82
83
 
84
+ @perf_meta_fn
83
85
  def __add__(self, other: float | int) -> JudgmentWindow:
84
86
  """Add a scalar to the intervals."""
85
- return JudgmentWindow(
86
- self.perfect + other,
87
- self.great + other,
88
- self.good + other,
87
+ return JudgmentWindow._quick_construct(
88
+ perfect=self.perfect + other,
89
+ great=self.great + other,
90
+ good=self.good + other,
89
91
  )
90
92
 
91
93
  @property
sonolus/script/debug.py CHANGED
@@ -68,16 +68,31 @@ def debug_pause():
68
68
  input("[DEBUG] Paused")
69
69
 
70
70
 
71
+ @meta_fn
71
72
  def assert_true(value: int | float | bool, message: str | None = None):
72
- message = message if message is not None else "Assertion failed"
73
- if not value:
74
- error(message)
73
+ if not ctx():
74
+ if not value:
75
+ raise AssertionError(message if message is not None else "Assertion failed")
76
+ return
77
+ value = Num._accept_(validate_value(value))
78
+ message = validate_value(message)
79
+ message = message._as_py_() or "Assertion failed"
80
+ if value._is_py_():
81
+ if value._as_py_():
82
+ return
83
+ else:
84
+ error(message)
85
+ else:
86
+ ctx().test = value.ir()
87
+ t_branch = ctx().branch(None)
88
+ f_branch = ctx().branch(0)
89
+ set_ctx(f_branch)
90
+ error(message) # type: ignore
91
+ set_ctx(t_branch)
75
92
 
76
93
 
77
94
  def assert_false(value: int | float | bool, message: str | None = None):
78
- message = message if message is not None else "Assertion failed"
79
- if value:
80
- error(message)
95
+ assert_true(not value, message)
81
96
 
82
97
 
83
98
  def static_assert(value: int | float | bool, message: str | None = None):
@@ -33,6 +33,10 @@ def meta_fn(fn=None):
33
33
  return decorator(fn)
34
34
 
35
35
 
36
+ # To indicate this was used for performance reasons rather than functionality
37
+ perf_meta_fn = meta_fn
38
+
39
+
36
40
  def validate_value[T](value: T) -> Value | T:
37
41
  result = try_validate_value(value)
38
42
  if result is None:
@@ -59,16 +63,16 @@ def try_validate_value(value: Any) -> Value | None:
59
63
  pass
60
64
 
61
65
  match value:
62
- case Enum():
63
- return validate_value(value.value)
64
66
  case Value():
65
67
  return value
68
+ case int() | float() | bool():
69
+ return Num._accept_(value)
70
+ case Enum():
71
+ return validate_value(value.value)
66
72
  case type():
67
73
  if value in {int, float, bool}:
68
74
  return BasicConstantValue.of(Num)
69
75
  return BasicConstantValue.of(value)
70
- case int() | float() | bool():
71
- return Num._accept_(value)
72
76
  case tuple():
73
77
  return TupleImpl._accept_(value)
74
78
  case dict():
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from sonolus.backend.ops import Op
4
4
  from sonolus.script.array_like import ArrayLike
5
5
  from sonolus.script.debug import static_error
6
+ from sonolus.script.internal.builtin_impls import _max, _min
7
+ from sonolus.script.internal.impl import perf_meta_fn
6
8
  from sonolus.script.internal.native import native_function
7
9
  from sonolus.script.internal.range import range_or_tuple
8
10
  from sonolus.script.num import Num
@@ -66,6 +68,7 @@ class Interval(Record):
66
68
  case _:
67
69
  static_error("Invalid type for interval check")
68
70
 
71
+ @perf_meta_fn
69
72
  def __add__(self, other: float | int) -> Interval:
70
73
  """Add a value to both ends of the interval.
71
74
 
@@ -75,8 +78,9 @@ class Interval(Record):
75
78
  Returns:
76
79
  A new interval with the value added to both ends.
77
80
  """
78
- return Interval(self.start + other, self.end + other)
81
+ return Interval._quick_construct(start=self.start + other, end=self.end + other)
79
82
 
83
+ @perf_meta_fn
80
84
  def __sub__(self, other: float | int) -> Interval:
81
85
  """Subtract a value from both ends of the interval.
82
86
 
@@ -86,8 +90,9 @@ class Interval(Record):
86
90
  Returns:
87
91
  A new interval with the value subtracted from both ends.
88
92
  """
89
- return Interval(self.start - other, self.end - other)
93
+ return Interval._quick_construct(start=self.start - other, end=self.end - other)
90
94
 
95
+ @perf_meta_fn
91
96
  def __mul__(self, other: float | int) -> Interval:
92
97
  """Multiply both ends of the interval by a value.
93
98
 
@@ -97,8 +102,9 @@ class Interval(Record):
97
102
  Returns:
98
103
  A new interval with both ends multiplied by the value.
99
104
  """
100
- return Interval(self.start * other, self.end * other)
105
+ return Interval._quick_construct(start=self.start * other, end=self.end * other)
101
106
 
107
+ @perf_meta_fn
102
108
  def __truediv__(self, other: float | int) -> Interval:
103
109
  """Divide both ends of the interval by a value.
104
110
 
@@ -108,8 +114,9 @@ class Interval(Record):
108
114
  Returns:
109
115
  A new interval with both ends divided by the value.
110
116
  """
111
- return Interval(self.start / other, self.end / other)
117
+ return Interval._quick_construct(start=self.start / other, end=self.end / other)
112
118
 
119
+ @perf_meta_fn
113
120
  def __floordiv__(self, other: float | int) -> Interval:
114
121
  """Divide both ends of the interval by a value and floor the result.
115
122
 
@@ -119,7 +126,7 @@ class Interval(Record):
119
126
  Returns:
120
127
  A new interval with both ends divided by the value and floored.
121
128
  """
122
- return Interval(self.start // other, self.end // other)
129
+ return Interval._quick_construct(start=self.start // other, end=self.end // other)
123
130
 
124
131
  def __and__(self, other: Interval) -> Interval:
125
132
  """Get the intersection of two intervals.
@@ -222,14 +229,17 @@ def _num_lerp_clamped(a, b, x, /):
222
229
  return a + (b - a) * max(0, min(1, x))
223
230
 
224
231
 
232
+ @perf_meta_fn
225
233
  def _generic_lerp[T](a: T, b: T, x: float, /) -> T:
226
234
  return a + (b - a) * x # type: ignore
227
235
 
228
236
 
237
+ @perf_meta_fn
229
238
  def _generic_lerp_clamped[T](a: T, b: T, x: float, /) -> T:
230
- return a + (b - a) * max(0, min(1, x)) # type: ignore
239
+ return a + (b - a) * _max(0, _min(1, x)) # type: ignore
231
240
 
232
241
 
242
+ @perf_meta_fn
233
243
  def lerp[T](a: T, b: T, x: float, /) -> T:
234
244
  """Linearly interpolate between two values.
235
245
 
@@ -248,6 +258,7 @@ def lerp[T](a: T, b: T, x: float, /) -> T:
248
258
  return _generic_lerp(a, b, x)
249
259
 
250
260
 
261
+ @perf_meta_fn
251
262
  def lerp_clamped[T](a: T, b: T, x: float, /) -> T:
252
263
  """Linearly interpolate between two values, clamped to the interval.
253
264
 
sonolus/script/num.py CHANGED
@@ -27,6 +27,8 @@ def _is_num(value: Any) -> TypeGuard[Num]:
27
27
 
28
28
  @final
29
29
  class _Num(Value, metaclass=_NumMeta):
30
+ __slots__ = ("data",)
31
+
30
32
  # This works for ints, floats, and bools
31
33
  # Since we don't support complex numbers, real is equal to the original number
32
34
  __match_args__ = ("real",)
@@ -34,16 +36,10 @@ class _Num(Value, metaclass=_NumMeta):
34
36
  data: DataValue
35
37
 
36
38
  def __init__(self, data: DataValue | IRExpr):
37
- if isinstance(data, complex):
38
- raise TypeError("Cannot create a Num from a complex number")
39
39
  if isinstance(data, int):
40
40
  data = float(data)
41
41
  if isinstance(data, IRConst | IRPureInstr | IRGet):
42
42
  data = ExprBackingValue(data)
43
- if _is_num(data):
44
- raise InternalError("Cannot create a Num from a Num")
45
- if not isinstance(data, BlockPlace | BackingValue | float | int | bool):
46
- raise TypeError(f"Cannot create a Num from {type(data)}")
47
43
  self.data = data
48
44
 
49
45
  def __str__(self) -> str:
@@ -451,6 +447,12 @@ class _Num(Value, metaclass=_NumMeta):
451
447
  return 0
452
448
 
453
449
 
450
+ def _create_num_raw(i: int) -> Num:
451
+ result = object.__new__(_Num)
452
+ result.data = float(i)
453
+ return result
454
+
455
+
454
456
  if TYPE_CHECKING:
455
457
  from typing import Protocol
456
458
 
sonolus/script/project.py CHANGED
@@ -8,6 +8,7 @@ from typing import ClassVar, TypedDict
8
8
 
9
9
  from sonolus.backend.optimize import optimize
10
10
  from sonolus.backend.optimize.passes import CompilerPass
11
+ from sonolus.build.compile import CompileCache
11
12
  from sonolus.script.archetype import ArchetypeSchema
12
13
  from sonolus.script.engine import Engine
13
14
  from sonolus.script.level import ExternalLevelData, Level, LevelData
@@ -65,8 +66,20 @@ class Project:
65
66
  """
66
67
  from sonolus.build.cli import build_collection, run_server
67
68
 
68
- build_collection(self, Path(build_dir), config)
69
- run_server(Path(build_dir) / "site", port=port)
69
+ if config is None:
70
+ config = BuildConfig()
71
+
72
+ cache = CompileCache()
73
+ build_collection(self, Path(build_dir), config, cache=cache)
74
+ run_server(
75
+ Path(build_dir) / "site",
76
+ port=port,
77
+ project_module_name=None,
78
+ core_module_names=None,
79
+ build_dir=Path(build_dir),
80
+ config=config,
81
+ cache=cache,
82
+ )
70
83
 
71
84
  def build(self, build_dir: PathLike, config: BuildConfig | None = None):
72
85
  """Build the project.
sonolus/script/quad.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Protocol, Self, assert_never, overload
4
4
 
5
+ from sonolus.script.internal.impl import perf_meta_fn
5
6
  from sonolus.script.record import Record
6
7
  from sonolus.script.values import zeros
7
8
  from sonolus.script.vec import Vec2, pnpoly
@@ -451,6 +452,7 @@ type QuadLike = _QuadLike | Quad
451
452
  """A type that can be used as a quad."""
452
453
 
453
454
 
455
+ @perf_meta_fn
454
456
  def flatten_quad(quad: QuadLike) -> tuple[float, float, float, float, float, float, float, float]:
455
457
  bl = quad.bl
456
458
  tl = quad.tl
sonolus/script/record.py CHANGED
@@ -171,6 +171,15 @@ class Record(GenericValue, metaclass=RecordMeta):
171
171
  result._value_ = kwargs
172
172
  return result
173
173
 
174
+ @classmethod
175
+ def _quick_construct(cls, **kwargs) -> Self:
176
+ result = object.__new__(cls)
177
+ for k, v in kwargs.items():
178
+ if isinstance(v, int | float):
179
+ kwargs[k] = Num._accept_(v)
180
+ result._value_ = kwargs
181
+ return result
182
+
174
183
  @classmethod
175
184
  def _size_(cls) -> int:
176
185
  return sum(field.type._size_() for field in cls._fields_)
sonolus/script/runtime.py CHANGED
@@ -30,7 +30,7 @@ from sonolus.script.globals import (
30
30
  _watch_runtime_update,
31
31
  )
32
32
  from sonolus.script.internal.context import ctx
33
- from sonolus.script.internal.impl import meta_fn
33
+ from sonolus.script.internal.impl import meta_fn, perf_meta_fn
34
34
  from sonolus.script.num import Num
35
35
  from sonolus.script.quad import Quad, Rect
36
36
  from sonolus.script.record import Record
@@ -1154,9 +1154,10 @@ def canvas() -> _PreviewRuntimeCanvas:
1154
1154
  return _PreviewRuntimeCanvas # type: ignore
1155
1155
 
1156
1156
 
1157
+ @perf_meta_fn
1157
1158
  def screen() -> Rect:
1158
1159
  """Get the screen boundaries as a rectangle."""
1159
- return Rect(t=1, r=aspect_ratio(), b=-1, l=-aspect_ratio())
1160
+ return Rect._quick_construct(t=1, r=aspect_ratio(), b=-1, l=-aspect_ratio())
1160
1161
 
1161
1162
 
1162
1163
  def level_score() -> _LevelScore:
sonolus/script/sprite.py CHANGED
@@ -6,6 +6,7 @@ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
6
6
  from sonolus.backend.ops import Op
7
7
  from sonolus.script.array_like import ArrayLike
8
8
  from sonolus.script.debug import static_error
9
+ from sonolus.script.internal.impl import perf_meta_fn
9
10
  from sonolus.script.internal.introspection import get_field_specifiers
10
11
  from sonolus.script.internal.native import native_function
11
12
  from sonolus.script.quad import QuadLike, flatten_quad
@@ -29,6 +30,7 @@ class Sprite(Record):
29
30
  """Check if the sprite is available."""
30
31
  return _has_skin_sprite(self.id)
31
32
 
33
+ @perf_meta_fn
32
34
  def draw(self, quad: QuadLike, z: float = 0.0, a: float = 1.0):
33
35
  """Draw the sprite.
34
36
 
@@ -39,6 +41,7 @@ class Sprite(Record):
39
41
  """
40
42
  _draw(self.id, *flatten_quad(quad), z, a)
41
43
 
44
+ @perf_meta_fn
42
45
  def draw_curved_b(self, quad: QuadLike, cp: Vec2, n: float, z: float = 0.0, a: float = 1.0):
43
46
  """Draw the sprite with a curved bottom with a quadratic Bézier curve.
44
47
 
@@ -51,6 +54,7 @@ class Sprite(Record):
51
54
  """
52
55
  _draw_curved_b(self.id, *flatten_quad(quad), z, a, n, *cp.tuple)
53
56
 
57
+ @perf_meta_fn
54
58
  def draw_curved_t(self, quad: QuadLike, cp: Vec2, n: float, z: float = 0.0, a: float = 1.0):
55
59
  """Draw the sprite with a curved top with a quadratic Bézier curve.
56
60
 
@@ -63,6 +67,7 @@ class Sprite(Record):
63
67
  """
64
68
  _draw_curved_t(self.id, *flatten_quad(quad), z, a, n, *cp.tuple)
65
69
 
70
+ @perf_meta_fn
66
71
  def draw_curved_l(self, quad: QuadLike, cp: Vec2, n: float, z: float = 0.0, a: float = 1.0):
67
72
  """Draw the sprite with a curved left side with a quadratic Bézier curve.
68
73
 
@@ -75,6 +80,7 @@ class Sprite(Record):
75
80
  """
76
81
  _draw_curved_l(self.id, *flatten_quad(quad), z, a, n, *cp.tuple)
77
82
 
83
+ @perf_meta_fn
78
84
  def draw_curved_r(self, quad: QuadLike, cp: Vec2, n: float, z: float = 0.0, a: float = 1.0):
79
85
  """Draw the sprite with a curved right side with a quadratic Bézier curve.
80
86
 
@@ -87,6 +93,7 @@ class Sprite(Record):
87
93
  """
88
94
  _draw_curved_r(self.id, *flatten_quad(quad), z, a, n, *cp.tuple)
89
95
 
96
+ @perf_meta_fn
90
97
  def draw_curved_bt(self, quad: QuadLike, cp1: Vec2, cp2: Vec2, n: float, z: float = 0.0, a: float = 1.0):
91
98
  """Draw the sprite with a curved bottom and top with a cubic Bézier curve.
92
99
 
@@ -100,6 +107,7 @@ class Sprite(Record):
100
107
  """
101
108
  _draw_curved_bt(self.id, *flatten_quad(quad), z, a, n, *cp1.tuple, *cp2.tuple)
102
109
 
110
+ @perf_meta_fn
103
111
  def draw_curved_lr(self, quad: QuadLike, cp1: Vec2, cp2: Vec2, n: float, z: float = 0.0, a: float = 1.0):
104
112
  """Draw the sprite with a curved left and right side with a cubic Bézier curve.
105
113