Rebuild core around render graph AST and shared memory

Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-18 11:58:31 +00:00
co-authored by heaust
parent a23ef37b4d
commit 0e44917f9e
101 changed files with 4409 additions and 2610 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"name": "@yawn/gltf-import",
"version": "0.1.0",
"description": "glTF fetch worker that uploads directly into Yawn shared SOA memory",
"type": "module",
"exports": "./src/index.js"
}
+101
View File
@@ -0,0 +1,101 @@
export class GltfImportError extends Error {
constructor(code) {
super(code);
this.name = "GltfImportError";
this.code = code;
}
}
/** Fetches glTF in a dedicated worker and commits only shared-memory upload metadata. */
export class GltfImporter {
#core;
#worker;
#next = 1;
#pending = new Map();
#tail = Promise.resolve();
#disposed = false;
constructor(core, { workerFactory } = {}) {
if (!core?.allocateArray || !core?.commitGlbUpload) throw new TypeError("core must implement the Yawn shared upload protocol");
this.#core = core;
this.#worker = workerFactory
? workerFactory()
: new Worker(new URL("./worker.js", import.meta.url), {
type: "module",
name: "yawn-gltf-import",
});
this.#worker.addEventListener("message", event => this.#message(event.data));
this.#worker.addEventListener("error", () => this.#fail("GLTF_WORKER_ERROR"));
this.#worker.addEventListener("messageerror", () => this.#fail("GLTF_WORKER_ERROR"));
this.#worker.start?.();
}
load(url, options = {}) {
if (this.#disposed) return Promise.reject(new GltfImportError("DISPOSED"));
const source = url instanceof URL ? url.href : url;
if (typeof source !== "string" || !source) return Promise.reject(new TypeError("url must be a URL or nonempty string"));
const operation = this.#tail.then(() => this.#start(source, options));
this.#tail = operation.catch(() => {});
return operation;
}
#start(url, options) {
let request = this.#next++ >>> 0;
if (!request) request = this.#next++ >>> 0;
return new Promise((resolve, reject) => {
this.#pending.set(request, { resolve, reject, options, array: null });
this.#worker.postMessage({ type: "load", request, url });
});
}
async #message(message) {
const pending = this.#pending.get(message?.request);
if (!pending) return;
try {
if (message.type === "allocate") {
const length = Math.ceil(message.byteLength / 16);
pending.array = await this.#core.allocateArray({
name: "upload.gltf",
domain: "fixed",
scalar: "u32",
lanes: 4,
stride: 16,
length,
});
this.#worker.postMessage({
type: "storage",
request: message.request,
...pending.array.share(),
});
} else if (message.type === "ready") {
const result = await this.#core.commitGlbUpload(
pending.array,
message.byteLength,
pending.options,
);
this.#pending.delete(message.request);
pending.resolve(result);
} else if (message.type === "error") {
this.#pending.delete(message.request);
pending.reject(new GltfImportError(message.code || "GLTF_IMPORT_FAILED"));
}
} catch (error) {
this.#pending.delete(message.request);
pending.reject(error);
}
}
#fail(code) {
if (this.#disposed) return;
this.#disposed = true;
const error = new GltfImportError(code);
for (const pending of this.#pending.values()) pending.reject(error);
this.#pending.clear();
this.#worker.terminate?.();
}
dispose() {
if (this.#disposed) return;
this.#fail("DISPOSED");
}
}
+43
View File
@@ -0,0 +1,43 @@
const MAGIC = 0x414f5359;
/** Publishes one byte payload into a packed fixed SOA allocation. */
export function writeSharedUpload(buffer, descriptor, bytes) {
if (!(buffer instanceof SharedArrayBuffer) || !(bytes instanceof Uint8Array))
throw new TypeError("shared upload requires SharedArrayBuffer storage and Uint8Array bytes");
if (
!descriptor ||
descriptor.domain !== "fixed" ||
descriptor.scalar !== "u32" ||
descriptor.stride !== descriptor.lanes * 4 ||
descriptor.controlPtr % 64 !== 0 ||
descriptor.dataOffset !== 64 ||
bytes.byteLength < 1 ||
bytes.byteLength > descriptor.length * descriptor.lanes * 4
) throw new TypeError("invalid packed fixed SOA upload");
const control = new Int32Array(buffer, descriptor.controlPtr, 16);
if (
(Atomics.load(control, 0) >>> 0) !== MAGIC ||
(Atomics.load(control, 2) >>> 0) !== descriptor.id
) throw new Error("SOA_PROTOCOL_MISMATCH");
let sequence;
for (let attempt = 0; attempt < 1024; attempt++) {
const candidate = Atomics.load(control, 9) >>> 0;
if (!(candidate & 1) && (Atomics.compareExchange(control, 9, candidate | 0, (candidate + 1) | 0) >>> 0) === candidate) {
sequence = candidate;
break;
}
}
if (sequence === undefined) throw new Error("SOA_BUSY");
try {
new Uint8Array(
buffer,
descriptor.controlPtr + descriptor.dataOffset,
bytes.byteLength,
).set(bytes);
} finally {
Atomics.store(control, 9, (sequence + 2) | 0);
Atomics.notify(control, 9);
}
}
+28
View File
@@ -0,0 +1,28 @@
import { writeSharedUpload } from "./shared-upload.js";
const downloads = new Map();
addEventListener("message", async ({ data: message }) => {
const request = message?.request;
try {
if (message?.type === "load") {
const response = await fetch(message.url);
if (!response.ok) throw new Error(`HTTP_${response.status}`);
const bytes = new Uint8Array(await response.arrayBuffer());
if (!bytes.byteLength) throw new Error("GLTF_EMPTY");
downloads.set(request, bytes);
postMessage({ type: "allocate", request, byteLength: bytes.byteLength });
return;
}
if (message?.type === "storage") {
const bytes = downloads.get(request);
if (!bytes) throw new Error("GLTF_REQUEST_UNKNOWN");
writeSharedUpload(message.buffer, message.descriptor, bytes);
downloads.delete(request);
postMessage({ type: "ready", request, byteLength: bytes.byteLength });
}
} catch (error) {
downloads.delete(request);
postMessage({ type: "error", request, code: error?.message || "GLTF_IMPORT_FAILED" });
}
});