@lightningjs/renderer 0.5.0 → 0.6.0

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.
Files changed (93) hide show
  1. package/README.md +40 -8
  2. package/dist/exports/main-api.d.ts +3 -3
  3. package/dist/exports/main-api.js +3 -3
  4. package/dist/exports/main-api.js.map +1 -1
  5. package/dist/src/core/CoreNode.d.ts +24 -10
  6. package/dist/src/core/CoreNode.js +92 -20
  7. package/dist/src/core/CoreNode.js.map +1 -1
  8. package/dist/src/core/CoreShaderManager.d.ts +4 -0
  9. package/dist/src/core/CoreShaderManager.js +8 -0
  10. package/dist/src/core/CoreShaderManager.js.map +1 -1
  11. package/dist/src/core/CoreTextNode.d.ts +2 -4
  12. package/dist/src/core/CoreTextNode.js +5 -53
  13. package/dist/src/core/CoreTextNode.js.map +1 -1
  14. package/dist/src/core/Stage.d.ts +14 -3
  15. package/dist/src/core/Stage.js +52 -32
  16. package/dist/src/core/Stage.js.map +1 -1
  17. package/dist/src/core/animations/CoreAnimation.d.ts +1 -0
  18. package/dist/src/core/animations/CoreAnimation.js +25 -15
  19. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  20. package/dist/src/core/lib/utils.d.ts +5 -1
  21. package/dist/src/core/lib/utils.js +17 -12
  22. package/dist/src/core/lib/utils.js.map +1 -1
  23. package/dist/src/core/platform.js +4 -4
  24. package/dist/src/core/platform.js.map +1 -1
  25. package/dist/src/core/renderers/CoreRenderer.d.ts +1 -3
  26. package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
  27. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +11 -17
  28. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +13 -30
  29. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  30. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +3 -0
  31. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js.map +1 -1
  32. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +5 -28
  33. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -1
  34. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.d.ts +61 -0
  35. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +126 -0
  36. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js.map +1 -0
  37. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +20 -2
  38. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +98 -45
  39. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  40. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +16 -4
  41. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +80 -42
  42. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  43. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +16 -8
  44. package/dist/src/core/text-rendering/renderers/TextRenderer.js +34 -8
  45. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  46. package/dist/src/main-api/{IRenderDriver.d.ts → ICoreDriver.d.ts} +3 -2
  47. package/dist/src/main-api/{IRenderDriver.js → ICoreDriver.js} +1 -1
  48. package/dist/src/main-api/ICoreDriver.js.map +1 -0
  49. package/dist/src/main-api/INode.d.ts +2 -2
  50. package/dist/src/main-api/RendererMain.d.ts +16 -6
  51. package/dist/src/main-api/RendererMain.js +9 -3
  52. package/dist/src/main-api/RendererMain.js.map +1 -1
  53. package/dist/src/render-drivers/main/{MainRenderDriver.d.ts → MainCoreDriver.d.ts} +3 -2
  54. package/dist/src/render-drivers/main/{MainRenderDriver.js → MainCoreDriver.js} +14 -4
  55. package/dist/src/render-drivers/main/MainCoreDriver.js.map +1 -0
  56. package/dist/src/render-drivers/threadx/{ThreadXRenderDriver.d.ts → ThreadXCoreDriver.d.ts} +4 -2
  57. package/dist/src/render-drivers/threadx/{ThreadXRenderDriver.js → ThreadXCoreDriver.js} +18 -4
  58. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +1 -0
  59. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +9 -0
  60. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +1 -1
  61. package/dist/src/render-drivers/threadx/worker/renderer.js +8 -0
  62. package/dist/src/render-drivers/threadx/worker/renderer.js.map +1 -1
  63. package/dist/src/utils.js +2 -1
  64. package/dist/src/utils.js.map +1 -1
  65. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  66. package/exports/main-api.ts +3 -3
  67. package/package.json +8 -3
  68. package/src/core/CoreNode.ts +107 -21
  69. package/src/core/CoreShaderManager.ts +11 -0
  70. package/src/core/CoreTextNode.ts +5 -58
  71. package/src/core/Stage.ts +65 -34
  72. package/src/core/animations/CoreAnimation.ts +47 -27
  73. package/src/core/lib/utils.ts +39 -13
  74. package/src/core/platform.ts +5 -4
  75. package/src/core/renderers/CoreRenderer.ts +1 -2
  76. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +14 -35
  77. package/src/core/renderers/webgl/shaders/DefaultShader.ts +4 -0
  78. package/src/core/renderers/webgl/shaders/DynamicShader.ts +5 -29
  79. package/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts +186 -0
  80. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +107 -50
  81. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +95 -44
  82. package/src/core/text-rendering/renderers/TextRenderer.ts +44 -16
  83. package/src/main-api/{IRenderDriver.ts → ICoreDriver.ts} +4 -2
  84. package/src/main-api/INode.ts +2 -2
  85. package/src/main-api/RendererMain.ts +23 -6
  86. package/src/render-drivers/main/{MainRenderDriver.ts → MainCoreDriver.ts} +17 -4
  87. package/src/render-drivers/threadx/{ThreadXRenderDriver.ts → ThreadXCoreDriver.ts} +23 -7
  88. package/src/render-drivers/threadx/ThreadXRendererMessage.ts +11 -0
  89. package/src/render-drivers/threadx/worker/renderer.ts +10 -0
  90. package/src/utils.ts +2 -1
  91. package/dist/src/main-api/IRenderDriver.js.map +0 -1
  92. package/dist/src/render-drivers/main/MainRenderDriver.js.map +0 -1
  93. package/dist/src/render-drivers/threadx/ThreadXRenderDriver.js.map +0 -1
@@ -37,7 +37,7 @@ import type {
37
37
  NodeTextureLoadedPayload,
38
38
  } from '../common/CommonTypes.js';
39
39
  import { EventEmitter } from '../common/EventEmitter.js';
40
- import type { Rect } from './lib/utils.js';
40
+ import { intersectRect, type Rect } from './lib/utils.js';
41
41
  import { Matrix3d } from './lib/Matrix3d.js';
42
42
 
43
43
  export interface CoreNodeProps {
@@ -86,18 +86,13 @@ export class CoreNode extends EventEmitter implements ICoreNode {
86
86
  readonly children: CoreNode[] = [];
87
87
  protected props: Required<CoreNodeProps>;
88
88
 
89
- /**
90
- * Recalculation type
91
- * 0 - no recalculation
92
- * 1 - alpha recalculation
93
- * 2 - translate recalculation
94
- * 4 - transform recalculation
95
- */
96
- public recalculationType = 6;
89
+ public recalculationType = 0;
97
90
  public hasUpdates = true;
98
91
  public globalTransform?: Matrix3d;
99
92
  public scaleRotateTransform?: Matrix3d;
100
93
  public localTransform?: Matrix3d;
94
+ public clippingRect: Rect | null = null;
95
+ private parentClippingRect: Rect | null = null;
101
96
 
102
97
  private isComplex = false;
103
98
 
@@ -179,27 +174,46 @@ export class CoreNode extends EventEmitter implements ICoreNode {
179
174
  }
180
175
 
181
176
  setHasUpdates(): void {
182
- if (!this.props.alpha) {
183
- return;
184
- }
185
177
  this.hasUpdates = true;
186
- let p = this?.props.parent;
178
+ }
179
+
180
+ setChildrenHasUpdates(): void {
181
+ this.children.forEach((child) => {
182
+ child.setRecalculationType(2);
183
+ });
184
+ }
187
185
 
188
- while (p) {
189
- p.hasUpdates = true;
190
- p = p?.props.parent;
186
+ setParentHasUpdates(): void {
187
+ if (!this.props.parent) {
188
+ return;
191
189
  }
190
+
191
+ this.props.parent.setRecalculationType(1);
192
192
  }
193
193
 
194
194
  /**
195
+ * Change types types is used to determine the scope of the changes being applied
195
196
  * 1 - alpha recalculation
196
197
  * 2 - translate recalculation
197
198
  * 4 - transform recalculation
199
+ * 8 - z-index recalculation
200
+ *
198
201
  * @param type
199
202
  */
200
203
  setRecalculationType(type: number): void {
201
204
  this.recalculationType |= type;
202
205
  this.setHasUpdates();
206
+
207
+ // always forcing parent updates so the root will have an hasUpdates flag
208
+ this.setParentHasUpdates();
209
+
210
+ if (type & 4) {
211
+ this.setChildrenHasUpdates();
212
+ }
213
+ }
214
+
215
+ sortChildren() {
216
+ this.children.sort((a, b) => a.zIndex - b.zIndex);
203
217
  }
204
218
 
205
219
  updateScaleRotateTransform() {
@@ -235,7 +249,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
235
249
  * @todo: test for correct calculation flag
236
250
  * @param delta
237
251
  */
238
- update(delta: number): void {
252
+ update(delta: number, parentClippingRect: Rect | null = null): void {
239
253
  assertTruthy(this.localTransform);
240
254
  const parentGlobalTransform = this.parent?.globalTransform;
241
255
  if (parentGlobalTransform) {
@@ -250,12 +264,19 @@ export class CoreNode extends EventEmitter implements ICoreNode {
250
264
  );
251
265
  }
252
266
 
267
+ this.calculateClippingRect(parentClippingRect);
268
+
253
269
  if (this.children.length) {
254
270
  this.children.forEach((child) => {
255
- child.update(delta);
271
+ child.update(delta, this.clippingRect);
256
272
  });
257
273
  }
258
274
 
275
+ if (this.recalculationType & 8) {
276
+ // reorder z-index
277
+ this.sortChildren();
278
+ }
279
+
259
280
  // reset update flag
260
281
  this.hasUpdates = false;
261
282
 
@@ -263,7 +284,55 @@ export class CoreNode extends EventEmitter implements ICoreNode {
263
284
  this.recalculationType = 0;
264
285
  }
265
286
 
266
- renderQuads(renderer: CoreRenderer, clippingRect: Rect | null): void {
287
+ /**
288
+ * This function calculates the clipping rectangle for a node.
289
+ *
290
+ * If the node's globalTransform is not set, the function returns immediately.
291
+ * If the node's props do not require clipping and there is no parent clipping rectangle, the node's clipping rectangle is set to null.
292
+ * If the parent clipping rectangle has not changed and the node's clipping rectangle is already set, the function returns immediately.
293
+ *
294
+ * The function then checks if the node is rotated. If the node requires clipping and is not rotated, a new clipping rectangle is created based on the node's global transform and dimensions.
295
+ * If a parent clipping rectangle exists, it is intersected with the node's clipping rectangle (if it exists), or replaces the node's clipping rectangle.
296
+ *
297
+ * Finally, the node's parentClippingRect and clippingRect properties are updated.
298
+ */
299
+ calculateClippingRect(parentClippingRect: Rect | null = null) {
300
+ if (!this.globalTransform) {
301
+ return;
302
+ }
303
+
304
+ if (!this.props.clipping && !parentClippingRect) {
305
+ this.clippingRect = null;
306
+ return;
307
+ }
308
+
309
+ if (this.parentClippingRect === parentClippingRect && this.clippingRect) {
310
+ return;
311
+ }
312
+
313
+ const gt = this.globalTransform;
314
+ const isRotated = gt.tb !== 0 || gt.tc !== 0;
315
+
316
+ let clippingRect: Rect | null =
317
+ this.props.clipping && !isRotated
318
+ ? {
319
+ x: gt.tx,
320
+ y: gt.ty,
321
+ width: this.width * gt.ta,
322
+ height: this.height * gt.td,
323
+ }
324
+ : null;
325
+ if (parentClippingRect && clippingRect) {
326
+ clippingRect = intersectRect(parentClippingRect, clippingRect);
327
+ } else if (parentClippingRect) {
328
+ clippingRect = parentClippingRect;
329
+ }
330
+
331
+ this.parentClippingRect = parentClippingRect;
332
+ this.clippingRect = clippingRect;
333
+ }
334
+
335
+ renderQuads(renderer: CoreRenderer): void {
267
336
  const {
268
337
  width,
269
338
  height,
@@ -276,12 +345,12 @@ export class CoreNode extends EventEmitter implements ICoreNode {
276
345
  shader,
277
346
  shaderProps,
278
347
  } = this.props;
279
- const { zIndex, worldAlpha, globalTransform: gt } = this;
348
+ const { zIndex, worldAlpha, globalTransform: gt, clippingRect } = this;
280
349
 
281
350
  assertTruthy(gt);
282
351
 
283
352
  // add to list of renderables to be sorted before rendering
284
- renderer.addRenderable({
353
+ renderer.addQuad({
285
354
  width,
286
355
  height,
287
356
  colorTl,
@@ -494,6 +563,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
494
563
 
495
564
  set alpha(value: number) {
496
565
  this.props.alpha = value;
566
+ this.setRecalculationType(1);
497
567
  }
498
568
 
499
569
  get worldAlpha(): number {
@@ -509,6 +579,8 @@ export class CoreNode extends EventEmitter implements ICoreNode {
509
579
 
510
580
  set clipping(value: boolean) {
511
581
  this.props.clipping = value;
582
+ this.clippingRect = null;
583
+ this.setRecalculationType(4);
512
584
  }
513
585
 
514
586
  get color(): number {
@@ -528,6 +600,8 @@ export class CoreNode extends EventEmitter implements ICoreNode {
528
600
  this.colorBr = value;
529
601
  }
530
602
  this.props.color = value;
603
+
604
+ this.setRecalculationType(2);
531
605
  }
532
606
 
533
607
  get colorTop(): number {
@@ -540,6 +614,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
540
614
  this.colorTr = value;
541
615
  }
542
616
  this.props.colorTop = value;
617
+ this.setRecalculationType(2);
543
618
  }
544
619
 
545
620
  get colorBottom(): number {
@@ -552,6 +627,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
552
627
  this.colorBr = value;
553
628
  }
554
629
  this.props.colorBottom = value;
630
+ this.setRecalculationType(2);
555
631
  }
556
632
 
557
633
  get colorLeft(): number {
@@ -564,6 +640,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
564
640
  this.colorBl = value;
565
641
  }
566
642
  this.props.colorLeft = value;
643
+ this.setRecalculationType(2);
567
644
  }
568
645
 
569
646
  get colorRight(): number {
@@ -576,6 +653,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
576
653
  this.colorBr = value;
577
654
  }
578
655
  this.props.colorRight = value;
656
+ this.setRecalculationType(2);
579
657
  }
580
658
 
581
659
  get colorTl(): number {
@@ -584,6 +662,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
584
662
 
585
663
  set colorTl(value: number) {
586
664
  this.props.colorTl = value;
665
+ this.setRecalculationType(2);
587
666
  }
588
667
 
589
668
  get colorTr(): number {
@@ -592,6 +671,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
592
671
 
593
672
  set colorTr(value: number) {
594
673
  this.props.colorTr = value;
674
+ this.setRecalculationType(2);
595
675
  }
596
676
 
597
677
  get colorBl(): number {
@@ -600,6 +680,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
600
680
 
601
681
  set colorBl(value: number) {
602
682
  this.props.colorBl = value;
683
+ this.setRecalculationType(2);
603
684
  }
604
685
 
605
686
  get colorBr(): number {
@@ -608,6 +689,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
608
689
 
609
690
  set colorBr(value: number) {
610
691
  this.props.colorBr = value;
692
+ this.setRecalculationType(2);
611
693
  }
612
694
 
613
695
  // we're only interested in parent zIndex to test
@@ -633,6 +715,7 @@ export class CoreNode extends EventEmitter implements ICoreNode {
633
715
 
634
716
  set zIndex(value: number) {
635
717
  this.props.zIndex = value;
718
+ this.props.parent?.setRecalculationType(8);
636
719
  }
637
720
 
638
721
  get parent(): CoreNode | null {
@@ -655,7 +738,10 @@ export class CoreNode extends EventEmitter implements ICoreNode {
655
738
  }
656
739
  if (newParent) {
657
740
  newParent.children.push(this);
741
+ // force parent to recalculate z-index for its children
742
+ newParent.setRecalculationType(8);
658
743
  }
744
+
659
745
  this.updateScaleRotateTransform();
660
746
  }
661
747
  //#endregion Properties
@@ -41,6 +41,7 @@ import { GlitchEffect } from './renderers/webgl/shaders/effects/GlitchEffect.js'
41
41
  import { FadeOutEffect } from './renderers/webgl/shaders/effects/FadeOutEffect.js';
42
42
  import { RadialGradientEffect } from './renderers/webgl/shaders/effects/RadialGradientEffect.js';
43
43
  import type { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js';
44
+ import { RadialProgressEffect } from './renderers/webgl/shaders/effects/RadialProgressEffect.js';
44
45
 
45
46
  export interface ShaderMap {
46
47
  DefaultShader: typeof DefaultShader;
@@ -67,6 +68,7 @@ export interface EffectMap {
67
68
  radialGradient: typeof RadialGradientEffect;
68
69
  grayscale: typeof GrayscaleEffect;
69
70
  glitch: typeof GlitchEffect;
71
+ radialProgress: typeof RadialProgressEffect;
70
72
  }
71
73
 
72
74
  export class CoreShaderManager {
@@ -95,6 +97,7 @@ export class CoreShaderManager {
95
97
  this.registerEffectType('grayscale', GrayscaleEffect);
96
98
  this.registerEffectType('glitch', GlitchEffect);
97
99
  this.registerEffectType('radius', RadiusEffect);
100
+ this.registerEffectType('radialProgress', RadialProgressEffect);
98
101
  }
99
102
 
100
103
  registerShaderType<Type extends keyof ShaderMap>(
@@ -111,6 +114,14 @@ export class CoreShaderManager {
111
114
  this.effectConstructors[effectType] = effectClass;
112
115
  }
113
116
 
117
+ getRegisteredEffects(): Partial<EffectMap> {
118
+ return this.effectConstructors;
119
+ }
120
+
121
+ getRegisteredShaders(): Partial<ShaderMap> {
122
+ return this.shConstructors;
123
+ }
124
+
114
125
  loadShader<Type extends keyof ShaderMap>(
115
126
  shType: Type,
116
127
  props?: ExtractProps<ShaderMap[Type]>,
@@ -48,13 +48,11 @@ type ICoreTextNode = Omit<
48
48
  export class CoreTextNode extends CoreNode implements ICoreTextNode {
49
49
  textRenderer: TextRenderer;
50
50
  trState: TextRendererState;
51
- updateScheduled: boolean;
52
51
  private _textRendererOverride: CoreTextNodeProps['textRendererOverride'] =
53
52
  null;
54
53
 
55
54
  constructor(stage: Stage, props: CoreTextNodeProps) {
56
55
  super(stage, props);
57
- this.updateScheduled = false;
58
56
  this._textRendererOverride = props.textRendererOverride;
59
57
  const { resolvedTextRenderer, textRendererState } =
60
58
  this.resolveTextRendererAndState(
@@ -67,8 +65,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
67
65
  color: props.color,
68
66
  zIndex: props.zIndex,
69
67
  contain: props.contain,
70
- scaleX: props.scaleX,
71
- scaleY: props.scaleY,
72
68
  scrollable: props.scrollable,
73
69
  scrollY: props.scrollY,
74
70
  offsetY: props.offsetY,
@@ -128,7 +124,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
128
124
 
129
125
  override set width(value: number) {
130
126
  this.textRenderer.set.width(this.trState, value);
131
- this.updateText();
132
127
  }
133
128
 
134
129
  override get height(): number {
@@ -137,7 +132,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
137
132
 
138
133
  override set height(value: number) {
139
134
  this.textRenderer.set.height(this.trState, value);
140
- this.updateText();
141
135
  }
142
136
 
143
137
  override get color(): number {
@@ -146,7 +140,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
146
140
 
147
141
  override set color(value: number) {
148
142
  this.textRenderer.set.color(this.trState, value);
149
- this.updateText();
150
143
  }
151
144
 
152
145
  get text(): string {
@@ -155,7 +148,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
155
148
 
156
149
  set text(value: string) {
157
150
  this.textRenderer.set.text(this.trState, value);
158
- this.updateText();
159
151
  }
160
152
 
161
153
  get textRendererOverride(): CoreTextNodeProps['textRendererOverride'] {
@@ -177,7 +169,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
177
169
 
178
170
  set fontSize(value: CoreTextNodeProps['fontSize']) {
179
171
  this.textRenderer.set.fontSize(this.trState, value);
180
- this.updateText();
181
172
  }
182
173
 
183
174
  get fontFamily(): CoreTextNodeProps['fontFamily'] {
@@ -186,7 +177,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
186
177
 
187
178
  set fontFamily(value: CoreTextNodeProps['fontFamily']) {
188
179
  this.textRenderer.set.fontFamily(this.trState, value);
189
- this.updateText();
190
180
  }
191
181
 
192
182
  get fontStretch(): CoreTextNodeProps['fontStretch'] {
@@ -195,7 +185,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
195
185
 
196
186
  set fontStretch(value: CoreTextNodeProps['fontStretch']) {
197
187
  this.textRenderer.set.fontStretch(this.trState, value);
198
- this.updateText();
199
188
  }
200
189
 
201
190
  get fontStyle(): CoreTextNodeProps['fontStyle'] {
@@ -204,7 +193,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
204
193
 
205
194
  set fontStyle(value: CoreTextNodeProps['fontStyle']) {
206
195
  this.textRenderer.set.fontStyle(this.trState, value);
207
- this.updateText();
208
196
  }
209
197
 
210
198
  get fontWeight(): CoreTextNodeProps['fontWeight'] {
@@ -213,7 +201,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
213
201
 
214
202
  set fontWeight(value: CoreTextNodeProps['fontWeight']) {
215
203
  this.textRenderer.set.fontWeight(this.trState, value);
216
- this.updateText();
217
204
  }
218
205
 
219
206
  get textAlign(): CoreTextNodeProps['textAlign'] {
@@ -222,7 +209,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
222
209
 
223
210
  set textAlign(value: CoreTextNodeProps['textAlign']) {
224
211
  this.textRenderer.set.textAlign(this.trState, value);
225
- this.updateText();
226
212
  }
227
213
 
228
214
  get contain(): CoreTextNodeProps['contain'] {
@@ -231,7 +217,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
231
217
 
232
218
  set contain(value: CoreTextNodeProps['contain']) {
233
219
  this.textRenderer.set.contain(this.trState, value);
234
- this.updateText();
235
220
  }
236
221
 
237
222
  get scrollable(): CoreTextNodeProps['scrollable'] {
@@ -240,7 +225,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
240
225
 
241
226
  set scrollable(value: CoreTextNodeProps['scrollable']) {
242
227
  this.textRenderer.set.scrollable(this.trState, value);
243
- this.updateText();
244
228
  }
245
229
 
246
230
  get scrollY(): CoreTextNodeProps['scrollY'] {
@@ -249,7 +233,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
249
233
 
250
234
  set scrollY(value: CoreTextNodeProps['scrollY']) {
251
235
  this.textRenderer.set.scrollY(this.trState, value);
252
- this.updateText();
253
236
  }
254
237
 
255
238
  get offsetY(): CoreTextNodeProps['offsetY'] {
@@ -258,7 +241,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
258
241
 
259
242
  set offsetY(value: CoreTextNodeProps['offsetY']) {
260
243
  this.textRenderer.set.offsetY(this.trState, value);
261
- this.updateText();
262
244
  }
263
245
 
264
246
  get letterSpacing(): CoreTextNodeProps['letterSpacing'] {
@@ -267,7 +249,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
267
249
 
268
250
  set letterSpacing(value: CoreTextNodeProps['letterSpacing']) {
269
251
  this.textRenderer.set.letterSpacing(this.trState, value);
270
- this.updateText();
271
252
  }
272
253
 
273
254
  get debug(): CoreTextNodeProps['debug'] {
@@ -276,41 +257,24 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
276
257
 
277
258
  set debug(value: CoreTextNodeProps['debug']) {
278
259
  this.textRenderer.set.debug(this.trState, value);
279
- this.updateText();
280
260
  }
281
261
 
282
- override update(delta: number) {
283
- super.update(delta);
262
+ override update(delta: number, parentClippingRect: Rect | null = null) {
263
+ super.update(delta, parentClippingRect);
284
264
 
285
265
  assertTruthy(this.globalTransform);
286
266
 
287
267
  // globalTransform is updated in super.update(delta)
288
268
  this.textRenderer.set.x(this.trState, this.globalTransform.tx);
289
269
  this.textRenderer.set.y(this.trState, this.globalTransform.ty);
290
-
291
- if (this.trState.status === 'loading') {
292
- // Update the text state now
293
- this.textRenderer.updateState(this.trState);
294
- }
295
- }
296
-
297
- private updateText() {
298
- if (this.updateScheduled) {
299
- return;
300
- }
301
- this.updateScheduled = true;
302
- queueMicrotask(() => {
303
- this.updateScheduled = false;
304
- this.textRenderer.updateState(this.trState);
305
- });
306
270
  }
307
271
 
308
- override renderQuads(renderer: CoreRenderer, clippingRect: Rect | null) {
272
+ override renderQuads(renderer: CoreRenderer) {
309
273
  assertTruthy(this.globalTransform);
310
274
  this.textRenderer.renderQuads(
311
275
  this.trState,
312
276
  this.globalTransform,
313
- clippingRect,
277
+ this.clippingRect,
314
278
  this.worldAlpha,
315
279
  );
316
280
  }
@@ -343,26 +307,9 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
343
307
  });
344
308
  }
345
309
 
346
- // Forward basic status events from the text renderer state
347
- textRendererState.emitter.on('loading', () => {
348
- // This event will be fired only once between the `loading` and `loaded`
349
- // event ONLY if the font is not already loaded
350
- textRendererState.emitter.once('fontLoaded', () => {
351
- // When it's fired we must run update to make sure the text renders
352
- this.updateText();
353
- });
354
-
355
- textRendererState.emitter.once('loaded', () => {
356
- // Make sure we stop listening for fontLoaded events
357
- textRendererState.emitter.off('fontLoaded');
358
- });
359
- });
360
-
361
310
  textRendererState.emitter.on('loaded', this.onTextLoaded);
362
311
  textRendererState.emitter.on('failed', this.onTextFailed);
363
- this.updateText();
364
-
365
- // TODO: Handle text renderer errors
312
+ resolvedTextRenderer.scheduleUpdateState(textRendererState);
366
313
 
367
314
  return {
368
315
  resolvedTextRenderer,
package/src/core/Stage.ts CHANGED
@@ -35,7 +35,7 @@ import type {
35
35
  } from './text-rendering/renderers/TextRenderer.js';
36
36
  import { SdfTextRenderer } from './text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js';
37
37
  import { CanvasTextRenderer } from './text-rendering/renderers/CanvasTextRenderer.js';
38
- import { intersectRect, type Rect } from './lib/utils.js';
38
+ import { EventEmitter } from '../common/EventEmitter.js';
39
39
 
40
40
  export interface StageOptions {
41
41
  rootId: number;
@@ -45,6 +45,7 @@ export interface StageOptions {
45
45
  devicePhysicalPixelRatio: number;
46
46
  canvas: HTMLCanvasElement | OffscreenCanvas;
47
47
  clearColor: number;
48
+ fpsUpdateInterval: number;
48
49
  debug?: {
49
50
  monitorTextureCache?: boolean;
50
51
  };
@@ -53,7 +54,7 @@ export interface StageOptions {
53
54
  const bufferMemory = 2e6;
54
55
  const autoStart = true;
55
56
 
56
- export class Stage {
57
+ export class Stage extends EventEmitter {
57
58
  /// Module Instances
58
59
  public readonly animationManager: AnimationManager;
59
60
  public readonly txManager: CoreTextureManager;
@@ -67,11 +68,14 @@ export class Stage {
67
68
  deltaTime = 0;
68
69
  lastFrameTime = 0;
69
70
  currentFrameTime = 0;
71
+ private fpsNumFrames = 0;
72
+ private fpsElapsedTime = 0;
70
73
 
71
74
  /**
72
75
  * Stage constructor
73
76
  */
74
77
  constructor(readonly options: StageOptions) {
78
+ super();
75
79
  const { canvas, clearColor, rootId, debug, appWidth, appHeight } = options;
76
80
  this.txManager = new CoreTextureManager();
77
81
  this.shManager = new CoreShaderManager();
@@ -149,11 +153,12 @@ export class Stage {
149
153
  startLoop(this);
150
154
  }
151
155
  }
156
+
152
157
  /**
153
- * Start a new frame draw
158
+ * Update animations
154
159
  */
155
- drawFrame() {
156
- const { renderer, scene, animationManager } = this;
160
+ updateAnimations() {
161
+ const { scene, animationManager } = this;
157
162
  if (!scene?.root) {
158
163
  return;
159
164
  }
@@ -166,48 +171,74 @@ export class Stage {
166
171
 
167
172
  // step animation
168
173
  animationManager.update(this.deltaTime);
174
+ }
175
+
176
+ /**
177
+ * Check if the scene has updates
178
+ */
179
+ hasSceneUpdates() {
180
+ const { scene } = this;
181
+
182
+ if (!scene?.root) {
183
+ return false;
184
+ }
185
+
186
+ return scene?.root?.hasUpdates;
187
+ }
188
+
189
+ /**
190
+ * Start a new frame draw
191
+ */
192
+ drawFrame() {
193
+ const { renderer, scene } = this;
194
+ if (!scene?.root) {
195
+ return;
196
+ }
169
197
 
170
198
  // reset and clear viewport
171
- renderer?.reset();
199
+ scene?.root?.update(this.deltaTime);
172
200
 
173
201
  // test if we need to update the scene
174
- if (scene?.root?.hasUpdates) {
175
- scene?.root?.update(this.deltaTime);
176
- }
202
+ renderer?.reset();
177
203
 
178
204
  this.addQuads(scene.root);
179
205
 
180
- renderer?.sortRenderables();
181
206
  renderer?.render();
207
+
208
+ // If there's an FPS update interval, emit the FPS update event
209
+ // when the specified interval has elapsed.
210
+ const { fpsUpdateInterval } = this.options;
211
+ if (fpsUpdateInterval) {
212
+ this.fpsNumFrames++;
213
+ this.fpsElapsedTime += this.deltaTime;
214
+ if (this.fpsElapsedTime >= fpsUpdateInterval) {
215
+ const fps = Math.round(
216
+ (this.fpsNumFrames * 1000) / this.fpsElapsedTime,
217
+ );
218
+ this.fpsNumFrames = 0;
219
+ this.fpsElapsedTime = 0;
220
+ this.emit('fpsUpdate', fps);
221
+ }
222
+ }
182
223
  }
183
224
 
184
- addQuads(node: CoreNode, parentClippingRect: Rect | null = null) {
225
+ addQuads(node: CoreNode) {
185
226
  assertTruthy(this.renderer && node.globalTransform);
186
- const gt = node.globalTransform;
187
- const isRotated = gt.tb !== 0 || gt.tc !== 0;
188
-
189
- let clippingRect: Rect | null =
190
- node.clipping && !isRotated
191
- ? {
192
- x: gt.tx,
193
- y: gt.ty,
194
- width: node.width * gt.ta,
195
- height: node.height * gt.td,
196
- }
197
- : null;
198
- if (parentClippingRect && clippingRect) {
199
- clippingRect = intersectRect(parentClippingRect, clippingRect);
200
- } else if (parentClippingRect) {
201
- clippingRect = parentClippingRect;
202
- }
203
227
 
204
- node.renderQuads(this.renderer, clippingRect);
205
- node.children.forEach((child) => {
206
- if (child.worldAlpha === 0) {
207
- return;
228
+ node.renderQuads(this.renderer);
229
+ for (let i = 0; i < node.children.length; i++) {
230
+ const child = node.children[i];
231
+
232
+ if (!child) {
233
+ continue;
208
234
  }
209
- this.addQuads(child, clippingRect);
210
- });
235
+
236
+ if (child?.worldAlpha === 0) {
237
+ continue;
238
+ }
239
+
240
+ this.addQuads(child);
241
+ }
211
242
  }
212
243
 
213
244
  /**