Publish Forma Engine 0.3.0 source with documentation and CI
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
let entities = [],
|
||||
scripts = [],
|
||||
instances = [],
|
||||
input = {},
|
||||
physics = {},
|
||||
commands = [],
|
||||
frame = 0;
|
||||
const merge = (a, b) => {
|
||||
for (const [k, v] of Object.entries(b)) {
|
||||
if (["__proto__", "constructor", "prototype"].includes(k)) continue;
|
||||
a[k] =
|
||||
v && typeof v === "object" && !Array.isArray(v)
|
||||
? merge(a[k] || {}, v)
|
||||
: v;
|
||||
}
|
||||
return a;
|
||||
};
|
||||
function api(i) {
|
||||
return {
|
||||
state: i.state,
|
||||
params: i.params,
|
||||
get input() {
|
||||
return input;
|
||||
},
|
||||
physics: (id = i.id) => structuredClone(physics[id] || { grounded: false, velocity: { x: 0, y: 0, z: 0 }, contacts: [] }),
|
||||
velocity(value) { commands.push({ type: "velocity", id: i.id, value }); },
|
||||
teleport(position, yaw) { commands.push({ type: "teleport", id: i.id, position, yaw }); },
|
||||
emit(name, data = {}) { commands.push({ type: "event", id: i.id, name, data }); },
|
||||
get: (id = i.id) =>
|
||||
structuredClone(entities.find((n) => n.id === id) || null),
|
||||
entities: () => structuredClone(entities),
|
||||
position: (id = i.id) => [
|
||||
...(entities.find((n) => n.id === id)?.transform.position || [0, 0, 0]),
|
||||
],
|
||||
patch(id, patch) {
|
||||
const n = entities.find((n) => n.id === id);
|
||||
if (n) {
|
||||
merge(n, structuredClone(patch));
|
||||
commands.push({ type: "patch", id, patch });
|
||||
}
|
||||
},
|
||||
move(delta) {
|
||||
commands.push({ type: "move", id: i.id, delta });
|
||||
},
|
||||
rotate(y) {
|
||||
this.patch(i.id, { transform: { rotation: [0, y, 0] } });
|
||||
},
|
||||
animate(name, loop = true) {
|
||||
commands.push({ type: "animate", id: i.id, name, loop });
|
||||
},
|
||||
effect(name, id = i.id) {
|
||||
commands.push({ type: "effect", id, name });
|
||||
},
|
||||
log(message) {
|
||||
commands.push({ type: "log", id: i.id, message: String(message) });
|
||||
},
|
||||
destroy(id = i.id) {
|
||||
this.patch(id, { enabled: false });
|
||||
},
|
||||
spawn(template, position) {
|
||||
commands.push({ type: "spawn", template, position });
|
||||
},
|
||||
scene(sceneId) {
|
||||
commands.push({ type: "scene", sceneId });
|
||||
},
|
||||
};
|
||||
}
|
||||
function fail(i, e) {
|
||||
i.failed = true;
|
||||
commands.push({
|
||||
type: "error",
|
||||
id: i.id,
|
||||
scriptId: i.scriptId,
|
||||
message: String(e?.stack || e),
|
||||
});
|
||||
}
|
||||
function reconcile() {
|
||||
instances = instances.filter((i) =>
|
||||
entities.some(
|
||||
(n) => n.id === i.id && n.components.script?.scriptId === i.scriptId,
|
||||
),
|
||||
);
|
||||
for (const n of entities) {
|
||||
if (!n.enabled || instances.some((i) => i.id === n.id)) continue;
|
||||
const binding = n.components.script,
|
||||
def = scripts.find((s) => s.id === binding?.scriptId);
|
||||
if (!def) continue;
|
||||
const i = {
|
||||
id: n.id,
|
||||
scriptId: def.id,
|
||||
state: {},
|
||||
params: {
|
||||
...Object.fromEntries(
|
||||
Object.entries(def.fields).map(([k, v]) => [k, v.default]),
|
||||
),
|
||||
...binding.params,
|
||||
},
|
||||
behavior: null,
|
||||
failed: false,
|
||||
};
|
||||
instances.push(i);
|
||||
try {
|
||||
i.behavior = new Function("return (" + def.source + ");")();
|
||||
if (!i.behavior || typeof i.behavior !== "object")
|
||||
throw Error("Скрипт должен вернуть {start, update}");
|
||||
i.behavior.start?.(api(i));
|
||||
} catch (e) {
|
||||
fail(i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.onmessage = (e) => {
|
||||
const m = e.data;
|
||||
commands = [];
|
||||
entities = m.entities;
|
||||
physics = m.physics || {};
|
||||
if (m.type === "init") {
|
||||
scripts = m.scripts;
|
||||
instances = [];
|
||||
reconcile();
|
||||
postMessage({ type: "ready", commands });
|
||||
} else {
|
||||
input = m.input;
|
||||
reconcile();
|
||||
for (const i of instances) {
|
||||
if (i.failed || !entities.some((n) => n.id === i.id && n.enabled))
|
||||
continue;
|
||||
try {
|
||||
i.behavior.update?.(api(i), m.dt);
|
||||
} catch (e) {
|
||||
fail(i, e);
|
||||
}
|
||||
}
|
||||
postMessage({
|
||||
type: "frame",
|
||||
frame: ++frame,
|
||||
commands:
|
||||
commands.length < 5000
|
||||
? commands
|
||||
: [{ type: "error", message: "Лимит 5000 команд за кадр" }],
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user