@vibecook/ice 0.1.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.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/dist/chunk-5S7GSXJG.js +6003 -0
- package/dist/chunk-5S7GSXJG.js.map +1 -0
- package/dist/chunk-BCJ47SX7.js +550 -0
- package/dist/chunk-BCJ47SX7.js.map +1 -0
- package/dist/chunk-FIAXPNNY.js +1446 -0
- package/dist/chunk-FIAXPNNY.js.map +1 -0
- package/dist/devtools.js +417 -0
- package/dist/devtools.js.map +1 -0
- package/dist/dom.js +41 -0
- package/dist/dom.js.map +1 -0
- package/dist/index.js +450 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.js +63 -0
- package/dist/kernel.js.map +1 -0
- package/dist/r3f.js +1101 -0
- package/dist/r3f.js.map +1 -0
- package/dist/react.js +440 -0
- package/dist/react.js.map +1 -0
- package/dist/types/packages/core/src/boot/hmr.d.ts +12 -0
- package/dist/types/packages/core/src/catalog/camera-derived.d.ts +144 -0
- package/dist/types/packages/core/src/catalog/gesture.d.ts +254 -0
- package/dist/types/packages/core/src/catalog/graph.d.ts +55 -0
- package/dist/types/packages/core/src/catalog/index.d.ts +32 -0
- package/dist/types/packages/core/src/catalog/pointer.d.ts +111 -0
- package/dist/types/packages/core/src/catalog/scene.d.ts +34 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +53 -0
- package/dist/types/packages/core/src/catalog/settings-resources.d.ts +46 -0
- package/dist/types/packages/core/src/doc/autosave.d.ts +69 -0
- package/dist/types/packages/core/src/doc/bootstrap.d.ts +74 -0
- package/dist/types/packages/core/src/doc/broadcast-relay.d.ts +52 -0
- package/dist/types/packages/core/src/doc/channels.d.ts +49 -0
- package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +22 -0
- package/dist/types/packages/core/src/doc/doc-kit.d.ts +50 -0
- package/dist/types/packages/core/src/doc/envelope.d.ts +36 -0
- package/dist/types/packages/core/src/doc/migrate.d.ts +69 -0
- package/dist/types/packages/core/src/doc/version-gate.d.ts +49 -0
- package/dist/types/packages/core/src/engine/commit-sink.d.ts +61 -0
- package/dist/types/packages/core/src/engine/engine.d.ts +62 -0
- package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -0
- package/dist/types/packages/core/src/engine/instantiate.d.ts +45 -0
- package/dist/types/packages/core/src/engine/pipeline.d.ts +17 -0
- package/dist/types/packages/core/src/engine/reflectors.d.ts +56 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +120 -0
- package/dist/types/packages/core/src/guards/dev.d.ts +2 -0
- package/dist/types/packages/core/src/guards/guarded-tx.d.ts +51 -0
- package/dist/types/packages/core/src/guards/live-writer.d.ts +33 -0
- package/dist/types/packages/core/src/guards/resource-writer.d.ts +17 -0
- package/dist/types/packages/core/src/helpers/phase-set.d.ts +70 -0
- package/dist/types/packages/core/src/helpers/version-stamps.d.ts +55 -0
- package/dist/types/packages/core/src/index.d.ts +63 -0
- package/dist/types/packages/core/src/input/measure-queue.d.ts +30 -0
- package/dist/types/packages/core/src/input/queue.d.ts +60 -0
- package/dist/types/packages/core/src/interaction/install.d.ts +58 -0
- package/dist/types/packages/core/src/nav/nested-canvas.d.ts +62 -0
- package/dist/types/packages/core/src/ops/cascade.d.ts +18 -0
- package/dist/types/packages/core/src/ops/claims.d.ts +15 -0
- package/dist/types/packages/core/src/ops/gestures.d.ts +10 -0
- package/dist/types/packages/core/src/ops/point-pick.d.ts +42 -0
- package/dist/types/packages/core/src/ops/port-geometry.d.ts +17 -0
- package/dist/types/packages/core/src/ops/selection.d.ts +14 -0
- package/dist/types/packages/core/src/presence/index.d.ts +7 -0
- package/dist/types/packages/core/src/presence/presence-kit.d.ts +39 -0
- package/dist/types/packages/core/src/presence/publish.d.ts +14 -0
- package/dist/types/packages/core/src/presence/remote-cursors.d.ts +18 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +43 -0
- package/dist/types/packages/core/src/schema/prefab.d.ts +69 -0
- package/dist/types/packages/core/src/settings/defaults.d.ts +56 -0
- package/dist/types/packages/core/src/systems/camera-sim.d.ts +19 -0
- package/dist/types/packages/core/src/systems/chrome.d.ts +48 -0
- package/dist/types/packages/core/src/systems/cleanup.d.ts +19 -0
- package/dist/types/packages/core/src/systems/l0-input.d.ts +31 -0
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +44 -0
- package/dist/types/packages/core/src/systems/l1-wires.d.ts +30 -0
- package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +40 -0
- package/dist/types/packages/core/src/systems/l2-recognize.d.ts +45 -0
- package/dist/types/packages/core/src/systems/l3-behave.d.ts +24 -0
- package/dist/types/packages/core/src/systems/l3-claim.d.ts +26 -0
- package/dist/types/packages/core/src/systems/l3-connect.d.ts +57 -0
- package/dist/types/packages/core/src/systems/l3-draw.d.ts +21 -0
- package/dist/types/packages/core/src/systems/l3-drop.d.ts +21 -0
- package/dist/types/packages/core/src/systems/l3-marquee.d.ts +35 -0
- package/dist/types/packages/core/src/systems/l3-ports.d.ts +30 -0
- package/dist/types/packages/core/src/systems/l3-resize.d.ts +17 -0
- package/dist/types/packages/core/src/systems/l3-snap.d.ts +19 -0
- package/dist/types/packages/core/src/systems/l4-cursor.d.ts +24 -0
- package/dist/types/packages/core/src/systems/measure-ingest.d.ts +26 -0
- package/dist/types/packages/core/src/tools/define-tool.d.ts +79 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +124 -0
- package/dist/types/packages/core/src/widget/equip.d.ts +14 -0
- package/dist/types/packages/core/src/widget/mount-store.d.ts +47 -0
- package/dist/types/packages/core/src/widget/props.d.ts +98 -0
- package/dist/types/packages/core/src/widget/spawn.d.ts +30 -0
- package/dist/types/packages/devtools/src/index.d.ts +8 -0
- package/dist/types/packages/devtools/src/panel.d.ts +48 -0
- package/dist/types/packages/dom/src/host.d.ts +24 -0
- package/dist/types/packages/dom/src/index.d.ts +19 -0
- package/dist/types/packages/dom/src/loop.d.ts +11 -0
- package/dist/types/packages/dom/src/measure-adapter.d.ts +31 -0
- package/dist/types/packages/dom/src/measure-wiring.d.ts +49 -0
- package/dist/types/packages/dom/src/planes.d.ts +33 -0
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +48 -0
- package/dist/types/packages/dom/src/reflectors/chrome.d.ts +33 -0
- package/dist/types/packages/dom/src/reflectors/cursor.d.ts +21 -0
- package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +54 -0
- package/dist/types/packages/dom/src/reflectors/graybox.d.ts +31 -0
- package/dist/types/packages/dom/src/reflectors/grid.d.ts +63 -0
- package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +38 -0
- package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +20 -0
- package/dist/types/packages/dom/src/reflectors/wires.d.ts +88 -0
- package/dist/types/packages/ice/src/devtools.d.ts +1 -0
- package/dist/types/packages/ice/src/dom.d.ts +1 -0
- package/dist/types/packages/ice/src/index.d.ts +1 -0
- package/dist/types/packages/ice/src/kernel.d.ts +1 -0
- package/dist/types/packages/ice/src/r3f.d.ts +1 -0
- package/dist/types/packages/ice/src/react.d.ts +1 -0
- package/dist/types/packages/kernel/src/anchors.d.ts +18 -0
- package/dist/types/packages/kernel/src/bezier.d.ts +38 -0
- package/dist/types/packages/kernel/src/coords.d.ts +67 -0
- package/dist/types/packages/kernel/src/eviction.d.ts +23 -0
- package/dist/types/packages/kernel/src/index.d.ts +13 -0
- package/dist/types/packages/kernel/src/shapes.d.ts +22 -0
- package/dist/types/packages/kernel/src/snap.d.ts +44 -0
- package/dist/types/packages/kernel/src/spatial-index.d.ts +16 -0
- package/dist/types/packages/kernel/src/zoom-bands.d.ts +19 -0
- package/dist/types/packages/r3f/src/bridge.d.ts +68 -0
- package/dist/types/packages/r3f/src/composite-material.d.ts +17 -0
- package/dist/types/packages/r3f/src/compositor-pass.d.ts +83 -0
- package/dist/types/packages/r3f/src/dev-write-trap.d.ts +30 -0
- package/dist/types/packages/r3f/src/gl-root.d.ts +21 -0
- package/dist/types/packages/r3f/src/gl-router.d.ts +60 -0
- package/dist/types/packages/r3f/src/index.d.ts +16 -0
- package/dist/types/packages/r3f/src/island-state.d.ts +68 -0
- package/dist/types/packages/r3f/src/island.d.ts +13 -0
- package/dist/types/packages/r3f/src/pool.d.ts +56 -0
- package/dist/types/packages/r3f/src/resource-registry.d.ts +30 -0
- package/dist/types/packages/r3f/src/use-island-frame.d.ts +24 -0
- package/dist/types/packages/react/src/engine-context.d.ts +31 -0
- package/dist/types/packages/react/src/hooks.d.ts +40 -0
- package/dist/types/packages/react/src/index.d.ts +14 -0
- package/dist/types/packages/react/src/infinite-canvas.d.ts +71 -0
- package/dist/types/packages/react/src/keymap.d.ts +42 -0
- package/dist/types/packages/react/src/use-commit.d.ts +35 -0
- package/dist/types/packages/react/src/use-presence.d.ts +23 -0
- package/dist/types/packages/react/src/use-tool.d.ts +4 -0
- package/dist/types/packages/react/src/widget-root.d.ts +33 -0
- package/package.json +112 -0
package/dist/r3f.js
ADDED
|
@@ -0,0 +1,1101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Camera,
|
|
3
|
+
Grab,
|
|
4
|
+
Position,
|
|
5
|
+
PrefabId,
|
|
6
|
+
RUNTIME_BUDGETS,
|
|
7
|
+
Size,
|
|
8
|
+
StackZ,
|
|
9
|
+
Viewport,
|
|
10
|
+
Visible,
|
|
11
|
+
pickTopAt,
|
|
12
|
+
widgets
|
|
13
|
+
} from "./chunk-5S7GSXJG.js";
|
|
14
|
+
import {
|
|
15
|
+
compositeCameraFrustum,
|
|
16
|
+
computeIslandPhase,
|
|
17
|
+
fboPixelSize,
|
|
18
|
+
isOutOfBand,
|
|
19
|
+
screenToWorld,
|
|
20
|
+
selectBand,
|
|
21
|
+
selectEvictions,
|
|
22
|
+
worldRectToComposite,
|
|
23
|
+
worldToIsland
|
|
24
|
+
} from "./chunk-BCJ47SX7.js";
|
|
25
|
+
|
|
26
|
+
// ../r3f/src/island-state.ts
|
|
27
|
+
var FRESH_PAINTED_AT = { w: 0, h: 0, dpr: 1, band: 0 };
|
|
28
|
+
function createIslandStateStore() {
|
|
29
|
+
const entries = /* @__PURE__ */ new Map();
|
|
30
|
+
let paintSeq = 0;
|
|
31
|
+
const ensure = (key) => {
|
|
32
|
+
let s = entries.get(key);
|
|
33
|
+
if (s === void 0) {
|
|
34
|
+
s = {
|
|
35
|
+
phase: "Cold",
|
|
36
|
+
paintGeneration: 0,
|
|
37
|
+
fboGeneration: -1,
|
|
38
|
+
paintedAt: FRESH_PAINTED_AT,
|
|
39
|
+
animRefs: 0,
|
|
40
|
+
animatedDecl: false,
|
|
41
|
+
lastPaintSeq: 0,
|
|
42
|
+
compositeScale: 1
|
|
43
|
+
};
|
|
44
|
+
entries.set(key, s);
|
|
45
|
+
}
|
|
46
|
+
return s;
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
ensure,
|
|
50
|
+
get: (key) => entries.get(key),
|
|
51
|
+
all: () => entries.entries(),
|
|
52
|
+
bumpPaint(key) {
|
|
53
|
+
ensure(key).paintGeneration += 1;
|
|
54
|
+
},
|
|
55
|
+
markPainted(key, at) {
|
|
56
|
+
const s = ensure(key);
|
|
57
|
+
s.fboGeneration = s.paintGeneration;
|
|
58
|
+
s.paintedAt = at;
|
|
59
|
+
s.lastPaintSeq = ++paintSeq;
|
|
60
|
+
},
|
|
61
|
+
markEvicted(key) {
|
|
62
|
+
const s = entries.get(key);
|
|
63
|
+
if (s !== void 0) s.fboGeneration = -1;
|
|
64
|
+
},
|
|
65
|
+
addAnimRef(key) {
|
|
66
|
+
ensure(key).animRefs += 1;
|
|
67
|
+
},
|
|
68
|
+
removeAnimRef(key) {
|
|
69
|
+
const s = entries.get(key);
|
|
70
|
+
if (s !== void 0 && s.animRefs > 0) s.animRefs -= 1;
|
|
71
|
+
},
|
|
72
|
+
isAnimating(key) {
|
|
73
|
+
const s = entries.get(key);
|
|
74
|
+
return s !== void 0 && (s.animatedDecl || s.animRefs > 0);
|
|
75
|
+
},
|
|
76
|
+
setAnimatedDecl(key, animated) {
|
|
77
|
+
ensure(key).animatedDecl = animated;
|
|
78
|
+
},
|
|
79
|
+
drop(key) {
|
|
80
|
+
entries.delete(key);
|
|
81
|
+
},
|
|
82
|
+
clear() {
|
|
83
|
+
entries.clear();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ../r3f/src/dev-write-trap.ts
|
|
89
|
+
function createRenderWriteTrap(world, enabled) {
|
|
90
|
+
let armed = false;
|
|
91
|
+
let unregister = null;
|
|
92
|
+
if (enabled) {
|
|
93
|
+
unregister = world.devOnWrite((kind) => {
|
|
94
|
+
if (!armed) return;
|
|
95
|
+
throw new Error(
|
|
96
|
+
`ice: world mutation (${kind}) during the GL render pass \u2014 the render path never writes ECS (design-004 \xA73). Move the write to a system, an input fact, or an app op OUTSIDE useIslandFrame/render.`
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
enabled,
|
|
102
|
+
begin() {
|
|
103
|
+
if (!enabled) return;
|
|
104
|
+
if (armed) throw new Error("ice: render write trap begin() while already armed.");
|
|
105
|
+
armed = true;
|
|
106
|
+
},
|
|
107
|
+
end() {
|
|
108
|
+
armed = false;
|
|
109
|
+
},
|
|
110
|
+
dispose() {
|
|
111
|
+
armed = false;
|
|
112
|
+
unregister?.();
|
|
113
|
+
unregister = null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ../r3f/src/bridge.ts
|
|
119
|
+
function createGLBridge(engine, opts = {}) {
|
|
120
|
+
const world = engine.world;
|
|
121
|
+
const state = createIslandStateStore();
|
|
122
|
+
const islands = /* @__PURE__ */ new Map();
|
|
123
|
+
const frameCbs = /* @__PURE__ */ new Map();
|
|
124
|
+
const renderAssert = createRenderWriteTrap(world, opts.devAssertRenderWrites === true);
|
|
125
|
+
let invalidate = null;
|
|
126
|
+
let pending = false;
|
|
127
|
+
const requestFrame = () => {
|
|
128
|
+
if (invalidate !== null) invalidate();
|
|
129
|
+
else pending = true;
|
|
130
|
+
};
|
|
131
|
+
const removeReflector = engine.registerReflector({
|
|
132
|
+
name: "r3fInvalidator",
|
|
133
|
+
observe: { resources: [Camera, Viewport] },
|
|
134
|
+
flush: () => requestFrame()
|
|
135
|
+
});
|
|
136
|
+
const removeWorldObserver = world.observe({
|
|
137
|
+
onDestroy: (e) => {
|
|
138
|
+
if (state.get(e) !== void 0) {
|
|
139
|
+
state.drop(e);
|
|
140
|
+
frameCbs.delete(e);
|
|
141
|
+
requestFrame();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
onReset: () => {
|
|
145
|
+
state.clear();
|
|
146
|
+
frameCbs.clear();
|
|
147
|
+
requestFrame();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
engine,
|
|
152
|
+
state,
|
|
153
|
+
renderAssert,
|
|
154
|
+
registerIsland(entity, handle) {
|
|
155
|
+
islands.set(entity, handle);
|
|
156
|
+
const s = state.ensure(entity);
|
|
157
|
+
const type = world.get(entity, PrefabId)?.id;
|
|
158
|
+
const widget = typeof type === "string" ? widgets.get(type) : void 0;
|
|
159
|
+
state.setAnimatedDecl(entity, widget?.animated === true);
|
|
160
|
+
const unsubs = [
|
|
161
|
+
// island dirt — content resolution/props changed, FBO stale:
|
|
162
|
+
world.reactive.observeValue(entity, Size, () => {
|
|
163
|
+
state.bumpPaint(entity);
|
|
164
|
+
requestFrame();
|
|
165
|
+
}),
|
|
166
|
+
// composite dirt — quad transform/order changed, texture still good:
|
|
167
|
+
world.reactive.observeValue(entity, Position, () => requestFrame()),
|
|
168
|
+
world.reactive.observeValue(entity, StackZ, () => requestFrame())
|
|
169
|
+
];
|
|
170
|
+
for (const group of widget?.groups ?? []) {
|
|
171
|
+
unsubs.push(
|
|
172
|
+
world.reactive.observeValue(entity, group.component, () => {
|
|
173
|
+
state.bumpPaint(entity);
|
|
174
|
+
requestFrame();
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (s.fboGeneration < 0) state.bumpPaint(entity);
|
|
179
|
+
requestFrame();
|
|
180
|
+
return () => {
|
|
181
|
+
for (const u of unsubs) u();
|
|
182
|
+
islands.delete(entity);
|
|
183
|
+
requestFrame();
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
islandFor: (entity) => islands.get(entity),
|
|
187
|
+
islands: () => islands.entries(),
|
|
188
|
+
addFrameCallback(entity, cb) {
|
|
189
|
+
let set = frameCbs.get(entity);
|
|
190
|
+
if (set === void 0) {
|
|
191
|
+
set = /* @__PURE__ */ new Set();
|
|
192
|
+
frameCbs.set(entity, set);
|
|
193
|
+
}
|
|
194
|
+
set.add(cb);
|
|
195
|
+
state.addAnimRef(entity);
|
|
196
|
+
requestFrame();
|
|
197
|
+
return () => {
|
|
198
|
+
set.delete(cb);
|
|
199
|
+
state.removeAnimRef(entity);
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
frameCallbacksFor(entity) {
|
|
203
|
+
const set = frameCbs.get(entity);
|
|
204
|
+
return set === void 0 ? [] : [...set];
|
|
205
|
+
},
|
|
206
|
+
requestFrame,
|
|
207
|
+
bumpPaint(entity) {
|
|
208
|
+
state.bumpPaint(entity);
|
|
209
|
+
requestFrame();
|
|
210
|
+
},
|
|
211
|
+
setCompositeScale(entity, scale) {
|
|
212
|
+
const s = state.ensure(entity);
|
|
213
|
+
if (s.compositeScale === scale) return;
|
|
214
|
+
s.compositeScale = scale;
|
|
215
|
+
requestFrame();
|
|
216
|
+
},
|
|
217
|
+
setInvalidate(fn) {
|
|
218
|
+
invalidate = fn;
|
|
219
|
+
if (fn !== null && pending) {
|
|
220
|
+
pending = false;
|
|
221
|
+
fn();
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
uninstall() {
|
|
225
|
+
removeReflector();
|
|
226
|
+
removeWorldObserver();
|
|
227
|
+
renderAssert.dispose();
|
|
228
|
+
islands.clear();
|
|
229
|
+
frameCbs.clear();
|
|
230
|
+
state.clear();
|
|
231
|
+
invalidate = null;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ../r3f/src/compositor-pass.ts
|
|
237
|
+
var GRABBED_RENDER_ORDER = 1e9;
|
|
238
|
+
function runCompositorPass(ctx) {
|
|
239
|
+
const { world, bridge, pool, quads, gl } = ctx;
|
|
240
|
+
const stats = { repainted: 0, pendingPaints: 0, evicted: 0, fboBytes: 0, anyHot: false };
|
|
241
|
+
const cam = world.getResource(Camera);
|
|
242
|
+
const vp = world.getResource(Viewport);
|
|
243
|
+
if (cam === void 0 || vp === void 0 || vp.w === 0) return stats;
|
|
244
|
+
ctx.compCamera.setFrustum(compositeCameraFrustum(cam, vp.w, vp.h));
|
|
245
|
+
const idleDpr = vp.dpr > 0 ? vp.dpr : 1;
|
|
246
|
+
const targetDpr = cam.gesturing ? Math.min(idleDpr, 1) : idleDpr;
|
|
247
|
+
if (gl.getPixelRatio() !== targetDpr) gl.setPixelRatio(targetDpr);
|
|
248
|
+
const phaseOf = (e) => {
|
|
249
|
+
const alive = world.isAlive(e);
|
|
250
|
+
const visible = alive && world.hasTag(e, Visible) && bridge.islandFor(e) !== void 0;
|
|
251
|
+
const s = bridge.state.get(e);
|
|
252
|
+
const hasFbo = s !== void 0 && s.fboGeneration >= 0 && pool.get(e) !== null;
|
|
253
|
+
return computeIslandPhase(true, visible, bridge.state.isAnimating(e), hasFbo);
|
|
254
|
+
};
|
|
255
|
+
for (const [e, s] of bridge.state.all()) {
|
|
256
|
+
s.phase = phaseOf(e);
|
|
257
|
+
if (s.phase === "Hot") stats.anyHot = true;
|
|
258
|
+
}
|
|
259
|
+
const band = selectBand(cam.zoom);
|
|
260
|
+
const effectiveDpr = gl.getPixelRatio() * band;
|
|
261
|
+
const toPaint = [];
|
|
262
|
+
for (const [e, s] of bridge.state.all()) {
|
|
263
|
+
if (bridge.islandFor(e) === void 0) continue;
|
|
264
|
+
const wantsPhase = s.phase === "Hot" || s.phase === "Waking";
|
|
265
|
+
const genDirty = s.paintGeneration > s.fboGeneration;
|
|
266
|
+
const bandStale = !cam.gesturing && s.fboGeneration >= 0 && isOutOfBand(cam.zoom, s.paintedAt.band);
|
|
267
|
+
if (wantsPhase || genDirty || bandStale) toPaint.push(e);
|
|
268
|
+
}
|
|
269
|
+
toPaint.sort((a, b) => {
|
|
270
|
+
const sa = bridge.state.get(a);
|
|
271
|
+
const sb = bridge.state.get(b);
|
|
272
|
+
const coldA = sa !== void 0 && sa.fboGeneration < 0 ? 0 : 1;
|
|
273
|
+
const coldB = sb !== void 0 && sb.fboGeneration < 0 ? 0 : 1;
|
|
274
|
+
if (coldA !== coldB) return coldA - coldB;
|
|
275
|
+
return (sa?.lastPaintSeq ?? 0) - (sb?.lastPaintSeq ?? 0);
|
|
276
|
+
});
|
|
277
|
+
const cap = Math.max(1, ctx.maxRepaintsPerFrame);
|
|
278
|
+
for (const e of toPaint) {
|
|
279
|
+
if (stats.repainted >= cap) {
|
|
280
|
+
stats.pendingPaints = toPaint.length - stats.repainted;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
const handle = bridge.islandFor(e);
|
|
284
|
+
const s = bridge.state.get(e);
|
|
285
|
+
const size = world.isAlive(e) ? world.get(e, Size) : void 0;
|
|
286
|
+
if (handle === void 0 || s === void 0 || size === void 0 || size.w <= 0 || size.h <= 0) continue;
|
|
287
|
+
if (s.phase === "Hot") {
|
|
288
|
+
for (const cb of bridge.frameCallbacksFor(e)) cb(ctx.dtMs);
|
|
289
|
+
}
|
|
290
|
+
ctx.islandCamera(e)?.setFrustum(size.w / 2, size.h / 2);
|
|
291
|
+
const fbo = pool.acquire(e, size.w, size.h, effectiveDpr);
|
|
292
|
+
gl.setRenderTarget(fbo);
|
|
293
|
+
try {
|
|
294
|
+
gl.clear();
|
|
295
|
+
gl.render(handle.scene, handle.camera);
|
|
296
|
+
} finally {
|
|
297
|
+
gl.setRenderTarget(null);
|
|
298
|
+
}
|
|
299
|
+
const px = fboPixelSize(size.w, size.h, effectiveDpr, 1);
|
|
300
|
+
bridge.state.markPainted(e, { w: px.width, h: px.height, dpr: effectiveDpr, band });
|
|
301
|
+
stats.repainted += 1;
|
|
302
|
+
}
|
|
303
|
+
if (pool.bytesUsed() > ctx.maxFboBytes) {
|
|
304
|
+
const candidates = [];
|
|
305
|
+
for (const info of pool.entryInfos()) {
|
|
306
|
+
const s = bridge.state.get(info.key);
|
|
307
|
+
const phase = s === void 0 ? "Dormant" : phaseOf(info.key);
|
|
308
|
+
if (s !== void 0) s.phase = phase;
|
|
309
|
+
candidates.push({
|
|
310
|
+
id: info.key,
|
|
311
|
+
phase,
|
|
312
|
+
bytes: info.bytes,
|
|
313
|
+
lastUsedMs: info.lastUsedMs
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
for (const key of selectEvictions(candidates, pool.bytesUsed(), ctx.maxFboBytes)) {
|
|
317
|
+
if (bridge.state.get(key)?.phase === "Dormant") {
|
|
318
|
+
console.debug(`[ice/r3f] evicting Dormant island ${key} \u2014 consider raising the FBO budget`);
|
|
319
|
+
}
|
|
320
|
+
pool.release(key);
|
|
321
|
+
bridge.state.markEvicted(key);
|
|
322
|
+
stats.evicted += 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const liveKeys = /* @__PURE__ */ new Set();
|
|
326
|
+
for (const [e] of bridge.state.all()) liveKeys.add(e);
|
|
327
|
+
for (const info of pool.entryInfos()) {
|
|
328
|
+
if (!liveKeys.has(info.key)) pool.release(info.key);
|
|
329
|
+
}
|
|
330
|
+
for (const key of quads.keys()) {
|
|
331
|
+
if (!liveKeys.has(key)) quads.remove(key);
|
|
332
|
+
}
|
|
333
|
+
for (const [e, s] of bridge.state.all()) {
|
|
334
|
+
const quad = quads.ensure(e);
|
|
335
|
+
const alive = world.isAlive(e);
|
|
336
|
+
const fbo = pool.get(e);
|
|
337
|
+
const pos = alive ? world.get(e, Position) : void 0;
|
|
338
|
+
const size = alive ? world.get(e, Size) : void 0;
|
|
339
|
+
if (fbo === null || s.fboGeneration < 0 || pos === void 0 || size === void 0) {
|
|
340
|
+
quad.setVisible(false);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const q = worldRectToComposite({ x: pos.x, y: pos.y, width: size.w, height: size.h });
|
|
344
|
+
const lift = s.compositeScale;
|
|
345
|
+
quad.setTransform(q.x, q.y, q.sx * lift, q.sy * lift);
|
|
346
|
+
quad.setTexture(fbo.texture);
|
|
347
|
+
const grabbed = world.get(e, Grab) !== void 0;
|
|
348
|
+
quad.setRenderOrder(grabbed || lift !== 1 ? GRABBED_RENDER_ORDER : world.get(e, StackZ)?.z ?? 0);
|
|
349
|
+
quad.setVisible(true);
|
|
350
|
+
pool.touch(e);
|
|
351
|
+
}
|
|
352
|
+
gl.setRenderTarget(null);
|
|
353
|
+
gl.clear();
|
|
354
|
+
gl.render(ctx.compositeScene, ctx.compCamera.raw);
|
|
355
|
+
stats.fboBytes = pool.bytesUsed();
|
|
356
|
+
return stats;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ../r3f/src/gl-root.tsx
|
|
360
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
361
|
+
import {
|
|
362
|
+
createElement as createElement2,
|
|
363
|
+
useEffect as useEffect3,
|
|
364
|
+
useMemo as useMemo2,
|
|
365
|
+
useRef as useRef2,
|
|
366
|
+
useSyncExternalStore
|
|
367
|
+
} from "react";
|
|
368
|
+
import { Mesh, OrthographicCamera as OrthographicCamera2, PlaneGeometry } from "three";
|
|
369
|
+
|
|
370
|
+
// ../r3f/src/composite-material.ts
|
|
371
|
+
import { ShaderMaterial } from "three";
|
|
372
|
+
var VERTEX_SHADER = (
|
|
373
|
+
/* glsl */
|
|
374
|
+
`
|
|
375
|
+
varying vec2 vUv;
|
|
376
|
+
void main() {
|
|
377
|
+
vUv = uv;
|
|
378
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
379
|
+
}
|
|
380
|
+
`
|
|
381
|
+
);
|
|
382
|
+
var FRAGMENT_SHADER = (
|
|
383
|
+
/* glsl */
|
|
384
|
+
`
|
|
385
|
+
uniform sampler2D map;
|
|
386
|
+
uniform float uOpacity;
|
|
387
|
+
varying vec2 vUv;
|
|
388
|
+
|
|
389
|
+
void main() {
|
|
390
|
+
vec4 c = texture2D(map, vUv);
|
|
391
|
+
if (c.a < 0.001) discard;
|
|
392
|
+
c.a *= uOpacity;
|
|
393
|
+
gl_FragColor = c;
|
|
394
|
+
#include <colorspace_fragment>
|
|
395
|
+
}
|
|
396
|
+
`
|
|
397
|
+
);
|
|
398
|
+
var CompositeMaterial = class extends ShaderMaterial {
|
|
399
|
+
// Direct handles onto our two uniform cells. ShaderMaterial types `uniforms`
|
|
400
|
+
// as an index signature, so `this.uniforms.map` reads as possibly-undefined
|
|
401
|
+
// under strict indexing; holding the cells we created sidesteps that and keeps
|
|
402
|
+
// the setters allocation-free.
|
|
403
|
+
mapUniform;
|
|
404
|
+
opacityUniform;
|
|
405
|
+
constructor() {
|
|
406
|
+
const uniforms = {
|
|
407
|
+
map: { value: null },
|
|
408
|
+
uOpacity: { value: 1 }
|
|
409
|
+
};
|
|
410
|
+
super({
|
|
411
|
+
vertexShader: VERTEX_SHADER,
|
|
412
|
+
fragmentShader: FRAGMENT_SHADER,
|
|
413
|
+
uniforms,
|
|
414
|
+
transparent: true,
|
|
415
|
+
depthWrite: false,
|
|
416
|
+
// Explicit: this pass must never tone-map (the FBO is already display-
|
|
417
|
+
// ready). v1 left toneMapped at the ShaderMaterial default and relied on
|
|
418
|
+
// the absent tonemapping chunk alone (CompositionMaterial.ts:140-142); we
|
|
419
|
+
// set it false so the no-tone-map contract holds even if the shader ever
|
|
420
|
+
// gains chunks. Behaviour is identical for the current shader.
|
|
421
|
+
toneMapped: false
|
|
422
|
+
});
|
|
423
|
+
this.mapUniform = uniforms.map;
|
|
424
|
+
this.opacityUniform = uniforms.uOpacity;
|
|
425
|
+
}
|
|
426
|
+
/** Bind the island's FBO texture (null clears). */
|
|
427
|
+
setMap(map) {
|
|
428
|
+
this.mapUniform.value = map;
|
|
429
|
+
}
|
|
430
|
+
/** Composite opacity. Pass-through — no clamping; callers pass valid 0..1. */
|
|
431
|
+
setOpacity(opacity) {
|
|
432
|
+
this.opacityUniform.value = opacity;
|
|
433
|
+
}
|
|
434
|
+
// dispose() is inherited from ShaderMaterial — it fires the dispose event that
|
|
435
|
+
// frees the compiled program; no override needed.
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// ../r3f/src/island.tsx
|
|
439
|
+
import { createPortal } from "@react-three/fiber";
|
|
440
|
+
import { createElement, useEffect as useEffect2, useMemo } from "react";
|
|
441
|
+
import { OrthographicCamera, Scene } from "three";
|
|
442
|
+
|
|
443
|
+
// ../r3f/src/use-island-frame.ts
|
|
444
|
+
import { createContext, useCallback, useContext, useEffect, useRef } from "react";
|
|
445
|
+
var IslandContext = createContext(null);
|
|
446
|
+
function useIslandContext() {
|
|
447
|
+
const ctx = useContext(IslandContext);
|
|
448
|
+
if (ctx === null) {
|
|
449
|
+
throw new Error("ice: useIslandFrame/useIslandInvalidate must run inside a GL widget's island.");
|
|
450
|
+
}
|
|
451
|
+
return ctx;
|
|
452
|
+
}
|
|
453
|
+
function useIslandFrame(cb) {
|
|
454
|
+
const { bridge, entity } = useIslandContext();
|
|
455
|
+
const ref = useRef(cb);
|
|
456
|
+
ref.current = cb;
|
|
457
|
+
useEffect(() => bridge.addFrameCallback(entity, (dt) => ref.current(dt)), [bridge, entity]);
|
|
458
|
+
}
|
|
459
|
+
function useIslandInvalidate() {
|
|
460
|
+
const { bridge, entity } = useIslandContext();
|
|
461
|
+
return useCallback(() => bridge.bumpPaint(entity), [bridge, entity]);
|
|
462
|
+
}
|
|
463
|
+
function useIslandLift(scale) {
|
|
464
|
+
const { bridge, entity } = useIslandContext();
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
bridge.setCompositeScale(entity, scale);
|
|
467
|
+
}, [bridge, entity, scale]);
|
|
468
|
+
useEffect(() => () => bridge.setCompositeScale(entity, 1), [bridge, entity]);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ../r3f/src/island.tsx
|
|
472
|
+
function Island({ bridge, world, entity, widget, environment }) {
|
|
473
|
+
const scene = useMemo(() => new Scene(), []);
|
|
474
|
+
const camera = useMemo(() => {
|
|
475
|
+
const cam = new OrthographicCamera(-1, 1, 1, -1, 0.1, 2e3);
|
|
476
|
+
cam.position.set(0, 0, 500);
|
|
477
|
+
cam.lookAt(0, 0, 0);
|
|
478
|
+
return cam;
|
|
479
|
+
}, []);
|
|
480
|
+
useEffect2(
|
|
481
|
+
() => bridge.registerIsland(entity, { scene, camera }),
|
|
482
|
+
[bridge, entity, scene, camera]
|
|
483
|
+
);
|
|
484
|
+
useEffect2(() => {
|
|
485
|
+
scene.environment = environment ?? null;
|
|
486
|
+
bridge.bumpPaint(entity);
|
|
487
|
+
}, [environment, scene, bridge, entity]);
|
|
488
|
+
const View = widget.component;
|
|
489
|
+
if (View == null) return null;
|
|
490
|
+
return createPortal(
|
|
491
|
+
createElement(
|
|
492
|
+
IslandContext.Provider,
|
|
493
|
+
{ value: { bridge, entity } },
|
|
494
|
+
createElement(View, { entity, world })
|
|
495
|
+
),
|
|
496
|
+
scene
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ../r3f/src/pool.ts
|
|
501
|
+
import { SRGBColorSpace, WebGLRenderTarget } from "three";
|
|
502
|
+
var COLOR_BYTES_PER_PIXEL = 4;
|
|
503
|
+
var SAMPLES = 4;
|
|
504
|
+
function targetBytes(pixelWidth, pixelHeight) {
|
|
505
|
+
return pixelWidth * pixelHeight * COLOR_BYTES_PER_PIXEL * (SAMPLES > 1 ? 2 : 1);
|
|
506
|
+
}
|
|
507
|
+
var RenderTargetPool = class {
|
|
508
|
+
entries = /* @__PURE__ */ new Map();
|
|
509
|
+
totalBytes = 0;
|
|
510
|
+
disposed = false;
|
|
511
|
+
now;
|
|
512
|
+
/**
|
|
513
|
+
* @param now Injected monotonic clock for LRU timestamps. Defaults to
|
|
514
|
+
* `performance.now()`; tests pass a deterministic stub so `lastUsedMs`
|
|
515
|
+
* assertions do not depend on wall-clock timing.
|
|
516
|
+
*/
|
|
517
|
+
constructor(now = () => performance.now()) {
|
|
518
|
+
this.now = now;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Get or create an FBO for `key` at the requested world size and effective
|
|
522
|
+
* device-pixel-ratio. Returns the existing target unchanged when its pixel
|
|
523
|
+
* dimensions already match; otherwise disposes the stale target and allocates
|
|
524
|
+
* a new one sized to the new resolution.
|
|
525
|
+
*/
|
|
526
|
+
acquire(key, worldW, worldH, effectiveDpr) {
|
|
527
|
+
if (this.disposed) {
|
|
528
|
+
throw new Error("RenderTargetPool: cannot acquire after dispose");
|
|
529
|
+
}
|
|
530
|
+
const pixelWidth = Math.max(1, Math.round(worldW * effectiveDpr));
|
|
531
|
+
const pixelHeight = Math.max(1, Math.round(worldH * effectiveDpr));
|
|
532
|
+
const nowMs = this.now();
|
|
533
|
+
const existing = this.entries.get(key);
|
|
534
|
+
if (existing && existing.pixelWidth === pixelWidth && existing.pixelHeight === pixelHeight) {
|
|
535
|
+
existing.lastUsedMs = nowMs;
|
|
536
|
+
return existing.rt;
|
|
537
|
+
}
|
|
538
|
+
if (existing) {
|
|
539
|
+
existing.rt.dispose();
|
|
540
|
+
this.totalBytes -= existing.bytes;
|
|
541
|
+
}
|
|
542
|
+
const rt = new WebGLRenderTarget(pixelWidth, pixelHeight, { samples: SAMPLES });
|
|
543
|
+
rt.texture.colorSpace = SRGBColorSpace;
|
|
544
|
+
const bytes = targetBytes(pixelWidth, pixelHeight);
|
|
545
|
+
this.entries.set(key, { rt, pixelWidth, pixelHeight, effectiveDpr, bytes, lastUsedMs: nowMs });
|
|
546
|
+
this.totalBytes += bytes;
|
|
547
|
+
return rt;
|
|
548
|
+
}
|
|
549
|
+
/** Look up an FBO without creating one. */
|
|
550
|
+
get(key) {
|
|
551
|
+
return this.entries.get(key)?.rt ?? null;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Refresh `lastUsedMs` without re-acquiring. The compositor calls this for
|
|
555
|
+
* every island it samples in the composition pass — without it, warm islands
|
|
556
|
+
* that never repaint would freeze their timestamp at their last paint and the
|
|
557
|
+
* LRU would treat still-visible islands as stale (design-004 §3).
|
|
558
|
+
*/
|
|
559
|
+
touch(key) {
|
|
560
|
+
const entry = this.entries.get(key);
|
|
561
|
+
if (!entry) return;
|
|
562
|
+
entry.lastUsedMs = this.now();
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Release `key`'s FBO. Returns true if something was released. Safe after
|
|
566
|
+
* dispose — returns false rather than corrupting the byte counter or
|
|
567
|
+
* double-disposing the target.
|
|
568
|
+
*/
|
|
569
|
+
release(key) {
|
|
570
|
+
if (this.disposed) return false;
|
|
571
|
+
const entry = this.entries.get(key);
|
|
572
|
+
if (!entry) return false;
|
|
573
|
+
entry.rt.dispose();
|
|
574
|
+
this.totalBytes = Math.max(0, this.totalBytes - entry.bytes);
|
|
575
|
+
this.entries.delete(key);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
/** Total GPU bytes consumed by the pool. */
|
|
579
|
+
bytesUsed() {
|
|
580
|
+
return this.totalBytes;
|
|
581
|
+
}
|
|
582
|
+
/** Number of FBOs currently held. */
|
|
583
|
+
size() {
|
|
584
|
+
return this.entries.size;
|
|
585
|
+
}
|
|
586
|
+
/** True after `dispose()` — callers should re-create the pool instead of using it. */
|
|
587
|
+
isDisposed() {
|
|
588
|
+
return this.disposed;
|
|
589
|
+
}
|
|
590
|
+
/** Iterate live entries (key + target). */
|
|
591
|
+
forEach(cb) {
|
|
592
|
+
for (const [key, entry] of this.entries) cb(key, entry.rt);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Snapshot of every live entry's `bytes` + `lastUsedMs` — the input an
|
|
596
|
+
* eviction pass ranks by LRU.
|
|
597
|
+
*/
|
|
598
|
+
entryInfos() {
|
|
599
|
+
const out = [];
|
|
600
|
+
for (const [key, entry] of this.entries) {
|
|
601
|
+
out.push({ key, bytes: entry.bytes, lastUsedMs: entry.lastUsedMs });
|
|
602
|
+
}
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
/** Dispose every FBO. After this, acquire throws and release returns false. */
|
|
606
|
+
dispose() {
|
|
607
|
+
if (this.disposed) return;
|
|
608
|
+
for (const entry of this.entries.values()) entry.rt.dispose();
|
|
609
|
+
this.entries.clear();
|
|
610
|
+
this.totalBytes = 0;
|
|
611
|
+
this.disposed = true;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// ../r3f/src/gl-root.tsx
|
|
616
|
+
function GLViews({
|
|
617
|
+
engine,
|
|
618
|
+
bridge,
|
|
619
|
+
store,
|
|
620
|
+
maxFboBytes = RUNTIME_BUDGETS.fboBytes,
|
|
621
|
+
maxRepaintsPerFrame = 4,
|
|
622
|
+
environment
|
|
623
|
+
}) {
|
|
624
|
+
const world = engine.world;
|
|
625
|
+
const gl = useThree((s) => s.gl);
|
|
626
|
+
const scene = useThree((s) => s.scene);
|
|
627
|
+
const set = useThree((s) => s.set);
|
|
628
|
+
const invalidate = useThree((s) => s.invalidate);
|
|
629
|
+
const poolRef = useRef2(null);
|
|
630
|
+
if (poolRef.current === null || poolRef.current.isDisposed()) {
|
|
631
|
+
poolRef.current = new RenderTargetPool();
|
|
632
|
+
}
|
|
633
|
+
const pool = poolRef.current;
|
|
634
|
+
const quadGeometry = useMemo2(() => new PlaneGeometry(1, 1), []);
|
|
635
|
+
const compCamera = useMemo2(() => new OrthographicCamera2(0, 1, 0, -1, 0.1, 1e4), []);
|
|
636
|
+
useEffect3(() => {
|
|
637
|
+
set({ camera: compCamera });
|
|
638
|
+
}, [set, compCamera]);
|
|
639
|
+
const quadsRef = useRef2(/* @__PURE__ */ new Map());
|
|
640
|
+
const quads = useMemo2(
|
|
641
|
+
() => ({
|
|
642
|
+
ensure(key) {
|
|
643
|
+
let mesh = quadsRef.current.get(key);
|
|
644
|
+
if (mesh === void 0) {
|
|
645
|
+
mesh = new Mesh(quadGeometry, new CompositeMaterial());
|
|
646
|
+
mesh.frustumCulled = false;
|
|
647
|
+
mesh.visible = false;
|
|
648
|
+
scene.add(mesh);
|
|
649
|
+
quadsRef.current.set(key, mesh);
|
|
650
|
+
}
|
|
651
|
+
const m = mesh;
|
|
652
|
+
return {
|
|
653
|
+
setTransform: (x, y, sx, sy) => {
|
|
654
|
+
m.position.set(x, y, 0);
|
|
655
|
+
m.scale.set(sx, sy, 1);
|
|
656
|
+
},
|
|
657
|
+
setTexture: (t) => m.material.setMap(t),
|
|
658
|
+
setVisible: (v) => {
|
|
659
|
+
m.visible = v;
|
|
660
|
+
},
|
|
661
|
+
setRenderOrder: (n) => {
|
|
662
|
+
m.renderOrder = n;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
remove(key) {
|
|
667
|
+
const mesh = quadsRef.current.get(key);
|
|
668
|
+
if (mesh !== void 0) {
|
|
669
|
+
scene.remove(mesh);
|
|
670
|
+
mesh.material.dispose();
|
|
671
|
+
quadsRef.current.delete(key);
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
keys: () => [...quadsRef.current.keys()]
|
|
675
|
+
}),
|
|
676
|
+
[scene, quadGeometry]
|
|
677
|
+
);
|
|
678
|
+
const glLike = useMemo2(
|
|
679
|
+
() => ({
|
|
680
|
+
setRenderTarget: (t) => gl.setRenderTarget(t),
|
|
681
|
+
clear: () => {
|
|
682
|
+
gl.setClearColor(0, 0);
|
|
683
|
+
gl.clear(true, true, false);
|
|
684
|
+
},
|
|
685
|
+
render: (s, c) => gl.render(s, c),
|
|
686
|
+
setPixelRatio: (n) => gl.setPixelRatio(n),
|
|
687
|
+
getPixelRatio: () => gl.getPixelRatio()
|
|
688
|
+
}),
|
|
689
|
+
[gl]
|
|
690
|
+
);
|
|
691
|
+
useEffect3(() => {
|
|
692
|
+
bridge.setInvalidate(() => invalidate());
|
|
693
|
+
return () => bridge.setInvalidate(null);
|
|
694
|
+
}, [bridge, invalidate]);
|
|
695
|
+
useEffect3(() => {
|
|
696
|
+
const quadMap = quadsRef.current;
|
|
697
|
+
return () => {
|
|
698
|
+
for (const mesh of quadMap.values()) {
|
|
699
|
+
scene.remove(mesh);
|
|
700
|
+
mesh.material.dispose();
|
|
701
|
+
}
|
|
702
|
+
quadMap.clear();
|
|
703
|
+
pool.dispose();
|
|
704
|
+
for (const [key] of bridge.state.all()) bridge.state.markEvicted(key);
|
|
705
|
+
};
|
|
706
|
+
}, [scene, pool, bridge]);
|
|
707
|
+
useFrame((_, delta) => {
|
|
708
|
+
bridge.renderAssert.begin();
|
|
709
|
+
let stats;
|
|
710
|
+
try {
|
|
711
|
+
stats = runCompositorPass({
|
|
712
|
+
world,
|
|
713
|
+
bridge,
|
|
714
|
+
pool,
|
|
715
|
+
quads,
|
|
716
|
+
gl: glLike,
|
|
717
|
+
compCamera: {
|
|
718
|
+
raw: compCamera,
|
|
719
|
+
// the REAL camera — gl.render instanceof-checks it
|
|
720
|
+
setFrustum: (f) => {
|
|
721
|
+
compCamera.left = f.left;
|
|
722
|
+
compCamera.right = f.right;
|
|
723
|
+
compCamera.top = f.top;
|
|
724
|
+
compCamera.bottom = f.bottom;
|
|
725
|
+
compCamera.position.set(f.x, f.y, 1e3);
|
|
726
|
+
compCamera.updateProjectionMatrix();
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
islandCamera: (entity) => {
|
|
730
|
+
const handle = bridge.islandFor(entity);
|
|
731
|
+
if (handle === void 0) return void 0;
|
|
732
|
+
return {
|
|
733
|
+
setFrustum: (halfW, halfH) => {
|
|
734
|
+
handle.camera.left = -halfW;
|
|
735
|
+
handle.camera.right = halfW;
|
|
736
|
+
handle.camera.top = halfH;
|
|
737
|
+
handle.camera.bottom = -halfH;
|
|
738
|
+
handle.camera.updateProjectionMatrix();
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
},
|
|
742
|
+
compositeScene: scene,
|
|
743
|
+
maxFboBytes,
|
|
744
|
+
maxRepaintsPerFrame,
|
|
745
|
+
dtMs: delta * 1e3
|
|
746
|
+
});
|
|
747
|
+
} finally {
|
|
748
|
+
bridge.renderAssert.end();
|
|
749
|
+
}
|
|
750
|
+
if (stats.anyHot || stats.pendingPaints > 0) invalidate();
|
|
751
|
+
}, 1);
|
|
752
|
+
const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
753
|
+
const islands = [];
|
|
754
|
+
for (const entry of entries) {
|
|
755
|
+
if (entry.hidden) continue;
|
|
756
|
+
if (!world.isAlive(entry.entity)) continue;
|
|
757
|
+
const type = world.get(entry.entity, PrefabId)?.id;
|
|
758
|
+
const widget = typeof type === "string" ? widgets.get(type) : void 0;
|
|
759
|
+
if (widget === void 0 || widget.surface !== "gl") continue;
|
|
760
|
+
islands.push(
|
|
761
|
+
createElement2(Island, {
|
|
762
|
+
key: String(entry.entity),
|
|
763
|
+
bridge,
|
|
764
|
+
world,
|
|
765
|
+
entity: entry.entity,
|
|
766
|
+
widget,
|
|
767
|
+
...environment !== void 0 ? { environment } : {}
|
|
768
|
+
})
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
return createElement2("group", null, islands);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ../r3f/src/resource-registry.ts
|
|
775
|
+
var ResourceRegistry = class {
|
|
776
|
+
geometries = /* @__PURE__ */ new Map();
|
|
777
|
+
materials = /* @__PURE__ */ new Map();
|
|
778
|
+
textures = /* @__PURE__ */ new Map();
|
|
779
|
+
disposed = false;
|
|
780
|
+
acquireGeometry(key, factory) {
|
|
781
|
+
const existing = this.geometries.get(key);
|
|
782
|
+
if (existing) {
|
|
783
|
+
existing.refCount++;
|
|
784
|
+
return existing.resource;
|
|
785
|
+
}
|
|
786
|
+
const resource = factory();
|
|
787
|
+
this.geometries.set(key, { resource, refCount: 1 });
|
|
788
|
+
return resource;
|
|
789
|
+
}
|
|
790
|
+
releaseGeometry(key) {
|
|
791
|
+
this.releaseFrom(this.geometries, key);
|
|
792
|
+
}
|
|
793
|
+
acquireMaterial(key, factory) {
|
|
794
|
+
const existing = this.materials.get(key);
|
|
795
|
+
if (existing) {
|
|
796
|
+
existing.refCount++;
|
|
797
|
+
return existing.resource;
|
|
798
|
+
}
|
|
799
|
+
const resource = factory();
|
|
800
|
+
this.materials.set(key, { resource, refCount: 1 });
|
|
801
|
+
return resource;
|
|
802
|
+
}
|
|
803
|
+
releaseMaterial(key) {
|
|
804
|
+
this.releaseFrom(this.materials, key);
|
|
805
|
+
}
|
|
806
|
+
acquireTexture(key, factory) {
|
|
807
|
+
const existing = this.textures.get(key);
|
|
808
|
+
if (existing) {
|
|
809
|
+
existing.refCount++;
|
|
810
|
+
return existing.resource;
|
|
811
|
+
}
|
|
812
|
+
const resource = factory();
|
|
813
|
+
this.textures.set(key, { resource, refCount: 1 });
|
|
814
|
+
return resource;
|
|
815
|
+
}
|
|
816
|
+
releaseTexture(key) {
|
|
817
|
+
this.releaseFrom(this.textures, key);
|
|
818
|
+
}
|
|
819
|
+
/** Number of distinct shared geometries currently held. */
|
|
820
|
+
geometryCount() {
|
|
821
|
+
return this.geometries.size;
|
|
822
|
+
}
|
|
823
|
+
/** Number of distinct shared materials currently held. */
|
|
824
|
+
materialCount() {
|
|
825
|
+
return this.materials.size;
|
|
826
|
+
}
|
|
827
|
+
/** Number of distinct shared textures currently held. */
|
|
828
|
+
textureCount() {
|
|
829
|
+
return this.textures.size;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Estimated GPU bytes for shared geometry attribute buffers. Best-effort —
|
|
833
|
+
* actual footprint depends on driver alignment, but a useful relative metric
|
|
834
|
+
* for the profiler.
|
|
835
|
+
*/
|
|
836
|
+
geometryBytes() {
|
|
837
|
+
let total = 0;
|
|
838
|
+
for (const { resource } of this.geometries.values()) {
|
|
839
|
+
for (const attr of Object.values(resource.attributes)) {
|
|
840
|
+
if ("array" in attr && attr.array.byteLength) {
|
|
841
|
+
total += attr.array.byteLength;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (resource.index) {
|
|
845
|
+
total += resource.index.array.byteLength;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return total;
|
|
849
|
+
}
|
|
850
|
+
/** Dispose every resource and clear the registry. */
|
|
851
|
+
dispose() {
|
|
852
|
+
if (this.disposed) return;
|
|
853
|
+
for (const { resource } of this.geometries.values()) resource.dispose();
|
|
854
|
+
for (const { resource } of this.materials.values()) resource.dispose();
|
|
855
|
+
for (const { resource } of this.textures.values()) resource.dispose();
|
|
856
|
+
this.geometries.clear();
|
|
857
|
+
this.materials.clear();
|
|
858
|
+
this.textures.clear();
|
|
859
|
+
this.disposed = true;
|
|
860
|
+
}
|
|
861
|
+
/** True after `dispose()` — callers should re-create the registry instead of using it. */
|
|
862
|
+
isDisposed() {
|
|
863
|
+
return this.disposed;
|
|
864
|
+
}
|
|
865
|
+
releaseFrom(map, key) {
|
|
866
|
+
const entry = map.get(key);
|
|
867
|
+
if (!entry) return;
|
|
868
|
+
entry.refCount--;
|
|
869
|
+
if (entry.refCount <= 0) {
|
|
870
|
+
queueMicrotask(() => {
|
|
871
|
+
const current = map.get(key);
|
|
872
|
+
if (current && current.refCount <= 0) {
|
|
873
|
+
current.resource.dispose();
|
|
874
|
+
map.delete(key);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
// ../r3f/src/gl-router.ts
|
|
882
|
+
import { Raycaster, Vector2 } from "three";
|
|
883
|
+
function handlersOf(obj) {
|
|
884
|
+
const inst = obj.__r3f;
|
|
885
|
+
if (inst === void 0 || (inst.eventCount ?? 0) === 0) return void 0;
|
|
886
|
+
return inst.handlers;
|
|
887
|
+
}
|
|
888
|
+
function createGLPointerRouter({ world, bridge, index }) {
|
|
889
|
+
const raycaster = new Raycaster();
|
|
890
|
+
const ndc = new Vector2();
|
|
891
|
+
const captures = /* @__PURE__ */ new Map();
|
|
892
|
+
const hovered = /* @__PURE__ */ new Map();
|
|
893
|
+
const unclaimedDowns = /* @__PURE__ */ new Map();
|
|
894
|
+
const castAt = (screenX, screenY) => {
|
|
895
|
+
const cam = world.getResource(Camera);
|
|
896
|
+
if (cam === void 0) return void 0;
|
|
897
|
+
const w = screenToWorld(screenX, screenY, cam);
|
|
898
|
+
const hit = pickTopAt(world, index, w.x, w.y, 0);
|
|
899
|
+
if (hit === void 0) return void 0;
|
|
900
|
+
const type = world.get(hit, PrefabId)?.id;
|
|
901
|
+
const widget = typeof type === "string" ? widgets.get(type) : void 0;
|
|
902
|
+
if (widget === void 0 || widget.surface !== "gl") return void 0;
|
|
903
|
+
const handle = bridge.islandFor(hit);
|
|
904
|
+
const pos = world.get(hit, Position);
|
|
905
|
+
const size = world.get(hit, Size);
|
|
906
|
+
if (handle === void 0 || pos === void 0 || size === void 0 || size.w <= 0 || size.h <= 0) {
|
|
907
|
+
return void 0;
|
|
908
|
+
}
|
|
909
|
+
const local = worldToIsland(w.x, w.y, { x: pos.x, y: pos.y, width: size.w, height: size.h });
|
|
910
|
+
ndc.set(local.x / (size.w / 2), local.y / (size.h / 2));
|
|
911
|
+
const cam3 = handle.camera;
|
|
912
|
+
cam3.left = -size.w / 2;
|
|
913
|
+
cam3.right = size.w / 2;
|
|
914
|
+
cam3.top = size.h / 2;
|
|
915
|
+
cam3.bottom = -size.h / 2;
|
|
916
|
+
cam3.updateProjectionMatrix();
|
|
917
|
+
cam3.updateMatrixWorld(true);
|
|
918
|
+
handle.scene.updateMatrixWorld(true);
|
|
919
|
+
raycaster.setFromCamera(ndc, cam3);
|
|
920
|
+
return { entity: hit, hits: raycaster.intersectObjects(handle.scene.children, true) };
|
|
921
|
+
};
|
|
922
|
+
const dispatch = (type, eventType, entity, hits, native, only) => {
|
|
923
|
+
const visited = /* @__PURE__ */ new Set();
|
|
924
|
+
let stopped = false;
|
|
925
|
+
let claimedBy;
|
|
926
|
+
const targets = [];
|
|
927
|
+
if (hits.length === 0 && only !== void 0) {
|
|
928
|
+
targets.push({ obj: only, hit: void 0 });
|
|
929
|
+
}
|
|
930
|
+
for (const hit of hits) {
|
|
931
|
+
for (let obj = hit.object; obj !== null; obj = obj.parent) {
|
|
932
|
+
if (visited.has(obj)) continue;
|
|
933
|
+
visited.add(obj);
|
|
934
|
+
targets.push({ obj, hit });
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
for (const { obj, hit } of targets) {
|
|
938
|
+
const fn = handlersOf(obj)?.[type];
|
|
939
|
+
if (fn === void 0) continue;
|
|
940
|
+
let localStop = false;
|
|
941
|
+
const ev = {
|
|
942
|
+
type: eventType,
|
|
943
|
+
nativeEvent: native,
|
|
944
|
+
point: hit?.point,
|
|
945
|
+
distance: hit?.distance ?? 0,
|
|
946
|
+
object: hit?.object ?? obj,
|
|
947
|
+
eventObject: obj,
|
|
948
|
+
intersections: hits,
|
|
949
|
+
entity,
|
|
950
|
+
stopPropagation() {
|
|
951
|
+
localStop = true;
|
|
952
|
+
},
|
|
953
|
+
get stopped() {
|
|
954
|
+
return localStop;
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
fn(ev);
|
|
958
|
+
if (localStop) {
|
|
959
|
+
stopped = true;
|
|
960
|
+
claimedBy = obj;
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return { stopped, claimedBy };
|
|
965
|
+
};
|
|
966
|
+
const syncHover = (now, native) => {
|
|
967
|
+
const nowEntity = now?.entity;
|
|
968
|
+
const nowObjs = /* @__PURE__ */ new Set();
|
|
969
|
+
if (now !== void 0) {
|
|
970
|
+
for (const hit of now.hits) {
|
|
971
|
+
for (let obj = hit.object; obj !== null; obj = obj.parent) nowObjs.add(obj);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
for (const [entity, prev] of hovered) {
|
|
975
|
+
const gone = [];
|
|
976
|
+
for (const obj of prev) {
|
|
977
|
+
if (entity !== nowEntity || !nowObjs.has(obj)) gone.push(obj);
|
|
978
|
+
}
|
|
979
|
+
for (const obj of gone) {
|
|
980
|
+
prev.delete(obj);
|
|
981
|
+
handlersOf(obj)?.onPointerOut?.({
|
|
982
|
+
type: "pointerout",
|
|
983
|
+
nativeEvent: native,
|
|
984
|
+
point: void 0,
|
|
985
|
+
distance: 0,
|
|
986
|
+
object: obj,
|
|
987
|
+
eventObject: obj,
|
|
988
|
+
intersections: [],
|
|
989
|
+
entity,
|
|
990
|
+
stopPropagation() {
|
|
991
|
+
},
|
|
992
|
+
stopped: false
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (prev.size === 0) hovered.delete(entity);
|
|
996
|
+
}
|
|
997
|
+
if (now !== void 0 && nowEntity !== void 0) {
|
|
998
|
+
let set = hovered.get(nowEntity);
|
|
999
|
+
if (set === void 0) {
|
|
1000
|
+
set = /* @__PURE__ */ new Set();
|
|
1001
|
+
hovered.set(nowEntity, set);
|
|
1002
|
+
}
|
|
1003
|
+
for (const obj of nowObjs) {
|
|
1004
|
+
if (set.has(obj) || handlersOf(obj) === void 0) continue;
|
|
1005
|
+
set.add(obj);
|
|
1006
|
+
handlersOf(obj)?.onPointerOver?.({
|
|
1007
|
+
type: "pointerover",
|
|
1008
|
+
nativeEvent: native,
|
|
1009
|
+
point: void 0,
|
|
1010
|
+
distance: 0,
|
|
1011
|
+
object: obj,
|
|
1012
|
+
eventObject: obj,
|
|
1013
|
+
intersections: now.hits,
|
|
1014
|
+
entity: nowEntity,
|
|
1015
|
+
stopPropagation() {
|
|
1016
|
+
},
|
|
1017
|
+
stopped: false
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
return {
|
|
1023
|
+
route(kind, screenX, screenY, native) {
|
|
1024
|
+
if (kind === "down") {
|
|
1025
|
+
const cast2 = castAt(screenX, screenY);
|
|
1026
|
+
if (cast2 === void 0) {
|
|
1027
|
+
unclaimedDowns.delete(native.pointerId);
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
const { stopped, claimedBy } = dispatch("onPointerDown", "pointerdown", cast2.entity, cast2.hits, native);
|
|
1031
|
+
if (stopped) {
|
|
1032
|
+
unclaimedDowns.delete(native.pointerId);
|
|
1033
|
+
captures.set(native.pointerId, { entity: cast2.entity, downObject: claimedBy });
|
|
1034
|
+
return true;
|
|
1035
|
+
}
|
|
1036
|
+
const top = cast2.hits[0]?.object;
|
|
1037
|
+
if (top !== void 0) unclaimedDowns.set(native.pointerId, { entity: cast2.entity, object: top });
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
const capture = captures.get(native.pointerId);
|
|
1041
|
+
if (kind === "move") {
|
|
1042
|
+
if (capture !== void 0) {
|
|
1043
|
+
const cast2 = castAt(screenX, screenY);
|
|
1044
|
+
const hits2 = cast2 !== void 0 && cast2.entity === capture.entity ? cast2.hits : [];
|
|
1045
|
+
dispatch("onPointerMove", "pointermove", capture.entity, hits2, native, capture.downObject);
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
if (native.buttons === 0) {
|
|
1049
|
+
const cast2 = castAt(screenX, screenY);
|
|
1050
|
+
syncHover(cast2, native);
|
|
1051
|
+
if (cast2 !== void 0) {
|
|
1052
|
+
dispatch("onPointerMove", "pointermove", cast2.entity, cast2.hits, native);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
if (capture === void 0) {
|
|
1058
|
+
const pending = unclaimedDowns.get(native.pointerId);
|
|
1059
|
+
unclaimedDowns.delete(native.pointerId);
|
|
1060
|
+
if (kind === "up" && pending !== void 0) {
|
|
1061
|
+
const cast2 = castAt(screenX, screenY);
|
|
1062
|
+
if (cast2 !== void 0 && cast2.entity === pending.entity && cast2.hits[0]?.object === pending.object) {
|
|
1063
|
+
dispatch("onClick", "click", cast2.entity, cast2.hits, native, pending.object);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
captures.delete(native.pointerId);
|
|
1069
|
+
const cast = castAt(screenX, screenY);
|
|
1070
|
+
const hits = cast !== void 0 && cast.entity === capture.entity ? cast.hits : [];
|
|
1071
|
+
if (kind === "up") {
|
|
1072
|
+
const { claimedBy } = dispatch("onPointerUp", "pointerup", capture.entity, hits, native, capture.downObject);
|
|
1073
|
+
const clickTarget = claimedBy ?? capture.downObject;
|
|
1074
|
+
if (clickTarget !== void 0 && clickTarget === capture.downObject) {
|
|
1075
|
+
dispatch("onClick", "click", capture.entity, hits, native, clickTarget);
|
|
1076
|
+
}
|
|
1077
|
+
} else {
|
|
1078
|
+
dispatch("onPointerCancel", "pointercancel", capture.entity, hits, native, capture.downObject);
|
|
1079
|
+
}
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
export {
|
|
1085
|
+
CompositeMaterial,
|
|
1086
|
+
GLViews,
|
|
1087
|
+
Island,
|
|
1088
|
+
IslandContext,
|
|
1089
|
+
RenderTargetPool,
|
|
1090
|
+
ResourceRegistry,
|
|
1091
|
+
createGLBridge,
|
|
1092
|
+
createGLPointerRouter,
|
|
1093
|
+
createIslandStateStore,
|
|
1094
|
+
createRenderWriteTrap,
|
|
1095
|
+
runCompositorPass,
|
|
1096
|
+
useIslandContext,
|
|
1097
|
+
useIslandFrame,
|
|
1098
|
+
useIslandInvalidate,
|
|
1099
|
+
useIslandLift
|
|
1100
|
+
};
|
|
1101
|
+
//# sourceMappingURL=r3f.js.map
|