@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
JavaScript
/**
* 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