UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

138 lines 5.82 kB
import { isDevEnvironment } from "./debug/index.js"; import { getComponent } from "./engine_components.js"; import { ContextRegistry } from "./engine_context_registry.js"; import { TypeStore } from "./engine_typestore.js"; /** * Quick access to the current Needle Engine context from anywhere — no need to pass `ctx` around. * Use it in React/Svelte/Vue components, button handlers, or plain JavaScript. * * Safe to import at module level, including in SSR environments. * For pages with multiple `<needle-engine>` elements, use `ctx` directly instead. * * @experimental This API may change in future releases. * * @example * import { needle } from "@needle-tools/engine"; * button.onclick = () => { * needle.sceneData.Camera.OrbitControls.autoRotate = true; * }; */ export const needle = new Proxy({}, { get(_target, prop) { if (prop === "then") return undefined; // not a Promise const ctx = ContextRegistry.Current; if (!ctx) { const fn = isDevEnvironment() ? console.error : console.warn; fn(`[needle] needle.${prop} was accessed before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`); return makeErrorProxy(`needle not ready — scene hasn't started yet`); } const val = ctx[prop]; return typeof val === "function" ? val.bind(ctx) : val; }, set(_target, prop, value) { const ctx = ContextRegistry.Current; if (!ctx) { const fn = isDevEnvironment() ? console.error : console.warn; fn(`[needle] needle.${prop} was set before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`); return true; } ctx[prop] = value; return true; }, }); const cache = new WeakMap(); /** * Returns a proxy that silently absorbs any property get/set and logs a * developer-friendly warning. Used when a node or component lookup fails so * that chained access like `ctx.sceneData.Foo.Bar.baz = 1` never throws. */ function makeErrorProxy(message) { const handler = { get(_t, prop) { if (prop === "then") return undefined; // not a Promise const fn = isDevEnvironment() ? console.error : console.warn; fn(`[SceneData] ${message}`); return makeErrorProxy(message); }, set(_t, prop) { const fn = isDevEnvironment() ? console.error : console.warn; fn(`[SceneData] ${message} (tried to set "${prop}")`); return true; // suppress TypeError }, }; return new Proxy({}, handler); } /** * Returns a proxy for a scene node that exposes `$object`, `$components`, * and child nodes as nested properties. */ function makeNodeProxy(ctx, node) { return new Proxy({}, { get(_t, prop) { if (prop === "$object") return node; if (prop === "$components") { return new Proxy({}, { get(_t2, compName) { if (compName === "then") return undefined; const ctor = TypeStore.get(compName); if (!ctor) return makeErrorProxy(`Component type "${compName}" not registered (node "${node.name}")`); const comp = getComponent(node, ctor); if (!comp) return makeErrorProxy(`Component "${compName}" not found on node "${node.name}"`); return comp; } }); } if (prop === "then") return undefined; // not a Promise // Child node lookup by name const child = node.children.find(c => c.name === prop) ?? null; if (!child) { const fn = isDevEnvironment() ? console.error : console.warn; fn(`[SceneData] "${prop}" is not a child of "${node.name}". Use .$object to get the Three.js object or .$components.Name to access a component.`); return makeErrorProxy(`"${prop}" not found on node "${node.name}"`); } return makeNodeProxy(ctx, child); } }); } /** * Returns a lazily-resolved proxy typed as {@link SceneData}. * The proxy is cached per context — each context gets exactly one instance. * * Shape mirrors the generated `needle-bindings.gen.d.ts`: * ctx.sceneData.MyGlb.Camera.$components.OrbitControls.autoRotate = true; * ctx.sceneData.MyGlb.Camera.$object // → THREE.Camera * ctx.sceneData.MyGlb.UI.Button.$components.Button // → Needle Button component * * GLB name is ignored at runtime (scene is already loaded). * Node lookup starts at the scene root. */ export function getSceneData(ctx) { let proxy = cache.get(ctx); if (!proxy) { proxy = new Proxy({}, { get(_target, _glbName) { // GLB name level — ignored at runtime, return node-name proxy return new Proxy({}, { get(_target, nodeName) { if (nodeName === "then") return undefined; const node = ctx.scene.getObjectByName(nodeName) ?? null; if (!node) return makeErrorProxy(`Node "${nodeName}" not found in scene`); return makeNodeProxy(ctx, node); } }); } }); cache.set(ctx, proxy); } return proxy; } //# sourceMappingURL=engine_scenedata.js.map