Description
In certain cases a Producer changes to a new value, but then later resets to an older value. In the current spec / implementation all downstream Consumers must recompute, even if the value of all signals they are consuming are equal to the previous time they were consumed.
Imagine this scenario (example taken from #197):
let n = 0;
const s = new Signal.State(0);
const c = new Signal.Computed(() => (n++, s.get()));
c.get(); // this triggers a computation of c (so n passes from 0 to 1)
s.set(1); // this does not trigger anything because the computation of c is lazy
s.set(0); // let's come back to the previous value of s
c.get(); // if we recompute c, there is no need to call the function as the last time c was computed was with s = 0
expect(n).toBe(1); // so I am expecting n to still be 1
// but this test fails: there is an (unneeded) re-computation
The best way for a Producer to be able to return an old value and have consumers "just work" would be to allow it to return to an old value version. In order to support the ability to return to an old version this check must be removed from the implementation:
// First check the versions. A mismatch means that the producer's value is known to have
// changed since the last time we read it.
if (seenVersion !== producer.version) {
return true;
}
It can be improved by allowing the Producer to be asked if it can possibly return the seenVersion
:
if (seenVersion !== producer.version && !producer?.canMaybeProduceVersion(seenVersion)) {
return true;
}
Then the Producer implementation will be allowed to store a cache of N
previous values, mapping to the previous version numbers.
If the seenVersion
is in that map, then canMaybeProduceVersion
will return true, forcing a recalculate.
For a State
, when a new value is being set; For a Computed
, when a new value is calculated:
The new value will be compared against cached values using the equals
callback. The first entry that returns positively will be used, and the value version
will be reset to that number.
I think this should be an option during the construction of State
and Computed
like
const computed = new Signal.computed(() => state.get(), {cacheSize: 1});
If #255 is implemented then a value will be considered "dropped" when it is evicted from the cache. A newly calculated value will be immediately dropped if a cached value is going to be used instead.