Computed signals should expose a callback that is invoked when a value is dropped
The Computed API allows for a the computation callback to be invoked under various different conditions, and this issue isn't for discussing that contract. After the callback is invoked, the node can either determine that the oldValue is to be dropped and assign the internal value slot to the newValue; or it can determine that the newValue and oldValue are equivalent, and reset the internal value to the oldValue; or there can be no oldValue, so newValue is definitely stored in the value slot.
In the first two cases a value is being abandoned, and for certain types of values there might be some amount of cleanup that might be appropriate.
Example use
const computed = new Signal.Computed(() => {
return createDisposable(someData.get());
}, {
equals: objectEquals,
onDropValue(disposableValue: Disposable) {
disposableValue[Symbol.dispose]();
},
});
This will likely replace some uses of Signal.subtle.watched and Signal.subtle.unwatched, but will work in all conditions, instead of only when watchers are involved.
This API allows Computed signals to be well-behaved owners of objects that they create, which is important as it can be difficult to predict externally which values that have been produced by a Computed need cleanup work done.
Another way of accomplishing this is by using an equals that always returns false, and then always cleaning up previous values, but this forces the state to managed externally, precludes use of more meaningful equals checks, and does not behave well with unexpected changes like the free method described in #254.
Naive onDropValue:
let lastValue: Disposable | undefined;
const computed = new Signal.Computed(() => {
if (lastValue) {
Signal.subtle.untrack(() => lastValue[Symbol.dispose]());
}
lastValue = createDisposable(someData.get());
return lastValue;
}, {
equals: () => false,
});