/// <summary> /// Creates an reaction that should run every time anything it observes changes. /// It also runs once when you create the reaction itself. It only responds /// to changes in observable state, things you have annotated atom. /// </summary> /// <param name="reaction">A function for reaction.</param> /// <param name="exceptionHandler">A function that called when an exception is thrown while computing an reaction.</param> /// <param name="debugName">Debug name for this reaction.</param> /// <returns>Created reaction.</returns> /// <example> /// /// var counter = Atom.Value(1); /// /// var reaction = Atom.Reaction(() => /// { /// Debug.Log("Counter: " + counter.Value); /// }); /// /// </example> public static Reaction Reaction( Action reaction, Action <Exception> exceptionHandler = null, string debugName = null) { var atom = new ReactionAtom(debugName, reaction, exceptionHandler); atom.Activate(); return(atom); }
/// <summary> /// Creates an reaction that should run every time anything it observes changes. /// It also runs once when you create the reaction itself. It only responds /// to changes in observable state, things you have annotated atom. /// </summary> /// <param name="lifetime">Reaction lifetime.</param> /// <param name="reaction">A function for reaction.</param> /// <param name="exceptionHandler">A function that called when an exception is thrown while computing an reaction.</param> /// <param name="debugName">Debug name for this reaction.</param> /// <returns>Created reaction.</returns> /// <example> /// /// var counter = Atom.Value(1); /// /// Atom.Reaction(Lifetime, () => /// { /// Debug.Log("Counter: " + counter.Value); /// }); /// /// </example> public static Reaction Reaction( Lifetime lifetime, Action reaction, Action<Exception> exceptionHandler = null, string debugName = null) { var atom = new ReactionAtom(debugName, reaction, exceptionHandler); lifetime.Register(atom); atom.Activate(); return atom; }
/// <summary> /// Creates an reaction that observes and runs the given predicate function /// until it returns true. Once that happens, the given effect function /// is executed and the reaction is deactivated.<br/> /// <br/> /// Returns a reaction, allowing you to cancel it manually. /// </summary> /// <param name="p">An observed predicate function.</param> /// <param name="sideEffect">An effect function.</param> /// <param name="exceptionHandler">A function that called when an exception is thrown while computing an reaction.</param> /// <param name="debugName">Debug name for this reaction.</param> /// <returns>Created reaction.</returns> /// <example> /// /// var counter = Atom.Value(1); /// /// var reaction = Atom.When( /// () => counter.Value == 10, /// () => Debug.Log("Counter value equals 10") /// ); /// /// </example> public static Reaction When( Func <bool> p, Action sideEffect, Action <Exception> exceptionHandler = null, string debugName = null) { Reaction watcher = null; watcher = new ReactionAtom(debugName, () => { Exception exception = null; try { if (!p()) { return; } } catch (Exception e) { exception = e; } if (exception != null && exceptionHandler == null) { Debug.LogException(exception); return; } using (NoWatch) { if (exception != null) { exceptionHandler(exception); } else { sideEffect(); } // ReSharper disable once AccessToModifiedClosure watcher?.Deactivate(); watcher = null; } }); watcher.Activate(); return(watcher); }
/// <summary> /// Creates an reaction that takes two functions: /// the first, data function, is tracked and returns the data that is used /// as input for the second, effect function. It is important to note that /// the side effect only reacts to data that was accessed in the data function, /// which might be less than the data that is actually used in the effect function. /// /// The typical pattern is that you produce the things you need in your side effect /// in the data function, and in that way control more precisely when the effect triggers. /// </summary> /// <param name="reaction">A data function.</param> /// <param name="effect">A side effect function.</param> /// <param name="exceptionHandler">A function that called when an exception is thrown while computing an reaction.</param> /// <param name="comparer">Data comparer used for reconciling.</param> /// <param name="fireImmediately">Should the side effect runs once when you create a reaction itself? Default value is true.</param> /// <param name="debugName">Debug name for this reaction.</param> /// <typeparam name="T">Reaction data type.</typeparam> /// <returns>Created reaction.</returns> /// <example> /// /// var counter = Atom.Value(1); /// /// var reaction = Atom.Reaction( /// () => counter.Value, /// (val, reactionHandle) => /// { /// Debug.Log("Counter: " + val); /// if (val == 10) /// { /// reactionHandle.Deactivate(); /// } /// } /// ); /// /// </example> public static Reaction Reaction <T>( AtomPull <T> reaction, Action <T, Reaction> effect, Action <Exception> exceptionHandler = null, IEqualityComparer <T> comparer = null, bool fireImmediately = true, string debugName = null) { var valueAtom = Computed(reaction, comparer: comparer); bool firstRun = true; Reaction atom = null; atom = new ReactionAtom(debugName, () => { var value = valueAtom.Value; using (NoWatch) { if (firstRun) { firstRun = false; if (!fireImmediately) { return; } } // ReSharper disable once AccessToModifiedClosure effect(value, atom); } }, exceptionHandler); atom.Activate(); return(atom); }
/// <summary> /// Creates an reaction that observes and runs the given predicate function /// until it returns true. Once that happens, the returned task is completed /// and the reaction is deactivated.<br/> /// <br/> /// The task will fail if the predicate throws an exception. /// </summary> /// <param name="p">An observed predicate function.</param> /// <param name="cancellationToken">Token for reaction cancellation.</param> /// <param name="debugName">Debug name for this reaction.</param> /// <returns>Task that completes when the predicate returns true or predicate function throws exception.</returns> /// <example> /// /// var counter = Atom.Value(1); /// /// await Atom.When(() => counter.Value == 10); /// Debug.Log("Counter value equals 10"); /// /// </example> public static Task When( Func <bool> p, CancellationToken cancellationToken = default, string debugName = null) { var taskCompletionSource = new TaskCompletionSource <object>(); Reaction watcher = null; CancellationTokenRegistration?cancellationTokenRegistration = null; void Dispose() { taskCompletionSource.TrySetCanceled(); // ReSharper disable once AccessToModifiedClosure cancellationTokenRegistration?.Dispose(); // ReSharper disable once AccessToModifiedClosure watcher?.Deactivate(); watcher = null; } watcher = new ReactionAtom(debugName, () => { Exception exception = null; try { if (!p()) { return; } } catch (Exception e) { exception = e; } using (NoWatch) { if (exception != null) { taskCompletionSource.TrySetException(exception); } else { taskCompletionSource.TrySetResult(null); } Dispose(); } }); if (cancellationToken.IsCancellationRequested) { Dispose(); } else { watcher.Activate(); if (cancellationToken.CanBeCanceled) { cancellationTokenRegistration = cancellationToken.Register(Dispose, true); } } return(taskCompletionSource.Task); }