public void CurrentAndFutureItems() {
        var source = new LifetimeSource();
        var p1 = new Perishable<int>(1, source.Lifetime);
        var p2 = new Perishable<int>(1, Lifetime.Immortal);
        var p = new PerishableCollection<int>();
        
        var li0 = new List<Perishable<int>>();
        p.CurrentAndFutureItems().Subscribe(e => {
            li0.Add(e);
            e.Lifetime.WhenDead(() => li0.Remove(e));
        });
        li0.AssertSequenceEquals();

        p.Add(p1);
        li0.AssertSequenceEquals(p1);
        
        var li1 = new List<Perishable<int>>();
        p.CurrentAndFutureItems().Subscribe(e => {
            li1.Add(e);
            e.Lifetime.WhenDead(() => li1.Remove(e));
        });
        li1.AssertSequenceEquals(p1);
        
        p.Add(p2.Value, p2.Lifetime);
        li0.AssertSequenceEquals(p1, p2);
        li1.AssertSequenceEquals(p1, p2);
        
        source.EndLifetime();
        li0.AssertSequenceEquals(p2);
        li1.AssertSequenceEquals(p2);
    }
    public void CurrentAndFutureItems()
    {
        var source = new LifetimeSource();
        var p1     = new Perishable <int>(1, source.Lifetime);
        var p2     = new Perishable <int>(1, Lifetime.Immortal);
        var p      = new PerishableCollection <int>();

        var li0 = new List <Perishable <int> >();

        p.CurrentAndFutureItems().Subscribe(e => {
            li0.Add(e);
            e.Lifetime.WhenDead(() => li0.Remove(e));
        });
        li0.AssertSequenceEquals();

        p.Add(p1);
        li0.AssertSequenceEquals(p1);

        var li1 = new List <Perishable <int> >();

        p.CurrentAndFutureItems().Subscribe(e => {
            li1.Add(e);
            e.Lifetime.WhenDead(() => li1.Remove(e));
        });
        li1.AssertSequenceEquals(p1);

        p.Add(p2.Value, p2.Lifetime);
        li0.AssertSequenceEquals(p1, p2);
        li1.AssertSequenceEquals(p1, p2);

        source.EndLifetime();
        li0.AssertSequenceEquals(p2);
        li1.AssertSequenceEquals(p2);
    }
예제 #3
0
        ///<summary>Handles periodically adding children to existing balls.</summary>
        public static void SetupPeriodicChildSpawning(this Game game, double expectedPerBallPerSecond, int maxChildrenPerBall, int maxGeneration)
        {
            game.Balls.CurrentAndFutureItems().Subscribe(
                ball => {
                if (ball.Value.Generation > maxGeneration)
                {
                    return;
                }

                // keep track of children
                var curChildCount = 0;
                var children      = new PerishableCollection <Ball>();
                children
                .CurrentAndFutureItems()
                .ObserveNonPerishedCount(completeWhenSourceCompletes: true)
                .Subscribe(e => curChildCount = e, ball.Lifetime);

                // spawn children periodically at random
                game.StochasticRate(
                    expectedPerBallPerSecond,
                    ball.Lifetime,
                    () => {
                    if (curChildCount >= maxChildrenPerBall)
                    {
                        return;
                    }
                    var child = game.SpawnBall(parent: ball.Value);
                    game.Connectors.Add(new Connector {
                        Child = child, Parent = ball.Value
                    }, child.Life.Lifetime);
                    children.Add(child, child.Life.Lifetime);
                });
            },
                game.Life);
        }
 ///<summary>Handles periodically adding children to existing balls.</summary>
 public static void SetupPeriodicChildSpawning(this Game game, double expectedPerBallPerSecond, int maxChildrenPerBall, int maxGeneration) {
     game.Balls.CurrentAndFutureItems().Subscribe(
         ball => {
             if (ball.Value.Generation > maxGeneration) return;
             
             // keep track of children
             var curChildCount = 0;
             var children = new PerishableCollection<Ball>();
             children
                 .CurrentAndFutureItems()
                 .ObserveNonPerishedCount(completeWhenSourceCompletes: true)
                 .Subscribe(e => curChildCount = e, ball.Lifetime);
             
             // spawn children periodically at random
             game.StochasticRate(
                 expectedPerBallPerSecond,
                 ball.Lifetime,
                 () => {
                     if (curChildCount >= maxChildrenPerBall) return;
                     var child = game.SpawnBall(parent: ball.Value);
                     game.Connectors.Add(new Connector {Child = child, Parent = ball.Value}, child.Life.Lifetime);
                     children.Add(child, child.Life.Lifetime);
                 });
         },
         game.Life);
 }
    public void CurrentAndFutureItems_EndingDuringEnumerationDoesNotLockup() {
        var s = new LifetimeSource();
        var p = new PerishableCollection<int>();
        for (var i = 0; i < 1000; i++) {
            p.Add(i, Lifetime.Immortal);
        }

        var t = Task.Factory.StartNew(
            () => p.CurrentAndFutureItems().Subscribe(
                item => s.EndLifetime(),
                s.Lifetime));
        t.Wait(TimeSpan.FromMilliseconds(100));
        t.AssertRanToCompletion();
    }
    public void ToPerishableCollection() {
        var source = new LifetimeSource();
        var p1 = new Perishable<int>(1, source.Lifetime);
        var p2 = new Perishable<int>(1, Lifetime.Immortal);
        var p = new PerishableCollection<int>();
        var q = p.CurrentAndFutureItems().ToPerishableCollection();
        q.CurrentItems().AssertSequenceEquals();

        p.Add(p1);
        q.CurrentItems().AssertSequenceEquals(p1);

        p.Add(p2.Value, p2.Lifetime);
        q.CurrentItems().AssertSequenceEquals(p1, p2);

        source.EndLifetime();
        q.CurrentItems().AssertSequenceEquals(p2);
    }
    public void CurrentAndFutureItems_EndingDuringEnumerationDoesNotLockup()
    {
        var s = new LifetimeSource();
        var p = new PerishableCollection <int>();

        for (var i = 0; i < 1000; i++)
        {
            p.Add(i, Lifetime.Immortal);
        }

        var t = Task.Factory.StartNew(
            () => p.CurrentAndFutureItems().Subscribe(
                item => s.EndLifetime(),
                s.Lifetime));

        t.Wait(TimeSpan.FromMilliseconds(100));
        t.AssertRanToCompletion();
    }
    public void ToPerishableCollection()
    {
        var source = new LifetimeSource();
        var p1     = new Perishable <int>(1, source.Lifetime);
        var p2     = new Perishable <int>(1, Lifetime.Immortal);
        var p      = new PerishableCollection <int>();
        var q      = p.CurrentAndFutureItems().ToPerishableCollection();

        q.CurrentItems().AssertSequenceEquals();

        p.Add(p1);
        q.CurrentItems().AssertSequenceEquals(p1);

        p.Add(p2.Value, p2.Lifetime);
        q.CurrentItems().AssertSequenceEquals(p1, p2);

        source.EndLifetime();
        q.CurrentItems().AssertSequenceEquals(p2);
    }
    public void IsObservationThreadSafe()
    {
        foreach (var repeat in Enumerable.Range(0, 5))
        {
            var n      = 5000;
            var nt     = 4;
            var p      = new PerishableCollection <int>();
            var queues = Enumerable.Range(0, 4).Select(e => new Queue <LifetimeSource>()).ToArray();
            var li     = new List <int>();
            p.CurrentAndFutureItems().Subscribe(
                e => {
                lock (li) li.Add(e.Value);
                e.Lifetime.WhenDead(() => { lock (li) li.Remove(e.Value); });
            });
            TestUtil.ConcurrencyTest(
                threadCount: nt,
                callbackCount: n,
                repeatedWork: (t, i) => {
                var r = new LifetimeSource();
                p.Add(i, r.Lifetime);
                if (i % 2 == 0)
                {
                    queues[t].Enqueue(r);
                }
                if (queues[t].Count > 20)
                {
                    queues[t].Dequeue().EndLifetime();
                }
            },
                finalWork: t => {
                while (queues[t].Count > 0)
                {
                    queues[t].Dequeue().EndLifetime();
                }
            });

            var expected = Enumerable.Range(0, n).Where(e => e % 2 != 0).SelectMany(e => Enumerable.Repeat(e, nt));
            li.OrderBy(e => e).AssertSequenceEquals(expected);
        }
    }
        private void SetupAndRunGame(Game game, bool initial)
        {
            // controls added to this collection should be displayed on the canvas until they perish
            var controls = new PerishableCollection <UIElement>();

            controls.CurrentAndFutureItems().Subscribe(
                e => {
                canvas.Children.Add(e.Value);
                e.Lifetime.WhenDead(() => canvas.Children.Remove(e.Value));
            },
                game.Life);

            // balls should move and bounce off borders
            game.SetupMoveAndBounceBalls(
                playArea: () => new Rect(0, 0, canvas.ActualWidth, canvas.ActualHeight));

            // connected balls should be be gently tugged towards each other
            game.SetupAttractBalls(
                deadRadius: 50,
                accellerationPerSecondChild: 10,
                accellerationPerSecondParent: 5);

            // balls should periodically spawn dependent children
            game.SetupPeriodicChildSpawning(
                expectedPerBallPerSecond: 0.2,
                maxChildrenPerBall: 2,
                maxGeneration: 5);

            // balls should be drawn using ellipse controls and have death animations
            game.SetupDrawBallsAsControls(
                controls,
                deathFadeOutDuration: 800.Milliseconds(),
                deathFinalRadiusFactor: 3);

            // ball connectors should be drawn using line controls and have cut and death animations
            game.SetupDrawConnectorsAsControls(
                controls,
                deathFadeDuration: 800.Milliseconds(),
                deathFinalThicknessFactor: 6,
                propagateBangColor: Colors.Green,
                propagateBangDuration: 400.Milliseconds(),
                propagateBangRotationsPerSecond: 3,
                propagateBangMaxRadius: 10,
                cutBangColor: Colors.Red,
                cutBangDuration: 400.Milliseconds(),
                cutBangMaxRadius: 15);

            // connectors that touch the cursor should die
            SetupMouseCutter(game, controls);

            // text displays of game state should track that state
            if (!initial)
            {
                SetupEnergyAndTime(
                    game,
                    energyLossForCutting: 2.5,
                    energyGainPerConnectorBroken: 1);
            }

            // there should be a few root balls to start with
            foreach (var repeat in 5.Range())
            {
                game.SpawnBall(parent: new Ball {
                    Pos    = new Point(game.Rng.NextDouble() * canvas.ActualWidth, game.Rng.NextDouble() * canvas.ActualHeight),
                    Radius = 10,
                    Life   = new LifetimeSource(),
                    Hue    = game.Rng.NextDouble() * 3
                });
            }

            // run the game loop until the game is over
            game.Loop().ContinueWith(e => {
                // exceptions?
            });
        }
    public void IsObservationThreadSafe() {
        foreach (var repeat in Enumerable.Range(0, 5)) {
            var n = 5000;
            var nt = 4;
            var p = new PerishableCollection<int>();
            var queues = Enumerable.Range(0, 4).Select(e => new Queue<LifetimeSource>()).ToArray();
            var li = new List<int>();
            p.CurrentAndFutureItems().Subscribe(
                e => {
                    lock (li) li.Add(e.Value);
                    e.Lifetime.WhenDead(() => { lock (li) li.Remove(e.Value); });
                });
            TestUtil.ConcurrencyTest(
                threadCount: nt,
                callbackCount: n,
                repeatedWork: (t, i) => {
                    var r = new LifetimeSource();
                    p.Add(i, r.Lifetime);
                    if (i % 2 == 0) queues[t].Enqueue(r);
                    if (queues[t].Count > 20)
                        queues[t].Dequeue().EndLifetime();
                },
                finalWork: t => {
                    while (queues[t].Count > 0)
                        queues[t].Dequeue().EndLifetime();
                });

            var expected = Enumerable.Range(0, n).Where(e => e % 2 != 0).SelectMany(e => Enumerable.Repeat(e, nt));
            li.OrderBy(e => e).AssertSequenceEquals(expected);
        }
    }
        private void SetupAndRunGame(Game game, bool initial) {
            // controls added to this collection should be displayed on the canvas until they perish
            var controls = new PerishableCollection<UIElement>();
            controls.CurrentAndFutureItems().Subscribe(
                e => {
                    canvas.Children.Add(e.Value);
                    e.Lifetime.WhenDead(() => canvas.Children.Remove(e.Value));
                },
                game.Life);

            // balls should move and bounce off borders
            game.SetupMoveAndBounceBalls(
                playArea: () => new Rect(0, 0, canvas.ActualWidth, canvas.ActualHeight));

            // connected balls should be be gently tugged towards each other
            game.SetupAttractBalls(
                deadRadius: 50,
                accellerationPerSecondChild: 10,
                accellerationPerSecondParent: 5);

            // balls should periodically spawn dependent children
            game.SetupPeriodicChildSpawning(
                expectedPerBallPerSecond: 0.2, 
                maxChildrenPerBall: 2, 
                maxGeneration: 5);

            // balls should be drawn using ellipse controls and have death animations
            game.SetupDrawBallsAsControls(
                controls, 
                deathFadeOutDuration: 800.Milliseconds(), 
                deathFinalRadiusFactor: 3);

            // ball connectors should be drawn using line controls and have cut and death animations
            game.SetupDrawConnectorsAsControls(
                controls,
                deathFadeDuration: 800.Milliseconds(),
                deathFinalThicknessFactor: 6,
                propagateBangColor: Colors.Green,
                propagateBangDuration: 400.Milliseconds(),
                propagateBangRotationsPerSecond: 3,
                propagateBangMaxRadius: 10,
                cutBangColor: Colors.Red,
                cutBangDuration: 400.Milliseconds(),
                cutBangMaxRadius: 15);

            // connectors that touch the cursor should die
            SetupMouseCutter(game, controls);

            // text displays of game state should track that state
            if (!initial) SetupEnergyAndTime(
                game,
                energyLossForCutting: 2.5,
                energyGainPerConnectorBroken: 1);

            // there should be a few root balls to start with
            foreach (var repeat in 5.Range()) {
                game.SpawnBall(parent: new Ball {
                    Pos = new Point(game.Rng.NextDouble()*canvas.ActualWidth, game.Rng.NextDouble()*canvas.ActualHeight),
                    Radius = 10,
                    Life = new LifetimeSource(),
                    Hue = game.Rng.NextDouble()*3
                });
            }

            // run the game loop until the game is over
            game.Loop().ContinueWith(e => {
                // exceptions?
            });
        }