Ejemplo n.º 1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ComputedValue{T}"/> class.
        /// </summary>
        /// <param name="sharedState">The shared state this computedValue is connected to.</param>
        /// <param name="options">An <see cref="ComputedValueOptions{T}"/> instance that define the options for this computed value.</param>
        public ComputedValue(ISharedState sharedState, ComputedValueOptions <T> options)
        {
            if (sharedState is null)
            {
                throw new ArgumentNullException(nameof(sharedState));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            this.Name        = options.Name;
            this.derivation  = options.Getter;
            this.SharedState = sharedState;

            if (options.Setter != null)
            {
                this.setter = sharedState.CreateAction($"{options.Name}-setter", options.Context, options.Setter);
            }

            this.scope            = options.Context;
            this.equalityComparer = options.EqualityComparer;
            this.keepAlive        = options.KeepAlive;
            this.requiresReaction = options.RequiresReaction;
        }
Ejemplo n.º 2
0
        public Person(ISharedState sharedState)
        {
            observableObject = new ObservableObject(nameof(Person), sharedState.GetEnhancer(typeof(DeepEnhancer)), sharedState);
            observableObject.AddObservableProperty <string>(nameof(FirstName));
            observableObject.AddObservableProperty <string>(nameof(LastName));

            observableObject.AddComputedMember(nameof(FullName3), new ComputedValueOptions <string>(this.Getter, nameof(FullName3))
            {
                Context          = this,
                KeepAlive        = false,
                RequiresReaction = false,
                EqualityComparer = new ReferenceEqualityComparer <string>(),
                Setter           = this.Setter,
            });

            testAction = sharedState.CreateAction("ChangeBothNames", this, new Action <string, string>(this.ChangeBothNames));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ComputedValue{T}"/> class.
        /// </summary>
        /// <param name="sharedState">The shared state this computedValue is connected to.</param>
        /// <param name="options">An <see cref="ComputedValueOptions{T}"/> instance that define the options for this computed value.</param>
        internal ComputedValue(ISharedState sharedState, ComputedValueOptions <T> options)
        {
            // resolve state state from context if necessary.
            this.SharedState = Net.SharedState.ResolveState(sharedState);

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            this.Name       = options.Name;
            this.derivation = options.Getter;

            if (options.Setter != null)
            {
                this.setter = sharedState.CreateAction($"{options.Name}-setter", options.Context, options.Setter);
            }

            this.scope            = options.Context;
            this.equalityComparer = options.EqualityComparer;
            this.keepAlive        = options.KeepAlive;
            this.requiresReaction = options.RequiresReaction;
        }
        /// <summary>
        /// Creates a reaction that operates on data of type T.
        /// </summary>
        /// <typeparam name="T">The type the reaction operates on.</typeparam>
        /// <param name="sharedState">The shared state to use.</param>
        /// <param name="expression">The expression that delivers a value.</param>
        /// <param name="effect">The effect that is executed when the value changes.</param>
        /// <param name="reactionOptions">The options to use for the reaction.</param>
        /// <returns>An <see cref="IDisposable"/> instance that can be used to stop the reaction.</returns>
        public static IDisposable Reaction <T>(this ISharedState sharedState, Func <Reaction, T> expression, Action <T, Reaction> effect, ReactionOptions <T> reactionOptions = null)
        {
            if (sharedState is null)
            {
                throw new ArgumentNullException(nameof(sharedState));
            }

            if (expression is null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            if (effect is null)
            {
                throw new ArgumentNullException(nameof(effect));
            }

            if (reactionOptions is null)
            {
                reactionOptions = new ReactionOptions <T>();
            }

            var name   = reactionOptions.Name ?? $"Reaction@{sharedState.GetUniqueId()}";
            var action = sharedState.CreateAction(
                name + "effect",
                reactionOptions.Context,
                reactionOptions.ErrorHandler != null ? WrapErrorHandler(reactionOptions.ErrorHandler, effect) : effect);
            var runSync = reactionOptions.Scheduler == null && reactionOptions.Delay == 0;

            var      firstTime   = true;
            var      isScheduled = false;
            T        value       = default;
            Reaction reaction    = null;

            var equals =
                reactionOptions.EqualityComparer != null ?
                new Func <T, T, bool>(reactionOptions.EqualityComparer.Equals) :
                new Func <T, T, bool>((x, y) => Equals(x, y));

            var scheduler = CreateSchedulerFromOptions(reactionOptions, ReactionRunner);

#pragma warning disable CA2000  // Dispose objects before losing scope
#pragma warning disable IDE0067 // Dispose objects before losing scope
            reaction = new Reaction(
                sharedState,
                name,
                () =>
            {
                if (firstTime || runSync)
                {
                    ReactionRunner().GetAwaiter().GetResult();
                }
                else if (!isScheduled)
                {
                    isScheduled = true;
                    scheduler().GetAwaiter().GetResult();
                }
            }, reactionOptions.ErrorHandler);
#pragma warning restore IDE0067 // Dispose objects before losing scope
#pragma warning restore CA2000  // Dispose objects before losing scope

            Task ReactionRunner()
            {
                isScheduled = false; // Q: move into reaction runner?
                if (reaction.IsDisposed)
                {
                    return(Task.CompletedTask);
                }

                var changed = false;

                reaction.Track(() =>
                {
                    var nextValue = expression(reaction);
                    changed       = firstTime || !equals(value, nextValue);
                    value         = nextValue;
                });
                if (firstTime && reactionOptions.FireImmediately)
                {
                    action(value, reaction);
                }

                if (!firstTime && changed)
                {
                    action(value, reaction);
                }

                if (firstTime)
                {
                    firstTime = false;
                }

                return(Task.CompletedTask);
            }

            reaction.Schedule();
            return(new DisposableDelegate(() => reaction.Dispose()));
        }
        /// <summary>
        /// Creates a reaction that operates on data of type T.
        /// </summary>
        /// <typeparam name="T">The type the reaction operates on.</typeparam>
        /// <param name="sharedState">The shared state to use.</param>
        /// <param name="expression">The expression that delivers a value.</param>
        /// <param name="effect">The effect that is executed when the value changes.</param>
        /// <param name="reactionOptions">The options to use for the reaction.</param>
        /// <returns>An <see cref="IDisposable"/> instance that can be used to stop the reaction.</returns>
        public static IDisposable Reaction <T>(this ISharedState sharedState, Func <Reaction, T> expression, Action <T, Reaction> effect, ReactionOptions <T> reactionOptions = null)
        {
            if (sharedState is null)
            {
                throw new ArgumentNullException(nameof(sharedState));
            }

            if (expression is null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            if (effect is null)
            {
                throw new ArgumentNullException(nameof(effect));
            }

            if (reactionOptions is null)
            {
                reactionOptions = new ReactionOptions <T>();
            }

            var name   = reactionOptions.Name ?? $"Reaction@{sharedState.GetUniqueId()}";
            var action = sharedState.CreateAction(
                name + "effect",
                reactionOptions.Context,
                reactionOptions.ErrorHandler != null ? WrapErrorHandler(reactionOptions.ErrorHandler, effect) : effect);
            var runSync = reactionOptions.Scheduler == null && reactionOptions.Delay == 0;

            var      firstTime   = true;
            var      isScheduled = false;
            T        value       = default;
            Reaction reaction    = null;

            var equals =
                reactionOptions.EqualityComparer != null ?
                new Func <T, T, bool>(reactionOptions.EqualityComparer.Equals) :
                new Func <T, T, bool>((x, y) => Equals(x, y));

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
            var scheduler = CreateSchedulerFromOptions(reactionOptions, async() =>
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
            {
                ReactionRunner();
            });

#pragma warning disable CA2000  // Dispose objects before losing scope
#pragma warning disable IDE0067 // Dispose objects before losing scope
            reaction = new Reaction(
                sharedState,
                name,
                () =>
            {
                if (firstTime || runSync)
                {
                    ReactionRunner();
                }
                else if (!isScheduled)
                {
                    var taskScheduler = sharedState.GetTaskScheduler();
                    isScheduled       = true;
                    Task.Factory.StartNew(
                        scheduler,
                        CancellationToken.None,
                        TaskCreationOptions.DenyChildAttach,
                        taskScheduler).Unwrap().ContinueWith(
                        t =>
                    {
                        if (t.Exception != null)
                        {
                            reaction.ReportExceptionInReaction(t.Exception);
                        }
                    }, taskScheduler);
                }
            }, reactionOptions.ErrorHandler);
#pragma warning restore IDE0067 // Dispose objects before losing scope
#pragma warning restore CA2000  // Dispose objects before losing scope

            void ReactionRunner()
            {
                isScheduled = false; // Q: move into reaction runner?
                if (reaction.IsDisposed)
                {
                    return;
                }

                var changed = false;

                reaction.Track(() =>
                {
                    var nextValue = expression(reaction);
                    changed       = firstTime || !equals(value, nextValue);
                    value         = nextValue;
                });
                if (firstTime && reactionOptions.FireImmediately)
                {
                    action(value, reaction);
                }

                if (!firstTime && changed)
                {
                    action(value, reaction);
                }

                if (firstTime)
                {
                    firstTime = false;
                }
            }

            reaction.Schedule();
            return(new DisposableDelegate(() => reaction.Dispose()));
        }