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.

136 lines 4.32 kB
/** * Type guard to check if an object implements {@link IDisposable}. * * @example * ```ts * if (isDisposable(obj)) { * obj.dispose(); // safe to call * } * ``` */ export function isDisposable(value) { return value !== null && typeof value === "object" && "dispose" in value && typeof value.dispose === "function"; } export function on(target, type, listener, options) { target.addEventListener(type, listener, options); return { dispose() { target.removeEventListener(type, listener, options); } }; } /** * A store for managing disposable resources (event subscriptions, listeners, callbacks) * that should be cleaned up together. * * DisposableStore collects disposables and disposes them all at once when * {@link dispose} is called. After disposal, the store can be reused — new items * can be added and a subsequent {@link dispose} call will clean those up. * * This is the same pattern used internally by VSCode for lifecycle-bound resource management. * * @example Basic usage * ```ts * import { DisposableStore, on } from "@needle-tools/engine"; * * const store = new DisposableStore(); * * // Register a DOM event listener (typed!) * store.add(on(window, "resize", (ev) => console.log(ev))); * * // Register the return value of EventList.on() * store.add(myEventList.on(data => console.log(data))); * * // Register a raw cleanup function * store.add(() => someSDK.off("event", handler)); * * // Later: dispose everything at once * store.dispose(); * ``` * * @example Use with Needle Engine components * ```ts * import { Behaviour, serializable, EventList, on } from "@needle-tools/engine"; * * export class MyComponent extends Behaviour { * @serializable(EventList) * onClick?: EventList; * * onEnable() { * // DOM events — fully typed * this.autoCleanup(on(window, "resize", (ev) => this.onResize(ev))); * * // EventList — .on() returns a function, autoCleanup accepts it * this.autoCleanup(this.onClick?.on(() => console.log("clicked!"))); * } * // No onDisable needed — cleaned up automatically! * } * ``` * * @category Utilities * @group Lifecycle */ // #region DisposableStore export class DisposableStore { _disposables = []; /** The number of registered disposables */ get size() { return this._disposables.length; } /** * Register a disposable resource. Accepts: * - An {@link IDisposable} object (has a `dispose()` method) — e.g. from {@link on} * - A cleanup function (e.g. return value of `EventList.on()`) * - `null` or `undefined` (safe no-op for conditional subscriptions) * * When {@link dispose} is called, all registered resources are cleaned up. * * @param disposable The resource to register for disposal * * @example * ```ts * const store = new DisposableStore(); * * // IDisposable object from on() * store.add(on(window, "resize", handler)); * * // Function returned by EventList.on() * store.add(myEvent.on(handler)); * * // Raw cleanup function * store.add(() => connection.close()); * * // Conditional — safe with undefined * store.add(this.maybeEvent?.on(handler)); * ``` */ add(disposable) { if (!disposable) return; if (typeof disposable === "function") { this._disposables.push(disposable); } else if (typeof disposable === "object" && "dispose" in disposable) { this._disposables.push(() => disposable.dispose()); } } /** * Dispose all registered resources. Each registered disposable is cleaned up, * then the internal list is cleared. The store can be reused after disposal. * * Called automatically by the engine when a component's `onDisable` lifecycle fires. */ dispose() { for (let i = this._disposables.length - 1; i >= 0; i--) { try { this._disposables[i](); } catch (err) { console.error("Error disposing resource", err); } } this._disposables.length = 0; } } //# sourceMappingURL=engine_disposable.js.map