mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 14:20:35 +08:00
271 lines
8.2 KiB
JavaScript
271 lines
8.2 KiB
JavaScript
let cachedNativeModule = null;
|
|
let triedLoad = false;
|
|
let loadError = null;
|
|
let moduleSource = "none";
|
|
|
|
function shouldRetryNativeLoad() {
|
|
return (
|
|
!cachedNativeModule &&
|
|
triedLoad &&
|
|
typeof globalThis.__stBmeLoadRustWasmLayout === "function"
|
|
);
|
|
}
|
|
|
|
export function resetNativeModuleStatus() {
|
|
cachedNativeModule = null;
|
|
triedLoad = false;
|
|
loadError = null;
|
|
moduleSource = "none";
|
|
}
|
|
|
|
async function resolveWasmModuleInput(wasmUrl) {
|
|
if (
|
|
wasmUrl &&
|
|
typeof wasmUrl === "object" &&
|
|
wasmUrl.protocol === "file:" &&
|
|
typeof process === "object" &&
|
|
process?.versions?.node
|
|
) {
|
|
const { readFile } = await import("node:fs/promises");
|
|
return readFile(wasmUrl);
|
|
}
|
|
return wasmUrl;
|
|
}
|
|
|
|
async function initializeWasmModule(initFn, wasmUrl) {
|
|
if (typeof initFn !== "function") {
|
|
return;
|
|
}
|
|
|
|
const moduleInput = await resolveWasmModuleInput(wasmUrl);
|
|
|
|
try {
|
|
await initFn({ module_or_path: moduleInput });
|
|
} catch (error) {
|
|
if (
|
|
error &&
|
|
typeof error.message === "string" &&
|
|
/module_or_path|unexpected|invalid/i.test(error.message)
|
|
) {
|
|
await initFn(moduleInput);
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function loadFromWasmPackArtifacts() {
|
|
const module = await import("./pkg/stbme_core_pkg.js");
|
|
if (!module || typeof module.solve_layout !== "function") {
|
|
throw new Error("invalid wasm-pack module shape");
|
|
}
|
|
|
|
const wasmUrl = new URL("./pkg/stbme_core_pkg_bg.wasm", import.meta.url);
|
|
if (typeof module.default === "function") {
|
|
await initializeWasmModule(module.default, wasmUrl);
|
|
} else if (typeof module.__wbg_init === "function") {
|
|
await initializeWasmModule(module.__wbg_init, wasmUrl);
|
|
}
|
|
|
|
return {
|
|
solve_layout: module.solve_layout,
|
|
build_hydrate_records:
|
|
typeof module.build_hydrate_records === "function"
|
|
? module.build_hydrate_records
|
|
: null,
|
|
build_hydrate_records_compact:
|
|
typeof module.build_hydrate_records_compact === "function"
|
|
? module.build_hydrate_records_compact
|
|
: null,
|
|
build_persist_delta_compact_hash:
|
|
typeof module.build_persist_delta_compact_hash === "function"
|
|
? module.build_persist_delta_compact_hash
|
|
: null,
|
|
build_persist_delta_compact:
|
|
typeof module.build_persist_delta_compact === "function"
|
|
? module.build_persist_delta_compact
|
|
: null,
|
|
build_persist_delta:
|
|
typeof module.build_persist_delta === "function"
|
|
? module.build_persist_delta
|
|
: null,
|
|
};
|
|
}
|
|
|
|
async function loadNativeModule(options = {}) {
|
|
if (cachedNativeModule) return cachedNativeModule;
|
|
if (triedLoad && !(options?.forceRetry === true || shouldRetryNativeLoad())) {
|
|
throw loadError || new Error("stbme_core native module unavailable");
|
|
}
|
|
|
|
if (triedLoad && (options?.forceRetry === true || shouldRetryNativeLoad())) {
|
|
triedLoad = false;
|
|
loadError = null;
|
|
moduleSource = "none";
|
|
}
|
|
|
|
triedLoad = true;
|
|
|
|
let wasmPackError = null;
|
|
if (globalThis.__stBmeDisableWasmPackArtifacts !== true) {
|
|
try {
|
|
const wasmPackModule = await loadFromWasmPackArtifacts();
|
|
cachedNativeModule = wasmPackModule;
|
|
moduleSource = "wasm-pack-artifact";
|
|
return cachedNativeModule;
|
|
} catch (error) {
|
|
wasmPackError = error instanceof Error ? error : new Error(String(error));
|
|
}
|
|
} else {
|
|
wasmPackError = new Error("wasm-pack artifact loading disabled");
|
|
}
|
|
|
|
if (typeof globalThis.__stBmeLoadRustWasmLayout === "function") {
|
|
try {
|
|
const module = await globalThis.__stBmeLoadRustWasmLayout();
|
|
if (module && typeof module.solve_layout === "function") {
|
|
cachedNativeModule = module;
|
|
moduleSource = "global-loader";
|
|
return cachedNativeModule;
|
|
}
|
|
loadError = new Error("invalid native module shape");
|
|
throw loadError;
|
|
} catch (error) {
|
|
loadError = error instanceof Error ? error : new Error(String(error));
|
|
throw loadError;
|
|
}
|
|
}
|
|
|
|
loadError = new Error(
|
|
[
|
|
"Rust/WASM artifact is not initialized",
|
|
wasmPackError ? `wasm-pack load error: ${wasmPackError.message}` : "",
|
|
"define globalThis.__stBmeLoadRustWasmLayout for fallback injection",
|
|
]
|
|
.filter(Boolean)
|
|
.join("; "),
|
|
);
|
|
throw loadError;
|
|
}
|
|
|
|
function toFloat32Array(value) {
|
|
if (value instanceof Float32Array) return value;
|
|
if (!Array.isArray(value)) return new Float32Array(0);
|
|
return Float32Array.from(value.map((item) => Number(item) || 0));
|
|
}
|
|
|
|
export async function solveLayout(payload) {
|
|
const module = await loadNativeModule();
|
|
const raw = await module.solve_layout(payload);
|
|
const normalizedResult = raw && typeof raw === "object" ? raw : {};
|
|
const positions = toFloat32Array(normalizedResult.positions);
|
|
return {
|
|
ok: normalizedResult.ok === true,
|
|
usedNative: true,
|
|
positions,
|
|
diagnostics:
|
|
normalizedResult.diagnostics &&
|
|
typeof normalizedResult.diagnostics === "object"
|
|
? normalizedResult.diagnostics
|
|
: {
|
|
solver: "rust-wasm",
|
|
nodeCount: Math.floor(positions.length / 2),
|
|
edgeCount: 0,
|
|
iterations: 0,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function installNativePersistDeltaHook() {
|
|
const module = await loadNativeModule({
|
|
forceRetry: shouldRetryNativeLoad(),
|
|
});
|
|
if (
|
|
!module ||
|
|
(typeof module.build_persist_delta_compact_hash !== "function" &&
|
|
typeof module.build_persist_delta_compact !== "function" &&
|
|
typeof module.build_persist_delta !== "function")
|
|
) {
|
|
throw new Error("native persist delta builder unavailable");
|
|
}
|
|
|
|
globalThis.__stBmeNativeBuildPersistDelta = (beforeSnapshot, afterSnapshot, options = {}) => {
|
|
let raw = null;
|
|
const preparedInput =
|
|
options?.preparedDeltaInput && typeof options.preparedDeltaInput === "object"
|
|
? options.preparedDeltaInput
|
|
: null;
|
|
const preparedBridgeMode = String(preparedInput?.bridgeMode || "")
|
|
.trim()
|
|
.toLowerCase();
|
|
if (
|
|
typeof module.build_persist_delta_compact_hash === "function" &&
|
|
preparedInput &&
|
|
preparedBridgeMode === "hash"
|
|
) {
|
|
raw = module.build_persist_delta_compact_hash(preparedInput);
|
|
} else if (
|
|
typeof module.build_persist_delta_compact === "function" &&
|
|
preparedInput &&
|
|
(preparedBridgeMode === "json" || preparedBridgeMode === "")
|
|
) {
|
|
raw = module.build_persist_delta_compact(preparedInput);
|
|
} else if (
|
|
typeof module.build_persist_delta_compact === "function" &&
|
|
preparedInput &&
|
|
preparedBridgeMode === "hash" &&
|
|
Array.isArray(preparedInput?.afterNodes?.serialized)
|
|
) {
|
|
raw = module.build_persist_delta_compact(preparedInput);
|
|
} else if (typeof module.build_persist_delta === "function") {
|
|
raw = module.build_persist_delta({
|
|
beforeSnapshot,
|
|
afterSnapshot,
|
|
nowMs: options?.nowMs,
|
|
});
|
|
}
|
|
return raw && typeof raw === "object" ? raw : null;
|
|
};
|
|
|
|
return getNativeModuleStatus();
|
|
}
|
|
|
|
export async function installNativeHydrateHook() {
|
|
const module = await loadNativeModule({
|
|
forceRetry: shouldRetryNativeLoad(),
|
|
});
|
|
if (
|
|
!module ||
|
|
(typeof module.build_hydrate_records !== "function" &&
|
|
typeof module.build_hydrate_records_compact !== "function")
|
|
) {
|
|
throw new Error("native hydrate builder unavailable");
|
|
}
|
|
|
|
globalThis.__stBmeNativeHydrateSnapshotRecords = (snapshotView = {}, options = {}) => {
|
|
const hydratePayload = {
|
|
nodes: Array.isArray(snapshotView?.nodes) ? snapshotView.nodes : [],
|
|
edges: Array.isArray(snapshotView?.edges) ? snapshotView.edges : [],
|
|
recordsNormalized: options?.recordsNormalized === true,
|
|
preferCompactResult: options?.preferCompactResult !== false,
|
|
};
|
|
const raw =
|
|
typeof module.build_hydrate_records_compact === "function" &&
|
|
options?.preferCompactResult !== false
|
|
? module.build_hydrate_records_compact(hydratePayload)
|
|
: module.build_hydrate_records(hydratePayload);
|
|
return raw && typeof raw === "object" ? raw : null;
|
|
};
|
|
|
|
return getNativeModuleStatus();
|
|
}
|
|
|
|
export function getNativeModuleStatus() {
|
|
return {
|
|
loaded: Boolean(cachedNativeModule),
|
|
attempted: triedLoad,
|
|
source: moduleSource,
|
|
error: loadError?.message || "",
|
|
};
|
|
}
|