diff --git a/package.json b/package.json index 3f8ba4e..d1f0388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rbxts/proton", - "version": "0.6.0", + "version": "0.6.1", "description": "Framework for Roblox game development", "main": "out/init.lua", "scripts": { diff --git a/src/component.ts b/src/component.ts index b53d814..472ebe5 100644 --- a/src/component.ts +++ b/src/component.ts @@ -283,6 +283,69 @@ export function getComponentStoppedSignal(componentClas return runner.componentStopped as Signal<[component: C]>; } +/** + * Observe each component instance during its lifetime. The `observer` function + * will be called for each component instance that starts. The observer should + * return a cleanup function, which will then be called when the component stops. + * + * A root cleanup function is returned from this function too, which will stop + * all observations and call all current cleanup functions from your observer. + * + * ```ts + * const stopObserving = observeComponent(MyComponent, (myComponent) => { + * print("myComponent instance started"); + * return () => { + * print("myComponent instance stopped"); + * }; + * }); + * + * // If observations should stop, call the returned cleanup function: + * stopObserving(); + * ``` + * + * @param componentClass Component class + * @param observer Observer function + * @returns Root cleanup + */ +export function observeComponent( + componentClass: new () => C, + observer: (component: C) => () => void, +) { + const runner = componentClassToRunner.get(componentClass); + if (runner === undefined) { + error("[Proton]: Invalid component class", 2); + } + + const cleanups = new Map void>(); + + const onStopped = (component: BaseComponent) => { + const cleanup = cleanups.get(component); + if (cleanup === undefined) return; + cleanups.delete(component); + cleanup(); + }; + + const onStarted = (component: BaseComponent) => { + onStopped(component); + const cleanup = observer(component as C); + cleanups.set(component, cleanup); + }; + + const startedConnection = runner.componentStarted.Connect(onStarted); + const stoppedConnection = runner.componentStopped.Connect(onStopped); + for (const component of runner.getAll()) { + task.spawn(onStarted, component); + } + + return () => { + startedConnection.Disconnect(); + stoppedConnection.Disconnect(); + for (const [_, cleanup] of cleanups) { + task.spawn(cleanup); + } + }; +} + /** * Add a component manually (bypass CollectionService). * @param componentClass Component class