refactor: make instance type metadata opaque

Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab

Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-07-29 06:13:25 +00:00
co-authored by heaust
parent 092b319d75
commit 0e866596c0
11 changed files with 110 additions and 166 deletions
+12 -17
View File
@@ -1,8 +1,7 @@
import { SnapshotReader } from "./render-data-snapshot.js";
export const VISIBLE = 1;
const HEADER_WORDS = 16, SLOT_WORDS = 40, CAPACITY = 1024, SLOT_VERSION = 2;
const OP = { IMPORT_GLB: 1, MESH_FLAGS: 2, CREATE_INSTANCE: 3, INSTANCE_VISIBLE: 4, INSTANCE_TRANSFORM: 5, DESTROY_INSTANCE: 6, COMPILE_GRAPH: 7, DROP_GRAPH: 8, SWITCH_GRAPH: 9, SET_INSTANCE_TYPE: 10 };
const OP = { IMPORT_GLB: 1, CREATE_INSTANCE: 3, INSTANCE_TRANSFORM: 5, DESTROY_INSTANCE: 6, COMPILE_GRAPH: 7, DROP_GRAPH: 8, SWITCH_GRAPH: 9, SET_INSTANCE_TYPE: 10 };
const HANDLE_TOKEN = Symbol("renderer handle");
export class RendererError extends Error {
@@ -157,11 +156,11 @@ export class RendererClient {
return promise;
}
#mesh(handle, defaultType = Array(16).fill(0)) {
#mesh(handle, defaultInstanceHandle, defaultType = Array(16).fill(0)) {
return new Mesh(HANDLE_TOKEN,
visible => { validateVisible(visible); return this.#enqueue(OP.MESH_FLAGS, [...handle, visible ? 1 : 0]); },
async (transform, {type = defaultType, visible} = {}) => {
type = typeWords(type); if (visible !== undefined) { validateVisible(visible); type[0] = (type[0] & ~1) | Number(visible); }
this.#instance(defaultInstanceHandle),
async (transform, {type = defaultType} = {}) => {
type = typeWords(type);
const result = await this.#enqueue(OP.CREATE_INSTANCE, [...handle, ...floatWords(transform), ...type]);
return this.#instance(result);
});
@@ -169,7 +168,6 @@ export class RendererClient {
#instance(handle) {
return new Instance(HANDLE_TOKEN,
visible => { validateVisible(visible); return this.#enqueue(OP.INSTANCE_VISIBLE, [...handle, visible ? 1 : 0]); },
type => this.#enqueue(OP.SET_INSTANCE_TYPE, [...handle, ...typeWords(type)]),
transform => this.#enqueue(OP.INSTANCE_TRANSFORM, [...handle, ...floatWords(transform)]),
() => this.#enqueue(OP.DESTROY_INSTANCE, [...handle]));
@@ -185,7 +183,7 @@ export class RendererClient {
else throw new TypeError("GLB source must be URL, File, or ArrayBuffer");
if (this.#disposed) throw new RendererError("DISPOSED");
const result = await this.#withPayload(buffer, OP.IMPORT_GLB, [framing === "interior" ? 1 : 0]);
return result.meshes.map(item => this.#mesh(item.handle, item.defaultType));
return result.meshes.map(item => this.#mesh(item.handle, item.defaultInstance, item.defaultType));
}
@@ -260,7 +258,6 @@ function validateCompiledId(compiledId) {
if (!Array.isArray(compiledId) || compiledId.length !== 2 || compiledId.some(word => !Number.isInteger(word) || word < 0 || word > 0xffffffff)) throw new TypeError("compiledId must contain exactly two uint32 values");
}
function validateVisible(value) { if (typeof value !== "boolean") throw new TypeError("visible must be boolean"); }
function typeWords(words) { if (!words || words.length !== 16 || [...words].some(x => !Number.isInteger(x) || x < 0 || x > 0xffffffff)) throw new TypeError("type must contain exactly 16 uint32 values"); return Array.from(words, x => x >>> 0); }
function floatWords(matrix) {
@@ -269,27 +266,25 @@ function floatWords(matrix) {
}
class Mesh {
#setVisible; #createInstance;
constructor(token, setVisible, createInstance) {
#defaultInstance; #createInstance;
constructor(token, defaultInstance, createInstance) {
if (token !== HANDLE_TOKEN) throw new TypeError("Mesh cannot be constructed directly");
this.#setVisible = setVisible;
this.#defaultInstance = defaultInstance;
this.#createInstance = createInstance;
}
setVisible(visible) { return this.#setVisible(visible); }
get defaultInstance() { return this.#defaultInstance; }
createInstance(transform, options = {}) { return this.#createInstance(transform, options); }
}
class Instance {
#setVisible; #setType; #setTransform; #destroy; #dead = false;
constructor(token, setVisible, setType, setTransform, destroy) {
#setType; #setTransform; #destroy; #dead = false;
constructor(token, setType, setTransform, destroy) {
if (token !== HANDLE_TOKEN) throw new TypeError("Instance cannot be constructed directly");
this.#setVisible = setVisible;
this.#setType = setType;
this.#setTransform = setTransform;
this.#destroy = destroy;
}
#live() { if (this.#dead) throw new RendererError("STALE_HANDLE"); }
setVisible(visible) { this.#live(); return this.#setVisible(visible); }
setType(words) { this.#live(); return this.#setType(words); }
setTransform(transform) { this.#live(); return this.#setTransform(transform); }
async destroy() { this.#live(); await this.#destroy(); this.#dead = true; }