/**
         * Executes the provided function `f` and tracks which observables are being accessed.
         * The tracking information is stored on the `derivation` object and the derivation is registered
         * as observer of any of the accessed observables.
         */
        public static T TrackedDerivedFunction <T>(this IDerivation derivation, Func <T> func, object context)
        {
            // pre allocate array allocation + room for variation in deps
            // array will be trimmed by bindDependencies
            derivation.ChangeDependenciesStateTo0();

            derivation.NewObservings    = new List <IObservable>(derivation.Observings.Count + 100);
            derivation.UnboundDepsCount = 0;
            derivation.RunId            = States.NextRunId;

            var previous = States.UntrackedStart(derivation);

            T result = default(T);

            if (States.State.DisableErrorBoundaries)
            {
                result = func();
            }
            else
            {
                try
                {
                    result = func();
                }
                catch (Exception ex)
                {
                    throw new CaughtException(ex);
                }
            }

            States.UntrackedEnd(previous);
            derivation.BindDependencies();
            return(result);
        }
        public static bool ShouldCompute(this IDerivation derivation)
        {
            switch (derivation.DependenciesState)
            {
            case DerivationState.UP_TO_DATE:
                return(false);

            case DerivationState.NOT_TRACKING:
            case DerivationState.STALE:
                return(true);

            case DerivationState.POSSIBLY_STALE:
            {
                var prevUntracked = States.UntrackedStart();         // no need for those computeds to be reported, they will be picked up in trackDerivedFunction.
                foreach (var observer in derivation.Observings)
                {
                    if (observer is IComputedValue computed)
                    {
                        if (States.State.DisableErrorBoundaries)
                        {
                            var x = computed.Value;
                        }
                        else
                        {
                            try
                            {
                                var y = computed.Value;
                            }
                            catch
                            {
                                // we are not interested in the value *or* exception at this moment, but if there is one, notify all
                                States.UntrackedEnd(prevUntracked);
                                return(true);
                            }
                        }
                        // if ComputedValue `obj` actually changed it will be computed and propagated to its observers.
                        // and `derivation` is an observer of `obj`
                        // invariantShouldCompute(derivation)
                        if (derivation.DependenciesState == DerivationState.STALE)
                        {
                            States.UntrackedEnd(prevUntracked);
                            return(true);
                        }
                    }
                }
                derivation.ChangeDependenciesStateTo0();
                States.UntrackedEnd(prevUntracked);
                return(false);
            }

            default:
                return(false);
            }
        }