Skip to content

Signals should be able to efficiently return to an old value #256

Open
@DavidANeil

Description

@DavidANeil

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions