-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspawn3dStore.ts
More file actions
97 lines (77 loc) · 2.23 KB
/
spawn3dStore.ts
File metadata and controls
97 lines (77 loc) · 2.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { SvelteMap } from 'svelte/reactivity'
import type { Snippet } from 'svelte'
export type LockMode = 'element' | 'camera' | 'none'
export type Spawn3DItem = {
// DOM Metrics (Read-only for the renderer)
left: number
top: number
width: number
height: number
// State Flags
visible: boolean // Is the DOM element intersecting the viewport?
// User Config
lockAt: LockMode
distance: number
absoluteSizing: boolean
visibleOverride: boolean // Manual visibility control
autoHide: boolean // Should it hide when DOM is off-screen?
// Content
snippet?: Snippet<[Spawn3DItem]>
}
type MeasureFn = () => { left: number; top: number; width: number; height: number } | undefined
export class Spawn3DManager {
items = new SvelteMap<string, Spawn3DItem>()
// --- Batching Scheduler ---
private measureFns = new Map<string, MeasureFn>()
private dirtyIds = new Set<string>()
private raf = 0
register(id: string, data: Spawn3DItem) {
this.items.set(id, data)
}
setMeasureFn(id: string, measure: MeasureFn) {
this.measureFns.set(id, measure)
}
unregister(id: string) {
this.items.delete(id)
this.measureFns.delete(id)
this.dirtyIds.delete(id)
if (this.raf && this.dirtyIds.size === 0) {
cancelAnimationFrame(this.raf)
this.raf = 0
}
}
update(id: string, updates: Partial<Spawn3DItem>) {
if (this.items.has(id)) {
const current = this.items.get(id)!
this.items.set(id, { ...current, ...updates })
}
}
// Schedule a measurement for the next frame
markDirty(id: string) {
if (!this.items.has(id)) return
this.dirtyIds.add(id)
if (this.raf) return
this.raf = requestAnimationFrame(() => {
this.raf = 0
this.flushUpdates()
})
}
// The optimization: Separate READS from WRITES
private flushUpdates() {
const ids = Array.from(this.dirtyIds)
this.dirtyIds.clear()
const measurements: { id: string; rect: ReturnType<MeasureFn> }[] = []
// Phase 1: READ (Fast, no reflows if layout is clean)
for (const id of ids) {
const fn = this.measureFns.get(id)
if (fn) measurements.push({ id, rect: fn() })
}
// Phase 2: WRITE (Triggers Reactivity)
for (const { id, rect } of measurements) {
if (rect) {
this.update(id, rect)
}
}
}
}
export const spawn3dStore = new Spawn3DManager()