sonolus.py 0.3.0__py3-none-any.whl → 0.3.2__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/backend/node.py +13 -5
- sonolus/backend/optimize/allocate.py +41 -4
- sonolus/backend/optimize/flow.py +24 -7
- sonolus/backend/optimize/optimize.py +2 -9
- sonolus/backend/utils.py +6 -1
- sonolus/backend/visitor.py +47 -14
- sonolus/build/cli.py +6 -1
- sonolus/build/engine.py +1 -1
- sonolus/script/archetype.py +27 -17
- sonolus/script/array.py +15 -5
- sonolus/script/array_like.py +5 -3
- sonolus/script/containers.py +3 -3
- sonolus/script/debug.py +66 -8
- sonolus/script/globals.py +17 -0
- sonolus/script/internal/builtin_impls.py +2 -3
- sonolus/script/internal/context.py +50 -0
- sonolus/script/internal/simulation_context.py +131 -0
- sonolus/script/internal/tuple_impl.py +15 -10
- sonolus/script/iterator.py +3 -2
- sonolus/script/num.py +13 -3
- sonolus/script/options.py +24 -1
- sonolus/script/quad.py +2 -0
- sonolus/script/record.py +22 -3
- sonolus/script/runtime.py +383 -0
- sonolus/script/stream.py +149 -17
- sonolus/script/transform.py +289 -0
- sonolus/script/values.py +9 -3
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/RECORD +32 -31
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.0.dist-info → sonolus_py-0.3.2.dist-info}/licenses/LICENSE +0 -0
sonolus/script/runtime.py
CHANGED
|
@@ -259,6 +259,382 @@ class _TutorialRuntimeUi:
|
|
|
259
259
|
instruction: BasicRuntimeUiLayout
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
class UiLayout[T](Record):
|
|
263
|
+
"""The layout of a UI element."""
|
|
264
|
+
|
|
265
|
+
_underlying: T
|
|
266
|
+
|
|
267
|
+
def update(
|
|
268
|
+
self,
|
|
269
|
+
anchor: Vec2 | None = None,
|
|
270
|
+
pivot: Vec2 | None = None,
|
|
271
|
+
dimensions: Vec2 | None = None,
|
|
272
|
+
rotation: float | None = None,
|
|
273
|
+
alpha: float | None = None,
|
|
274
|
+
horizontal_align: HorizontalAlign | None = None,
|
|
275
|
+
background: bool | None = None,
|
|
276
|
+
):
|
|
277
|
+
"""Update the layout properties if it's available in the current mode and do nothing otherwise."""
|
|
278
|
+
match self._underlying:
|
|
279
|
+
case RuntimeUiLayout():
|
|
280
|
+
self._underlying.update(
|
|
281
|
+
anchor=anchor,
|
|
282
|
+
pivot=pivot,
|
|
283
|
+
dimensions=dimensions,
|
|
284
|
+
rotation=rotation,
|
|
285
|
+
alpha=alpha,
|
|
286
|
+
horizontal_align=horizontal_align,
|
|
287
|
+
background=background,
|
|
288
|
+
)
|
|
289
|
+
case BasicRuntimeUiLayout():
|
|
290
|
+
self._underlying.update(
|
|
291
|
+
anchor=anchor,
|
|
292
|
+
pivot=pivot,
|
|
293
|
+
dimensions=dimensions,
|
|
294
|
+
rotation=rotation,
|
|
295
|
+
alpha=alpha,
|
|
296
|
+
background=background,
|
|
297
|
+
)
|
|
298
|
+
case _:
|
|
299
|
+
pass # do nothing
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def is_available(self) -> bool:
|
|
303
|
+
"""Check if the layout is available in the current mode."""
|
|
304
|
+
return self._underlying is not None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class UiConfig[T](Record):
|
|
308
|
+
"""The user configuration for a UI element."""
|
|
309
|
+
|
|
310
|
+
_underlying: T
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def scale(self) -> float:
|
|
314
|
+
"""The scale of the UI element."""
|
|
315
|
+
match self._underlying:
|
|
316
|
+
case RuntimeUiConfig():
|
|
317
|
+
return self._underlying.scale
|
|
318
|
+
case _:
|
|
319
|
+
return 1.0 # Default scale if not available
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def alpha(self) -> float:
|
|
323
|
+
"""The alpha (opacity) of the UI element."""
|
|
324
|
+
match self._underlying:
|
|
325
|
+
case RuntimeUiConfig():
|
|
326
|
+
return self._underlying.alpha
|
|
327
|
+
case _:
|
|
328
|
+
return 1.0 # Default alpha if not available
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def is_available(self) -> bool:
|
|
332
|
+
"""Check if the config is available in the current mode."""
|
|
333
|
+
return self._underlying is not None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class RuntimeUi(Record):
|
|
337
|
+
"""Holds the layouts for different UI elements across all modes."""
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
@meta_fn
|
|
341
|
+
def menu(self) -> UiLayout:
|
|
342
|
+
"""The configuration for the menu UI element.
|
|
343
|
+
|
|
344
|
+
Available in play, watch, preview, and tutorial mode.
|
|
345
|
+
"""
|
|
346
|
+
match ctx().global_state.mode:
|
|
347
|
+
case Mode.PLAY:
|
|
348
|
+
return UiLayout(_PlayRuntimeUi.menu)
|
|
349
|
+
case Mode.WATCH:
|
|
350
|
+
return UiLayout(_WatchRuntimeUi.menu)
|
|
351
|
+
case Mode.PREVIEW:
|
|
352
|
+
return UiLayout(_PreviewRuntimeUi.menu)
|
|
353
|
+
case Mode.TUTORIAL:
|
|
354
|
+
return UiLayout(_TutorialRuntimeUi.menu)
|
|
355
|
+
case _:
|
|
356
|
+
raise RuntimeError("Unsupported mode for menu UI layout")
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
@meta_fn
|
|
360
|
+
def menu_config(self) -> UiConfig:
|
|
361
|
+
"""The configuration for the menu UI element.
|
|
362
|
+
|
|
363
|
+
Available in play, watch, preview, and tutorial mode.
|
|
364
|
+
"""
|
|
365
|
+
match ctx().global_state.mode:
|
|
366
|
+
case Mode.PLAY:
|
|
367
|
+
return UiConfig(_PlayRuntimeUiConfigs.menu)
|
|
368
|
+
case Mode.WATCH:
|
|
369
|
+
return UiConfig(_WatchRuntimeUiConfigs.menu)
|
|
370
|
+
case Mode.PREVIEW:
|
|
371
|
+
return UiConfig(_PreviewRuntimeUiConfigs.menu)
|
|
372
|
+
case Mode.TUTORIAL:
|
|
373
|
+
return UiConfig(_TutorialRuntimeUiConfigs.menu)
|
|
374
|
+
case _:
|
|
375
|
+
raise RuntimeError("Unsupported mode for menu UI configuration")
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
@meta_fn
|
|
379
|
+
def judgment(self) -> UiLayout:
|
|
380
|
+
"""The configuration for the judgment UI element.
|
|
381
|
+
|
|
382
|
+
Available in play and watch mode.
|
|
383
|
+
"""
|
|
384
|
+
match ctx().global_state.mode:
|
|
385
|
+
case Mode.PLAY:
|
|
386
|
+
return UiLayout(_PlayRuntimeUi.judgment)
|
|
387
|
+
case Mode.WATCH:
|
|
388
|
+
return UiLayout(_WatchRuntimeUi.judgment)
|
|
389
|
+
case _:
|
|
390
|
+
return UiLayout(None)
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
@meta_fn
|
|
394
|
+
def judgment_config(self) -> UiConfig:
|
|
395
|
+
"""The configuration for the judgment UI element.
|
|
396
|
+
|
|
397
|
+
Available in play and watch mode.
|
|
398
|
+
"""
|
|
399
|
+
match ctx().global_state.mode:
|
|
400
|
+
case Mode.PLAY:
|
|
401
|
+
return UiConfig(_PlayRuntimeUiConfigs.judgment)
|
|
402
|
+
case Mode.WATCH:
|
|
403
|
+
return UiConfig(_WatchRuntimeUiConfigs.judgment)
|
|
404
|
+
case _:
|
|
405
|
+
return UiConfig(None)
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
@meta_fn
|
|
409
|
+
def combo_value(self) -> UiLayout:
|
|
410
|
+
"""The configuration for the combo value UI element.
|
|
411
|
+
|
|
412
|
+
Available in play and watch mode.
|
|
413
|
+
"""
|
|
414
|
+
match ctx().global_state.mode:
|
|
415
|
+
case Mode.PLAY:
|
|
416
|
+
return UiLayout(_PlayRuntimeUi.combo_value)
|
|
417
|
+
case Mode.WATCH:
|
|
418
|
+
return UiLayout(_WatchRuntimeUi.combo_value)
|
|
419
|
+
case _:
|
|
420
|
+
return UiLayout(None)
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
@meta_fn
|
|
424
|
+
def combo_text(self) -> UiLayout:
|
|
425
|
+
"""The configuration for the combo text UI element.
|
|
426
|
+
|
|
427
|
+
Available in play and watch mode.
|
|
428
|
+
"""
|
|
429
|
+
match ctx().global_state.mode:
|
|
430
|
+
case Mode.PLAY:
|
|
431
|
+
return UiLayout(_PlayRuntimeUi.combo_text)
|
|
432
|
+
case Mode.WATCH:
|
|
433
|
+
return UiLayout(_WatchRuntimeUi.combo_text)
|
|
434
|
+
case _:
|
|
435
|
+
return UiLayout(None)
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
@meta_fn
|
|
439
|
+
def combo_config(self) -> UiConfig:
|
|
440
|
+
"""The configuration for the combo UI element.
|
|
441
|
+
|
|
442
|
+
Available in play and watch mode.
|
|
443
|
+
"""
|
|
444
|
+
match ctx().global_state.mode:
|
|
445
|
+
case Mode.PLAY:
|
|
446
|
+
return UiConfig(_PlayRuntimeUiConfigs.combo)
|
|
447
|
+
case Mode.WATCH:
|
|
448
|
+
return UiConfig(_WatchRuntimeUiConfigs.combo)
|
|
449
|
+
case _:
|
|
450
|
+
return UiConfig(None)
|
|
451
|
+
|
|
452
|
+
@property
|
|
453
|
+
@meta_fn
|
|
454
|
+
def primary_metric_bar(self) -> UiLayout:
|
|
455
|
+
"""The configuration for the primary metric bar UI element.
|
|
456
|
+
|
|
457
|
+
Available in play and watch mode.
|
|
458
|
+
"""
|
|
459
|
+
match ctx().global_state.mode:
|
|
460
|
+
case Mode.PLAY:
|
|
461
|
+
return UiLayout(_PlayRuntimeUi.primary_metric_bar)
|
|
462
|
+
case Mode.WATCH:
|
|
463
|
+
return UiLayout(_WatchRuntimeUi.primary_metric_bar)
|
|
464
|
+
case _:
|
|
465
|
+
return UiLayout(None)
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
@meta_fn
|
|
469
|
+
def primary_metric_value(self) -> UiLayout:
|
|
470
|
+
"""The configuration for the primary metric value UI element.
|
|
471
|
+
|
|
472
|
+
Available in play and watch mode.
|
|
473
|
+
"""
|
|
474
|
+
match ctx().global_state.mode:
|
|
475
|
+
case Mode.PLAY:
|
|
476
|
+
return UiLayout(_PlayRuntimeUi.primary_metric_value)
|
|
477
|
+
case Mode.WATCH:
|
|
478
|
+
return UiLayout(_WatchRuntimeUi.primary_metric_value)
|
|
479
|
+
case _:
|
|
480
|
+
return UiLayout(None)
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
@meta_fn
|
|
484
|
+
def primary_metric_config(self) -> UiConfig:
|
|
485
|
+
"""The configuration for the primary metric UI element.
|
|
486
|
+
|
|
487
|
+
Available in play and watch mode.
|
|
488
|
+
"""
|
|
489
|
+
match ctx().global_state.mode:
|
|
490
|
+
case Mode.PLAY:
|
|
491
|
+
return UiConfig(_PlayRuntimeUiConfigs.primary_metric)
|
|
492
|
+
case Mode.WATCH:
|
|
493
|
+
return UiConfig(_WatchRuntimeUiConfigs.primary_metric)
|
|
494
|
+
case _:
|
|
495
|
+
return UiConfig(None)
|
|
496
|
+
|
|
497
|
+
@property
|
|
498
|
+
@meta_fn
|
|
499
|
+
def secondary_metric_bar(self) -> UiLayout:
|
|
500
|
+
"""The configuration for the secondary metric bar UI element.
|
|
501
|
+
|
|
502
|
+
Available in play and watch mode.
|
|
503
|
+
"""
|
|
504
|
+
match ctx().global_state.mode:
|
|
505
|
+
case Mode.PLAY:
|
|
506
|
+
return UiLayout(_PlayRuntimeUi.secondary_metric_bar)
|
|
507
|
+
case Mode.WATCH:
|
|
508
|
+
return UiLayout(_WatchRuntimeUi.secondary_metric_bar)
|
|
509
|
+
case _:
|
|
510
|
+
return UiLayout(None)
|
|
511
|
+
|
|
512
|
+
@property
|
|
513
|
+
@meta_fn
|
|
514
|
+
def secondary_metric_value(self) -> UiLayout:
|
|
515
|
+
"""The configuration for the secondary metric value UI element.
|
|
516
|
+
|
|
517
|
+
Available in play and watch mode.
|
|
518
|
+
"""
|
|
519
|
+
match ctx().global_state.mode:
|
|
520
|
+
case Mode.PLAY:
|
|
521
|
+
return UiLayout(_PlayRuntimeUi.secondary_metric_value)
|
|
522
|
+
case Mode.WATCH:
|
|
523
|
+
return UiLayout(_WatchRuntimeUi.secondary_metric_value)
|
|
524
|
+
case _:
|
|
525
|
+
return UiLayout(None)
|
|
526
|
+
|
|
527
|
+
@property
|
|
528
|
+
@meta_fn
|
|
529
|
+
def secondary_metric_config(self) -> UiConfig:
|
|
530
|
+
"""The configuration for the secondary metric UI element.
|
|
531
|
+
|
|
532
|
+
Available in play and watch mode.
|
|
533
|
+
"""
|
|
534
|
+
match ctx().global_state.mode:
|
|
535
|
+
case Mode.PLAY:
|
|
536
|
+
return UiConfig(_PlayRuntimeUiConfigs.secondary_metric)
|
|
537
|
+
case Mode.WATCH:
|
|
538
|
+
return UiConfig(_WatchRuntimeUiConfigs.secondary_metric)
|
|
539
|
+
case _:
|
|
540
|
+
return UiConfig(None)
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
@meta_fn
|
|
544
|
+
def progress(self) -> UiLayout:
|
|
545
|
+
"""The configuration for the progress UI element.
|
|
546
|
+
|
|
547
|
+
Available in watch and preview mode.
|
|
548
|
+
"""
|
|
549
|
+
match ctx().global_state.mode:
|
|
550
|
+
case Mode.WATCH:
|
|
551
|
+
return UiLayout(_WatchRuntimeUi.progress)
|
|
552
|
+
case Mode.PREVIEW:
|
|
553
|
+
return UiLayout(_PreviewRuntimeUi.progress)
|
|
554
|
+
case _:
|
|
555
|
+
return UiLayout(None)
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
@meta_fn
|
|
559
|
+
def progress_config(self) -> UiConfig:
|
|
560
|
+
"""The configuration for the progress UI element.
|
|
561
|
+
|
|
562
|
+
Available in watch and preview mode.
|
|
563
|
+
"""
|
|
564
|
+
match ctx().global_state.mode:
|
|
565
|
+
case Mode.WATCH:
|
|
566
|
+
return UiConfig(_WatchRuntimeUiConfigs.progress)
|
|
567
|
+
case Mode.PREVIEW:
|
|
568
|
+
return UiConfig(_PreviewRuntimeUiConfigs.progress)
|
|
569
|
+
case _:
|
|
570
|
+
return UiConfig(None)
|
|
571
|
+
|
|
572
|
+
@property
|
|
573
|
+
@meta_fn
|
|
574
|
+
def previous(self) -> UiLayout:
|
|
575
|
+
"""The configuration for the previous navigation UI element.
|
|
576
|
+
|
|
577
|
+
Available in tutorial mode.
|
|
578
|
+
"""
|
|
579
|
+
match ctx().global_state.mode:
|
|
580
|
+
case Mode.TUTORIAL:
|
|
581
|
+
return UiLayout(_TutorialRuntimeUi.previous)
|
|
582
|
+
case _:
|
|
583
|
+
return UiLayout(None)
|
|
584
|
+
|
|
585
|
+
@property
|
|
586
|
+
@meta_fn
|
|
587
|
+
def next(self) -> UiLayout:
|
|
588
|
+
"""The configuration for the next navigation UI element.
|
|
589
|
+
|
|
590
|
+
Available in tutorial mode.
|
|
591
|
+
"""
|
|
592
|
+
match ctx().global_state.mode:
|
|
593
|
+
case Mode.TUTORIAL:
|
|
594
|
+
return UiLayout(_TutorialRuntimeUi.next)
|
|
595
|
+
case _:
|
|
596
|
+
return UiLayout(None)
|
|
597
|
+
|
|
598
|
+
@property
|
|
599
|
+
@meta_fn
|
|
600
|
+
def navigation_config(self) -> UiConfig:
|
|
601
|
+
"""The configuration for the navigation UI element.
|
|
602
|
+
|
|
603
|
+
Available in tutorial mode.
|
|
604
|
+
"""
|
|
605
|
+
match ctx().global_state.mode:
|
|
606
|
+
case Mode.TUTORIAL:
|
|
607
|
+
return UiConfig(_TutorialRuntimeUiConfigs.navigation)
|
|
608
|
+
case _:
|
|
609
|
+
return UiConfig(None)
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
@meta_fn
|
|
613
|
+
def instruction(self) -> UiLayout:
|
|
614
|
+
"""The configuration for the instruction UI element.
|
|
615
|
+
|
|
616
|
+
Available in tutorial mode.
|
|
617
|
+
"""
|
|
618
|
+
match ctx().global_state.mode:
|
|
619
|
+
case Mode.TUTORIAL:
|
|
620
|
+
return UiLayout(_TutorialRuntimeUi.instruction)
|
|
621
|
+
case _:
|
|
622
|
+
return UiLayout(None)
|
|
623
|
+
|
|
624
|
+
@property
|
|
625
|
+
@meta_fn
|
|
626
|
+
def instruction_config(self) -> UiConfig:
|
|
627
|
+
"""The configuration for the instruction UI element.
|
|
628
|
+
|
|
629
|
+
Available in tutorial mode.
|
|
630
|
+
"""
|
|
631
|
+
match ctx().global_state.mode:
|
|
632
|
+
case Mode.TUTORIAL:
|
|
633
|
+
return UiConfig(_TutorialRuntimeUiConfigs.instruction)
|
|
634
|
+
case _:
|
|
635
|
+
return UiConfig(None)
|
|
636
|
+
|
|
637
|
+
|
|
262
638
|
class Touch(Record):
|
|
263
639
|
"""Data of a touch event."""
|
|
264
640
|
|
|
@@ -733,6 +1109,13 @@ preview_ui_configs = _PreviewRuntimeUiConfigs
|
|
|
733
1109
|
tutorial_ui = _TutorialRuntimeUi
|
|
734
1110
|
tutorial_ui_configs = _TutorialRuntimeUiConfigs
|
|
735
1111
|
|
|
1112
|
+
_runtime_ui = RuntimeUi()
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def runtime_ui() -> RuntimeUi:
|
|
1116
|
+
"""Get the runtime UI configuration."""
|
|
1117
|
+
return _runtime_ui
|
|
1118
|
+
|
|
736
1119
|
|
|
737
1120
|
def canvas() -> _PreviewRuntimeCanvas:
|
|
738
1121
|
"""Get the preview canvas."""
|
sonolus/script/stream.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import cast, dataclass_transform
|
|
4
4
|
|
|
5
|
-
from sonolus.backend.ir import IRExpr, IRInstr, IRPureInstr
|
|
5
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr
|
|
6
6
|
from sonolus.backend.mode import Mode
|
|
7
7
|
from sonolus.backend.ops import Op
|
|
8
8
|
from sonolus.script.internal.context import ctx
|
|
@@ -14,6 +14,7 @@ from sonolus.script.internal.value import BackingValue, Value
|
|
|
14
14
|
from sonolus.script.iterator import SonolusIterator
|
|
15
15
|
from sonolus.script.num import Num
|
|
16
16
|
from sonolus.script.record import Record
|
|
17
|
+
from sonolus.script.runtime import delta_time, time
|
|
17
18
|
from sonolus.script.values import sizeof
|
|
18
19
|
|
|
19
20
|
|
|
@@ -42,7 +43,7 @@ class _StreamDataField(SonolusDescriptor):
|
|
|
42
43
|
self.type_ = type_
|
|
43
44
|
|
|
44
45
|
def _get(self):
|
|
45
|
-
return self.type_._from_backing_source_(lambda offset:
|
|
46
|
+
return self.type_._from_backing_source_(lambda offset: _SparseStreamBacking(self.offset, Num(offset)))
|
|
46
47
|
|
|
47
48
|
def __get__(self, instance, owner):
|
|
48
49
|
_check_can_read_or_write_stream()
|
|
@@ -150,10 +151,35 @@ class _StreamBacking(BackingValue):
|
|
|
150
151
|
ctx().add_statement(IRInstr(Op.StreamSet, [self.id.ir(), self.index.ir(), value]))
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
class
|
|
154
|
+
class _SparseStreamBacking(BackingValue):
|
|
155
|
+
id: Num
|
|
156
|
+
index: Num
|
|
157
|
+
|
|
158
|
+
def __init__(self, stream_id: int, index: Num):
|
|
159
|
+
super().__init__()
|
|
160
|
+
self.id = Num._accept_(stream_id)
|
|
161
|
+
self.index = Num._accept_(index)
|
|
162
|
+
|
|
163
|
+
def read(self) -> IRExpr:
|
|
164
|
+
"""Read the value from the stream."""
|
|
165
|
+
_check_can_read_stream()
|
|
166
|
+
return IRPureInstr(Op.StreamGetValue, [self.id.ir(), self.index.ir()])
|
|
167
|
+
|
|
168
|
+
def write(self, value: IRExpr) -> None:
|
|
169
|
+
"""Write the value to the stream."""
|
|
170
|
+
_check_can_write_stream()
|
|
171
|
+
ctx().add_statements(
|
|
172
|
+
IRInstr(Op.StreamSet, [self.id.ir(), self.index.ir(), value]),
|
|
173
|
+
IRInstr(Op.StreamSet, [self.id.ir(), (self.index - 0.5).ir(), IRConst(0)]),
|
|
174
|
+
IRInstr(Op.StreamSet, [self.id.ir(), (self.index + 0.5).ir(), IRConst(0)]),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Stream[T](Record):
|
|
154
179
|
"""Represents a stream.
|
|
155
180
|
|
|
156
|
-
Most users should use `@stream
|
|
181
|
+
Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups rather than
|
|
182
|
+
using this class directly.
|
|
157
183
|
|
|
158
184
|
If used directly, it is important that streams do not overlap. No other streams should have an offset in
|
|
159
185
|
`range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
|
|
@@ -224,6 +250,12 @@ class Stream[T](Record, BackingValue):
|
|
|
224
250
|
_check_can_read_stream()
|
|
225
251
|
return _stream_get_next_key(self.offset, key)
|
|
226
252
|
|
|
253
|
+
def next_key_or_default(self, key: int | float, default: int | float) -> int:
|
|
254
|
+
"""Get the next key, or the default value if there is no next key."""
|
|
255
|
+
_check_can_read_stream()
|
|
256
|
+
next_key = self.next_key(key)
|
|
257
|
+
return next_key if next_key > key else default
|
|
258
|
+
|
|
227
259
|
def previous_key(self, key: int | float) -> int:
|
|
228
260
|
"""Get the previous key, or the key unchanged if it is the first key or the stream is empty.
|
|
229
261
|
|
|
@@ -232,6 +264,12 @@ class Stream[T](Record, BackingValue):
|
|
|
232
264
|
_check_can_read_stream()
|
|
233
265
|
return _stream_get_previous_key(self.offset, key)
|
|
234
266
|
|
|
267
|
+
def previous_key_or_default(self, key: int | float, default: int | float) -> int:
|
|
268
|
+
"""Get the previous key, or the default value if there is no previous key."""
|
|
269
|
+
_check_can_read_stream()
|
|
270
|
+
previous_key = self.previous_key(key)
|
|
271
|
+
return previous_key if previous_key < key else default
|
|
272
|
+
|
|
235
273
|
def has_next_key(self, key: int | float) -> bool:
|
|
236
274
|
"""Check if there is a next key after the given key in the stream."""
|
|
237
275
|
_check_can_read_stream()
|
|
@@ -293,6 +331,22 @@ class Stream[T](Record, BackingValue):
|
|
|
293
331
|
_check_can_read_stream()
|
|
294
332
|
return _StreamAscIterator(self, self.next_key_inclusive(start))
|
|
295
333
|
|
|
334
|
+
def iter_items_since_previous_frame(self) -> SonolusIterator[tuple[int | float, T]]:
|
|
335
|
+
"""Iterate over the items in the stream since the last frame.
|
|
336
|
+
|
|
337
|
+
This is a convenience method that iterates over the items in the stream occurring after the time of the
|
|
338
|
+
previous frame and up to and including the current time.
|
|
339
|
+
|
|
340
|
+
Usage:
|
|
341
|
+
```python
|
|
342
|
+
stream = ...
|
|
343
|
+
for key, value in stream.iter_items_since_previous_frame():
|
|
344
|
+
do_something(key, value)
|
|
345
|
+
```
|
|
346
|
+
"""
|
|
347
|
+
_check_can_read_stream()
|
|
348
|
+
return _StreamBoundedAscIterator(self, self.next_key(time() - delta_time()), time())
|
|
349
|
+
|
|
296
350
|
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
297
351
|
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
298
352
|
|
|
@@ -323,6 +377,22 @@ class Stream[T](Record, BackingValue):
|
|
|
323
377
|
_check_can_read_stream()
|
|
324
378
|
return _StreamAscKeyIterator(self, self.next_key_inclusive(start))
|
|
325
379
|
|
|
380
|
+
def iter_keys_since_previous_frame(self) -> SonolusIterator[int | float]:
|
|
381
|
+
"""Iterate over the keys in the stream since the last frame.
|
|
382
|
+
|
|
383
|
+
This is a convenience method that iterates over the keys in the stream occurring after the time of the
|
|
384
|
+
previous frame and up to and including the current time.
|
|
385
|
+
|
|
386
|
+
Usage:
|
|
387
|
+
```python
|
|
388
|
+
stream = ...
|
|
389
|
+
for key in stream.iter_keys_since_previous_frame():
|
|
390
|
+
do_something(key)
|
|
391
|
+
```
|
|
392
|
+
"""
|
|
393
|
+
_check_can_read_stream()
|
|
394
|
+
return _StreamBoundedAscKeyIterator(self, self.next_key(time() - delta_time()), time())
|
|
395
|
+
|
|
326
396
|
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
327
397
|
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
328
398
|
|
|
@@ -353,6 +423,22 @@ class Stream[T](Record, BackingValue):
|
|
|
353
423
|
_check_can_read_stream()
|
|
354
424
|
return _StreamAscValueIterator(self, self.next_key_inclusive(start))
|
|
355
425
|
|
|
426
|
+
def iter_values_since_previous_frame(self) -> SonolusIterator[T]:
|
|
427
|
+
"""Iterate over the values in the stream since the last frame.
|
|
428
|
+
|
|
429
|
+
This is a convenience method that iterates over the values in the stream occurring after the time of the
|
|
430
|
+
previous frame and up to and including the current time.
|
|
431
|
+
|
|
432
|
+
Usage:
|
|
433
|
+
```python
|
|
434
|
+
stream = ...
|
|
435
|
+
for value in stream.iter_values_since_previous_frame():
|
|
436
|
+
do_something(value)
|
|
437
|
+
```
|
|
438
|
+
"""
|
|
439
|
+
_check_can_read_stream()
|
|
440
|
+
return _StreamBoundedAscValueIterator(self, self.next_key(time() - delta_time()), time())
|
|
441
|
+
|
|
356
442
|
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
357
443
|
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
358
444
|
|
|
@@ -372,7 +458,8 @@ class Stream[T](Record, BackingValue):
|
|
|
372
458
|
class StreamGroup[T, Size](Record):
|
|
373
459
|
"""Represents a group of streams.
|
|
374
460
|
|
|
375
|
-
Most users should use `@stream
|
|
461
|
+
Most users should use [`@streams`][sonolus.script.stream.streams] to declare stream groups rather than using this
|
|
462
|
+
class directly.
|
|
376
463
|
|
|
377
464
|
Usage:
|
|
378
465
|
Declaring a stream group:
|
|
@@ -420,13 +507,28 @@ class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
420
507
|
current_key: int | float
|
|
421
508
|
|
|
422
509
|
def has_next(self) -> bool:
|
|
423
|
-
return self.
|
|
510
|
+
return self.current_key in self.stream
|
|
511
|
+
|
|
512
|
+
def get(self) -> tuple[int | float, T]:
|
|
513
|
+
return self.current_key, self.stream[self.current_key]
|
|
514
|
+
|
|
515
|
+
def advance(self):
|
|
516
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
520
|
+
stream: Stream[T]
|
|
521
|
+
current_key: int | float
|
|
522
|
+
end_key: int | float
|
|
523
|
+
|
|
524
|
+
def has_next(self) -> bool:
|
|
525
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
424
526
|
|
|
425
527
|
def get(self) -> tuple[int | float, T]:
|
|
426
528
|
return self.current_key, self.stream[self.current_key]
|
|
427
529
|
|
|
428
530
|
def advance(self):
|
|
429
|
-
self.current_key = self.stream.
|
|
531
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
430
532
|
|
|
431
533
|
|
|
432
534
|
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -434,13 +536,13 @@ class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
434
536
|
current_key: int | float
|
|
435
537
|
|
|
436
538
|
def has_next(self) -> bool:
|
|
437
|
-
return self.
|
|
539
|
+
return self.current_key in self.stream
|
|
438
540
|
|
|
439
541
|
def get(self) -> tuple[int | float, T]:
|
|
440
542
|
return self.current_key, self.stream[self.current_key]
|
|
441
543
|
|
|
442
544
|
def advance(self):
|
|
443
|
-
self.current_key = self.stream.
|
|
545
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, self.current_key - 1)
|
|
444
546
|
|
|
445
547
|
|
|
446
548
|
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -448,13 +550,28 @@ class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
448
550
|
current_key: int | float
|
|
449
551
|
|
|
450
552
|
def has_next(self) -> bool:
|
|
451
|
-
return self.
|
|
553
|
+
return self.current_key in self.stream
|
|
452
554
|
|
|
453
555
|
def get(self) -> int | float:
|
|
454
556
|
return self.current_key
|
|
455
557
|
|
|
456
558
|
def advance(self):
|
|
457
|
-
self.current_key = self.stream.
|
|
559
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
563
|
+
stream: Stream[T]
|
|
564
|
+
current_key: int | float
|
|
565
|
+
end_key: int | float
|
|
566
|
+
|
|
567
|
+
def has_next(self) -> bool:
|
|
568
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
569
|
+
|
|
570
|
+
def get(self) -> int | float:
|
|
571
|
+
return self.current_key
|
|
572
|
+
|
|
573
|
+
def advance(self):
|
|
574
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
458
575
|
|
|
459
576
|
|
|
460
577
|
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -462,13 +579,13 @@ class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
462
579
|
current_key: int | float
|
|
463
580
|
|
|
464
581
|
def has_next(self) -> bool:
|
|
465
|
-
return self.
|
|
582
|
+
return self.current_key in self.stream
|
|
466
583
|
|
|
467
584
|
def get(self) -> int | float:
|
|
468
585
|
return self.current_key
|
|
469
586
|
|
|
470
587
|
def advance(self):
|
|
471
|
-
self.current_key = self.stream.
|
|
588
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, self.current_key - 1)
|
|
472
589
|
|
|
473
590
|
|
|
474
591
|
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -476,13 +593,28 @@ class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
476
593
|
current_key: int | float
|
|
477
594
|
|
|
478
595
|
def has_next(self) -> bool:
|
|
479
|
-
return self.
|
|
596
|
+
return self.current_key in self.stream
|
|
597
|
+
|
|
598
|
+
def get(self) -> T:
|
|
599
|
+
return self.stream[self.current_key]
|
|
600
|
+
|
|
601
|
+
def advance(self):
|
|
602
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
606
|
+
stream: Stream[T]
|
|
607
|
+
current_key: int | float
|
|
608
|
+
end_key: int | float
|
|
609
|
+
|
|
610
|
+
def has_next(self) -> bool:
|
|
611
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
480
612
|
|
|
481
613
|
def get(self) -> T:
|
|
482
614
|
return self.stream[self.current_key]
|
|
483
615
|
|
|
484
616
|
def advance(self):
|
|
485
|
-
self.current_key = self.stream.
|
|
617
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, self.current_key + 1)
|
|
486
618
|
|
|
487
619
|
|
|
488
620
|
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -490,13 +622,13 @@ class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
|
490
622
|
current_key: int | float
|
|
491
623
|
|
|
492
624
|
def has_next(self) -> bool:
|
|
493
|
-
return self.
|
|
625
|
+
return self.current_key in self.stream
|
|
494
626
|
|
|
495
627
|
def get(self) -> T:
|
|
496
628
|
return self.stream[self.current_key]
|
|
497
629
|
|
|
498
630
|
def advance(self):
|
|
499
|
-
self.current_key = self.stream.
|
|
631
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, self.current_key - 1)
|
|
500
632
|
|
|
501
633
|
|
|
502
634
|
@native_function(Op.StreamGetNextKey)
|