@mirage-engine/core 0.3.21 → 0.3.22

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.
@@ -2,25 +2,28 @@ import { StyleData } from "@mirage-engine/dom-tracker";
2
2
  import { MeshRegistry } from "../store/MeshRegistry";
3
3
  import { Painter } from "@mirage-engine/painter";
4
4
  import * as THREE from "three";
5
+ import { WASM_STRIDE, OFFSET_LOCAL_X, OFFSET_LOCAL_Y, OFFSET_PARENT, OFFSET_WORLD_X, OFFSET_WORLD_Y } from "../types";
5
6
 
6
7
  export function animateMeshByData(
7
8
  registry: MeshRegistry,
8
9
  data: Map<HTMLElement, StyleData>,
10
+ sharedArray?: Float32Array,
9
11
  ) {
10
12
  if (data.size === 0) return;
11
13
 
14
+ const elementsToUpdateLayout = new Set<HTMLElement>();
15
+
12
16
  data.forEach((styleData, element) => {
13
17
  const mesh = registry.get(element);
14
18
  if (!mesh || !mesh.userData.basePosition) return;
15
19
 
20
+ // Fast-path styling updates (colors, opacity, etc.)
16
21
  Painter.forceUpdateUniforms(mesh.material as THREE.ShaderMaterial, {
17
22
  backgroundColor: styleData.backgroundColor,
18
23
  backgroundImage: styleData.backgroundImage,
19
24
  boxShadow: styleData.boxShadow,
20
25
  opacity: styleData.opacity,
21
- borderRadius:
22
- styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
23
- // width and height are no longer updated here, they are updated in syncMeshesByDOM
26
+ borderRadius: styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
24
27
  });
25
28
 
26
29
  if (mesh.userData.nativeMesh) {
@@ -31,6 +34,96 @@ export function animateMeshByData(
31
34
  opacity: styleData.opacity,
32
35
  borderRadius: styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
33
36
  });
37
+
38
+ if (styleData.scaleX !== undefined || styleData.scaleY !== undefined) {
39
+ const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
40
+ const currentScaleX = nativeMesh.scale.x;
41
+ const currentScaleY = nativeMesh.scale.y;
42
+ const currentScaleZ = nativeMesh.scale.z;
43
+ nativeMesh.scale.set(
44
+ styleData.scaleX !== undefined ? styleData.scaleX * currentScaleX : currentScaleX,
45
+ styleData.scaleY !== undefined ? styleData.scaleY * currentScaleY : currentScaleY,
46
+ styleData.scaleZ !== undefined ? styleData.scaleZ * currentScaleZ : currentScaleZ
47
+ );
48
+ }
49
+ }
50
+
51
+ if (styleData.layoutChanged || styleData.width !== undefined || styleData.height !== undefined) {
52
+ elementsToUpdateLayout.add(element);
53
+ // Add all descendants that are in the registry
54
+ const descendants = element.querySelectorAll('*');
55
+ for (let i = 0; i < descendants.length; i++) {
56
+ const desc = descendants[i] as HTMLElement;
57
+ if (registry.has(desc)) {
58
+ elementsToUpdateLayout.add(desc);
59
+ }
60
+ }
61
+ } else if (styleData.x !== undefined || styleData.y !== undefined) {
62
+ // Just transform update (x, y) - doesn't affect child layout natively!
63
+ if (sharedArray && mesh.userData.wasmIndex !== undefined) {
64
+ const offset = mesh.userData.wasmIndex * WASM_STRIDE;
65
+ if (styleData.x !== undefined && mesh.userData.initialLocalX !== undefined) {
66
+ sharedArray[offset + OFFSET_LOCAL_X] = mesh.userData.initialLocalX + styleData.x;
67
+ }
68
+ if (styleData.y !== undefined && mesh.userData.initialLocalY !== undefined) {
69
+ sharedArray[offset + OFFSET_LOCAL_Y] = mesh.userData.initialLocalY + styleData.y;
70
+ }
71
+ }
72
+ }
73
+ });
74
+
75
+ // Now, process all layout updates efficiently!
76
+ elementsToUpdateLayout.forEach((element) => {
77
+ const mesh = registry.get(element);
78
+ if (!mesh) return;
79
+
80
+ const rect = element.getBoundingClientRect();
81
+
82
+ if (mesh.userData.domRect) {
83
+ mesh.userData.domRect.width = rect.width;
84
+ mesh.userData.domRect.height = rect.height;
85
+ }
86
+
87
+ let pad = (mesh.material as THREE.ShaderMaterial).userData?.shadowPadding || 0;
88
+ mesh.scale.set(rect.width + pad * 2, rect.height + pad * 2, 1);
89
+
90
+ Painter.forceUpdateUniforms(mesh.material as THREE.ShaderMaterial, {
91
+ width: rect.width,
92
+ height: rect.height,
93
+ });
94
+
95
+ if (sharedArray && mesh.userData.wasmIndex !== undefined) {
96
+ const offset = mesh.userData.wasmIndex * WASM_STRIDE;
97
+ const myWorldX = rect.left + (mesh.userData.isFixed ? 0 : window.scrollX);
98
+ const myWorldY = rect.top + (mesh.userData.isFixed ? 0 : window.scrollY);
99
+
100
+ const parentIndex = sharedArray[offset + OFFSET_PARENT];
101
+ let parentWorldX = 0;
102
+ let parentWorldY = 0;
103
+ if (parentIndex !== -1) {
104
+ const parentOffset = parentIndex * WASM_STRIDE;
105
+ parentWorldX = sharedArray[parentOffset + OFFSET_WORLD_X];
106
+ parentWorldY = sharedArray[parentOffset + OFFSET_WORLD_Y];
107
+ }
108
+
109
+ sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
110
+ sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
111
+ }
112
+
113
+ if (mesh.userData.nativeMesh) {
114
+ const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
115
+ let nPad = (nativeMesh.material as THREE.ShaderMaterial).userData?.shadowPadding || 0;
116
+ nativeMesh.scale.set(rect.width + nPad * 2, rect.height + nPad * 2, 1);
117
+
118
+ Painter.forceUpdateUniforms(nativeMesh.material as THREE.ShaderMaterial, {
119
+ width: rect.width,
120
+ height: rect.height,
121
+ });
122
+
123
+ if (mesh.userData.nativeRect) {
124
+ mesh.userData.nativeRect.width = rect.width;
125
+ mesh.userData.nativeRect.height = rect.height;
126
+ }
34
127
  }
35
128
  });
36
129
  }
@@ -47,8 +47,8 @@ export class Engine {
47
47
  this.syncer = new Syncer(this.target, this.renderer, this.registry, config);
48
48
  }
49
49
 
50
- public start() {
51
- this.syncer.start();
50
+ public async start() {
51
+ await this.syncer.start();
52
52
  }
53
53
 
54
54
  public stop() {
@@ -2,15 +2,18 @@ import { CoreConfig } from "../types/config";
2
2
  import { Renderer } from "../renderer/Renderer";
3
3
  import { MeshRegistry } from "../store/MeshRegistry";
4
4
  import { extractSceneGraph } from "../dom/Extractor";
5
- import { Visibility, USER_LAYER, ATTR_TRAVEL } from "../types";
5
+ import { Visibility, USER_LAYER, ATTR_TRAVEL, WASM_STRIDE } from "../types";
6
6
  import { animateMeshByData } from "../animation/Animator";
7
7
  import { Tracker } from "@mirage-engine/dom-tracker";
8
+ import { WasmSynchronizer } from "../wasm/WasmSynchronizer";
8
9
 
9
10
  export class Syncer {
10
11
  private target: HTMLElement;
11
12
  private renderer: Renderer;
12
13
  private registry: MeshRegistry;
13
14
  private isTravelEnabled: boolean = false;
15
+ private wasmSync: WasmSynchronizer;
16
+ private lastWasmNodeCount: number = 0;
14
17
 
15
18
  public tracker: Tracker;
16
19
 
@@ -23,6 +26,7 @@ export class Syncer {
23
26
  this.target = target;
24
27
  this.renderer = renderer;
25
28
  this.registry = registry;
29
+ this.wasmSync = new WasmSynchronizer();
26
30
 
27
31
  // Use Tracker from dom-tracker
28
32
  this.tracker = new Tracker(target, {
@@ -38,31 +42,55 @@ export class Syncer {
38
42
  this.renderer.createRenderTarget();
39
43
  }
40
44
 
45
+ const sharedArray = this.wasmSync.sharedArray;
46
+ this.renderer.updateScroll();
47
+ const extractContext = {
48
+ sharedArray: sharedArray || new Float32Array(),
49
+ currentIndex: 0,
50
+ scrollX: (this.renderer as any).getScrollX(),
51
+ scrollY: (this.renderer as any).getScrollY(),
52
+ };
53
+
41
54
  const sceneGraph = extractSceneGraph(
42
55
  this.target,
43
56
  pendingMask,
44
57
  USER_LAYER as Visibility,
45
58
  1,
46
59
  0,
47
- this.renderer.qualityFactor
60
+ this.renderer.qualityFactor,
61
+ undefined,
62
+ undefined,
63
+ undefined,
64
+ extractContext
48
65
  );
49
66
 
50
67
  if (sceneGraph) {
68
+ this.lastWasmNodeCount = extractContext.currentIndex;
51
69
  this.renderer.syncScene(sceneGraph, pendingDeletions);
70
+
71
+ if (sharedArray) {
72
+ this.renderer.saveInitialLocals(sharedArray);
73
+ }
52
74
  }
53
75
  });
54
76
 
55
77
  this.tracker.onStyleChange.add((pendingStyles) => {
56
- animateMeshByData(this.registry, pendingStyles);
78
+ animateMeshByData(this.registry, pendingStyles, this.wasmSync.sharedArray || undefined);
57
79
  });
58
80
 
59
81
  this.tracker.onRender.add(() => {
60
- this.renderer.syncMeshesByDOM();
82
+ this.renderer.updateScroll();
83
+ this.wasmSync.updatePhysics(this.lastWasmNodeCount);
84
+ if (this.wasmSync.sharedArray) {
85
+ this.renderer.syncMeshesByWasm(this.wasmSync.sharedArray);
86
+ }
61
87
  this.renderer.render();
62
88
  });
63
89
  }
64
90
 
65
- public start() {
91
+ public async start() {
92
+ // 10000개 노드 분량의 공유 메모리를 넉넉하게 선행 할당 (1-Pass 위함)
93
+ await this.wasmSync.initialize(10000 * WASM_STRIDE);
66
94
  this.tracker.start();
67
95
  }
68
96
 
@@ -14,6 +14,10 @@ import {
14
14
  ATTR_SELECT,
15
15
  ATTR_TRAVEL,
16
16
  ATTR_SHADER,
17
+ WASM_STRIDE,
18
+ OFFSET_PARENT,
19
+ OFFSET_LOCAL_X,
20
+ OFFSET_LOCAL_Y,
17
21
  } from "../types";
18
22
 
19
23
  import { BoxStyles, TextStyles, ShaderHooks } from "@mirage-engine/painter";
@@ -210,7 +214,15 @@ export function extractSceneGraph(
210
214
  inheritedNativeLayer?: number,
211
215
  inheritedNativeStyles?: any,
212
216
  inheritedClipElements?: HTMLElement[],
217
+ extractContext?: { sharedArray: Float32Array; currentIndex: number; scrollX?: number; scrollY?: number },
218
+ parentWasmIndex: number = -1,
219
+ parentWorldX: number = 0,
220
+ parentWorldY: number = 0,
221
+ inheritedIsFixed: boolean = false
213
222
  ): SceneNode | null {
223
+ const scrollX = extractContext?.scrollX ?? window.scrollX;
224
+ const scrollY = extractContext?.scrollY ?? window.scrollY;
225
+
214
226
  // Check text node
215
227
  if (sourceNode.nodeType === Node.TEXT_NODE) {
216
228
  const textNode = sourceNode as Text;
@@ -228,20 +240,34 @@ export function extractSceneGraph(
228
240
  const computed = parent ? window.getComputedStyle(parent) : null;
229
241
  if (!computed) return null;
230
242
 
243
+ const myIsFixed = inheritedIsFixed || computed.position === "fixed";
244
+
231
245
  // Calculate overall bounding box of the lines
232
246
  const minX = Math.min(...textLines.map((l) => l.rect.left));
233
247
  const minY = Math.min(...textLines.map((l) => l.rect.top));
234
248
  const maxX = Math.max(...textLines.map((l) => l.rect.left + l.rect.width));
235
249
  const maxY = Math.max(...textLines.map((l) => l.rect.top + l.rect.height));
236
250
 
251
+ const myWorldX = minX + (myIsFixed ? 0 : scrollX);
252
+ const myWorldY = minY + (myIsFixed ? 0 : scrollY);
253
+
254
+ let myWasmIndex = -1;
255
+ if (extractContext && extractContext.sharedArray) {
256
+ myWasmIndex = extractContext.currentIndex++;
257
+ const offset = myWasmIndex * WASM_STRIDE;
258
+ extractContext.sharedArray[offset + OFFSET_PARENT] = parentWasmIndex;
259
+ extractContext.sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
260
+ extractContext.sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
261
+ }
262
+
237
263
  // Create SceneNode for the text node
238
264
  return {
239
265
  id: Math.random().toString(36).substring(2, 9),
240
266
  type: "TEXT",
241
267
  element: textNode as unknown as HTMLElement,
242
268
  rect: {
243
- x: minX + window.scrollX,
244
- y: minY + window.scrollY,
269
+ x: minX + (myIsFixed ? 0 : scrollX),
270
+ y: minY + (myIsFixed ? 0 : scrollY),
245
271
  width: maxX - minX,
246
272
  height: maxY - minY,
247
273
  },
@@ -264,8 +290,8 @@ export function extractSceneGraph(
264
290
  textLines: textLines.map((l) => ({
265
291
  text: l.text.trim(),
266
292
  rect: {
267
- x: l.rect.left + window.scrollX,
268
- y: l.rect.top + window.scrollY,
293
+ x: l.rect.left + (myIsFixed ? 0 : scrollX),
294
+ y: l.rect.top + (myIsFixed ? 0 : scrollY),
269
295
  width: l.rect.width,
270
296
  height: l.rect.height,
271
297
  },
@@ -275,7 +301,7 @@ export function extractSceneGraph(
275
301
  visibility: inheritedFlow,
276
302
  isTraveler: false,
277
303
  captureLayer,
278
- isFixed: computed.position === "fixed",
304
+ isFixed: myIsFixed,
279
305
  nativeLayer: inheritedNativeLayer,
280
306
  nativeStyles: inheritedNativeStyles
281
307
  ? {
@@ -299,13 +325,14 @@ export function extractSceneGraph(
299
325
  : undefined,
300
326
  nativeRect: inheritedNativeStyles
301
327
  ? {
302
- x: minX + window.scrollX,
303
- y: minY + window.scrollY,
328
+ x: minX + (myIsFixed ? 0 : scrollX),
329
+ y: minY + (myIsFixed ? 0 : scrollY),
304
330
  width: maxX - minX,
305
331
  height: maxY - minY,
306
332
  }
307
333
  : undefined,
308
334
  clipElements: inheritedClipElements,
335
+ wasmIndex: myWasmIndex !== -1 ? myWasmIndex : undefined,
309
336
  children: [],
310
337
  };
311
338
  }
@@ -498,6 +525,20 @@ export function extractSceneGraph(
498
525
  return null;
499
526
  }
500
527
 
528
+ const myIsFixed = inheritedIsFixed || computed.position === "fixed";
529
+
530
+ const myWorldX = rect.left + (myIsFixed ? 0 : scrollX);
531
+ const myWorldY = rect.top + (myIsFixed ? 0 : scrollY);
532
+
533
+ let myWasmIndex = -1;
534
+ if (extractContext && extractContext.sharedArray) {
535
+ myWasmIndex = extractContext.currentIndex++;
536
+ const offset = myWasmIndex * WASM_STRIDE;
537
+ extractContext.sharedArray[offset + OFFSET_PARENT] = parentWasmIndex;
538
+ extractContext.sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
539
+ extractContext.sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
540
+ }
541
+
501
542
  // [TODO] dataset 방식으로 변경
502
543
  let id = element.getAttribute("data-mid");
503
544
  if (!id) {
@@ -627,6 +668,11 @@ export function extractSceneGraph(
627
668
  ? nativeParsedStyles
628
669
  : undefined,
629
670
  nextClipElements,
671
+ extractContext,
672
+ myWasmIndex,
673
+ myWorldX,
674
+ myWorldY,
675
+ myIsFixed
630
676
  );
631
677
  if (childNode) {
632
678
  children.push(childNode);
@@ -639,8 +685,8 @@ export function extractSceneGraph(
639
685
  type: "BOX",
640
686
  element,
641
687
  rect: {
642
- x: rect.left + window.scrollX,
643
- y: rect.top + window.scrollY,
688
+ x: rect.left + scrollX,
689
+ y: rect.top + scrollY,
644
690
  width: rect.width,
645
691
  height: rect.height,
646
692
  },
@@ -684,11 +730,11 @@ export function extractSceneGraph(
684
730
  x:
685
731
  nativeParsedStyles.x !== undefined
686
732
  ? parseFloat(nativeParsedStyles.x)
687
- : rect.left + window.scrollX,
733
+ : rect.left + scrollX,
688
734
  y:
689
735
  nativeParsedStyles.y !== undefined
690
736
  ? parseFloat(nativeParsedStyles.y)
691
- : rect.top + window.scrollY,
737
+ : rect.top + scrollY,
692
738
  width:
693
739
  nativeParsedStyles.width !== undefined
694
740
  ? parseFloat(nativeParsedStyles.width)
@@ -699,8 +745,9 @@ export function extractSceneGraph(
699
745
  : rect.height,
700
746
  }
701
747
  : undefined,
702
- isFixed: computed.position === "fixed",
748
+ isFixed: myIsFixed,
703
749
  clipElements: inheritedClipElements,
750
+ wasmIndex: myWasmIndex !== -1 ? myWasmIndex : undefined,
704
751
  children,
705
752
  shaderHooks,
706
753
  };
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Engine} from "./core/Engine";
2
2
  export * from "./types";
3
+ export * from "./wasm/WasmSynchronizer";