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