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);
    }
 ///<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);
 }
Exemplo n.º 4
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>Causes a rotating rectangle that grows and fades in then shrinks and fades out to be shown.</summary>
 public static Lifetime AnimateSpinningRectangleExplosion(this Game game,
                                                          PerishableCollection<UIElement> controls,
                                                          Func<double, Point> position,
                                                          Color color,
                                                          TimeSpan duration,
                                                          double rotationsPerSecond,
                                                          double finalRadius) {
     var rect = new Rectangle {
         RenderTransformOrigin = new Point(0.5, 0.5)
     };
     var life = game.AnimateWith(
         duration,
         (step, portion, ellapsed) => {
             var nearHalfPortion = (portion - 0.5).Abs()*2;
             var radius = 0.LerpTo(finalRadius, 1 - nearHalfPortion);
             var pos = position(portion);
             rect.Width = rect.Height = radius*2;
             rect.RenderTransform = new TransformGroup {
                 Children = new TransformCollection {
                     new RotateTransform(rotationsPerSecond*ellapsed.TotalSeconds*360),
                     new TranslateTransform(pos.X - radius, pos.Y - radius)
                 }
             };
             rect.Fill = new SolidColorBrush(color.LerpToTransparent(nearHalfPortion));
         });
     controls.Add(rect, life);
     return life;
 }
        ///<summary>Causes a rotating rectangle that grows and fades in then shrinks and fades out to be shown.</summary>
        public static Lifetime AnimateSpinningRectangleExplosion(this Game game,
                                                                 PerishableCollection <UIElement> controls,
                                                                 Func <double, Point> position,
                                                                 Color color,
                                                                 TimeSpan duration,
                                                                 double rotationsPerSecond,
                                                                 double finalRadius)
        {
            var rect = new Rectangle {
                RenderTransformOrigin = new Point(0.5, 0.5)
            };
            var life = game.AnimateWith(
                duration,
                (step, portion, ellapsed) => {
                var nearHalfPortion  = (portion - 0.5).Abs() * 2;
                var radius           = 0.LerpTo(finalRadius, 1 - nearHalfPortion);
                var pos              = position(portion);
                rect.Width           = rect.Height = radius * 2;
                rect.RenderTransform = new TransformGroup {
                    Children = new TransformCollection {
                        new RotateTransform(rotationsPerSecond * ellapsed.TotalSeconds * 360),
                        new TranslateTransform(pos.X - radius, pos.Y - radius)
                    }
                };
                rect.Fill = new SolidColorBrush(color.LerpToTransparent(nearHalfPortion));
            });

            controls.Add(rect, life);
            return(life);
        }
    public void CurrentItems() {
        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>();
        p.CurrentItems().AssertSequenceEquals();

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

        p.Add(p2.Value, p2.Lifetime);
        p.CurrentItems().AssertSequenceEquals(p1, p2);
        
        source.EndLifetime();
        p.CurrentItems().AssertSequenceEquals(p2);
    }
    public void IsResultThreadSafe()
    {
        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();
            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));
            p.CurrentItems().OrderBy(e => e.Value).Select(e => e.Value).AssertSequenceEquals(expected);
        }
    }
    public void CurrentItems()
    {
        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>();

        p.CurrentItems().AssertSequenceEquals();

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

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

        source.EndLifetime();
        p.CurrentItems().AssertSequenceEquals(p2);
    }
        private void SetupMouseCutter(Game game, PerishableCollection <UIElement> controls)
        {
            // create rectangle to center under mouse
            var rotater     = new RotateTransform();
            var translater  = new TranslateTransform();
            var mouseTarget = new Rectangle {
                Width            = 10,
                Height           = 10,
                Fill             = new SolidColorBrush(Colors.Black),
                IsHitTestVisible = false,
                RenderTransform  = new TransformGroup {
                    Children = new TransformCollection {
                        rotater,
                        translater
                    }
                },
                Visibility            = Visibility.Collapsed,
                RenderTransformOrigin = new Point(0.5, 0.5)
            };

            controls.Add(mouseTarget, game.Life);

            // make the rectangle rotate
            rotater.BeginAnimation(
                RotateTransform.AngleProperty,
                new DoubleAnimation(0, 360, 1.Seconds())
            {
                RepeatBehavior = RepeatBehavior.Forever
            });

            // watch mouse position over canvas, keeping the rotating rectangle centered on it
            var liveMousePos    = default(Point?);
            MouseEventHandler h = (sender, arg) => {
                mouseTarget.Visibility = Visibility.Visible;
                liveMousePos           = arg.GetPosition(canvas);
                translater.X           = liveMousePos.Value.X - mouseTarget.Width / 2;
                translater.Y           = liveMousePos.Value.Y - mouseTarget.Height / 2;
            };
            MouseEventHandler h2 = (sender, arg) => {
                mouseTarget.Visibility = Visibility.Collapsed;
                liveMousePos           = null;
            };

            canvas.MouseMove  += h;
            canvas.MouseLeave += h2;
            game.Life.WhenDead(() => canvas.MouseMove  -= h);
            game.Life.WhenDead(() => canvas.MouseLeave -= h2);

            // pass along mouse positions to the game
            game.SetupMouseCutter(
                controls,
                () => liveMousePos,
                cutTolerance: 5);
        }
    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 ToPerishableCollectionWithLifetime()
    {
        var source           = new LifetimeSource();
        var collectionSource = 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(collectionSource.Lifetime);

        q.CurrentItems().AssertSequenceEquals();

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

        collectionSource.EndLifetime();

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

        source.EndLifetime();

        source.EndLifetime();
        q.CurrentItems().AssertSequenceEquals();
    }
    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();
    }
        ///<summary>Handles making ellipse controls for each ball in the game, as while as showing their death animations.</summary>
        public static void SetupDrawBallsAsControls(this Game game, PerishableCollection <UIElement> controls, TimeSpan deathFadeOutDuration, double deathFinalRadiusFactor)
        {
            game.Balls.CurrentAndFutureItems().Subscribe(
                e => {
                // create an ellipse control to visually represent the ball
                var ball           = e.Value;
                var color          = ball.Hue.HueToApproximateColor(period: 3);
                var ellipseControl = new Ellipse {
                    Width               = ball.Radius * 2,
                    Height              = ball.Radius * 2,
                    VerticalAlignment   = VerticalAlignment.Top,
                    HorizontalAlignment = HorizontalAlignment.Left,
                    Fill = new SolidColorBrush(color)
                };

                // 'root' balls have a black border
                if (ball.Generation == 1)
                {
                    ellipseControl.StrokeThickness = 3;
                    ellipseControl.Stroke          = new SolidColorBrush(Colors.Black);
                }

                // reposition ellipse control during each game loop, until ball is dead
                game.LoopActions.Add(
                    step => ellipseControl.Reposition(ball.Pos, ball.Radius),
                    e.Lifetime);

                // once ball is dead, expand and fade out the ellipse
                var controlLifetime = e.Lifetime.ThenResurrect(() =>
                                                               game.AnimateWith(
                                                                   deathFadeOutDuration,
                                                                   (step, portion, dt) => {
                    // fade out
                    ellipseControl.Fill = new SolidColorBrush(color.LerpToTransparent(portion));
                    // expand
                    var radius = ball.Radius * 1.LerpTo(deathFinalRadiusFactor, portion);
                    ellipseControl.Reposition(ball.Pos, radius);
                }));

                controls.Add(ellipseControl, controlLifetime);
            },
                game.Life);
        }
 public void IsEnumeratingWhileMutatingThreadSafe()
 {
     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();
         TestUtil.ConcurrencyTest(
             threadCount: nt,
             callbackCount: n,
             repeatedWork: (t, i) => {
             if (t % 2 == 0)
             {
                 if (i % 500 != 0)
                 {
                     return;
                 }
                 var r = p.CurrentItems().OrderBy(e => e.Value).ToList();
                 (r.Count(e => e.Value % 2 == 0 && !e.Lifetime.IsDead) < nt / 2 * 20).AssertIsTrue();
             }
             else
             {
                 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();
             }
         });
     }
 }
        ///<summary>Handles making line controls for each connector in the game, as while as showing their death animations.</summary>
        public static void SetupDrawConnectorsAsControls(this Game game,
                                                         PerishableCollection <UIElement> controls,
                                                         TimeSpan deathFadeDuration,
                                                         double deathFinalThicknessFactor,
                                                         Color cutBangColor,
                                                         TimeSpan cutBangDuration,
                                                         double cutBangMaxRadius,
                                                         Color propagateBangColor,
                                                         TimeSpan propagateBangDuration,
                                                         double propagateBangRotationsPerSecond,
                                                         double propagateBangMaxRadius)
        {
            game.Connectors.CurrentAndFutureItems().Subscribe(
                e => {
                // create a line control to visually represent the line
                var con         = e.Value;
                var thickness   = con.Child.Radius * 0.1;
                var lineControl = new Line {
                    Stroke          = new SolidColorBrush(Colors.Black),
                    StrokeThickness = thickness,
                };

                // reposition line control during each game loop, until the connector is dead
                game.LoopActions.Add(
                    step => lineControl.Reposition(con.Line),
                    e.Lifetime);

                // show a bang if the connector is cut
                e.Lifetime.WhenDead(() => {
                    if (con.CutPoint == null || con.CutDir == null)
                    {
                        return;
                    }
                    var cutLineControl = new Line {
                        Stroke          = new SolidColorBrush(cutBangColor),
                        StrokeThickness = thickness,
                    };
                    var p    = con.CutPoint.Value;
                    var d    = con.CutDir.Value * cutBangMaxRadius;
                    var life = game.AnimateWith(
                        cutBangDuration,
                        (step, prop, elapsed) => {
                        var c = 1 - (prop - 0.5).Abs() * 2;
                        cutLineControl.Reposition((p - d * c).To(p + d * c));
                        cutLineControl.StrokeThickness = prop * cutBangMaxRadius / 2;
                        cutLineControl.Stroke          = new SolidColorBrush(cutBangColor.LerpToTransparent(1 - c));
                    });
                    controls.Add(cutLineControl, life);
                });

                // show a bang travelling along the connector when it dies
                e.Lifetime.WhenDead(() =>
                                    game.AnimateSpinningRectangleExplosion(
                                        controls,
                                        p => (con.CutPoint == null ? con.Line.Start : con.CutPoint.Value.ClosestPointOn(con.Line)).To(e.Value.Line.End).LerpAcross(p),
                                        propagateBangColor,
                                        propagateBangDuration,
                                        propagateBangRotationsPerSecond,
                                        propagateBangMaxRadius));

                // expand and fade out the line control after the connector dies
                var controlLife = e.Lifetime.ThenResurrect(() => game.AnimateWith(
                                                               deathFadeDuration,
                                                               (step, portion, dt) => {
                    lineControl.StrokeThickness = thickness * 1.LerpTo(deathFinalThicknessFactor, portion);
                    lineControl.Stroke          = new SolidColorBrush(Colors.Black.LerpToTransparent(portion));
                    //lineControl.Reposition(e.Value.Line);
                }));

                controls.Add(lineControl, controlLife);
            },
                game.Life);
        }
        ///<summary>Handles making line controls for each connector in the game, as while as showing their death animations.</summary>
        public static void SetupDrawConnectorsAsControls(this Game game,
                                                         PerishableCollection<UIElement> controls,
                                                         TimeSpan deathFadeDuration,
                                                         double deathFinalThicknessFactor,
                                                         Color cutBangColor,
                                                         TimeSpan cutBangDuration,
                                                         double cutBangMaxRadius,
                                                         Color propagateBangColor,
                                                         TimeSpan propagateBangDuration,
                                                         double propagateBangRotationsPerSecond,
                                                         double propagateBangMaxRadius) {
            game.Connectors.CurrentAndFutureItems().Subscribe(
                e => {
                    // create a line control to visually represent the line
                    var con = e.Value;
                    var thickness = con.Child.Radius * 0.1;
                    var lineControl = new Line {
                        Stroke = new SolidColorBrush(Colors.Black),
                        StrokeThickness = thickness,
                    };

                    // reposition line control during each game loop, until the connector is dead
                    game.LoopActions.Add(
                        step => lineControl.Reposition(con.Line),
                        e.Lifetime);

                    // show a bang if the connector is cut
                    e.Lifetime.WhenDead(() => {
                        if (con.CutPoint == null || con.CutDir == null) return;
                        var cutLineControl = new Line {
                            Stroke = new SolidColorBrush(cutBangColor),
                            StrokeThickness = thickness,
                        };
                        var p = con.CutPoint.Value;
                        var d = con.CutDir.Value*cutBangMaxRadius;
                        var life = game.AnimateWith(
                            cutBangDuration,
                            (step, prop, elapsed) => {
                                var c = 1 - (prop - 0.5).Abs()*2;
                                cutLineControl.Reposition((p - d*c).To(p + d*c));
                                cutLineControl.StrokeThickness = prop*cutBangMaxRadius/2;
                                cutLineControl.Stroke = new SolidColorBrush(cutBangColor.LerpToTransparent(1 - c));
                            });
                        controls.Add(cutLineControl, life);
                    });

                    // show a bang travelling along the connector when it dies
                    e.Lifetime.WhenDead(() =>
                        game.AnimateSpinningRectangleExplosion(
                            controls,
                            p => (con.CutPoint == null ? con.Line.Start : con.CutPoint.Value.ClosestPointOn(con.Line)).To(e.Value.Line.End).LerpAcross(p),
                            propagateBangColor,
                            propagateBangDuration,
                            propagateBangRotationsPerSecond,
                            propagateBangMaxRadius));

                    // expand and fade out the line control after the connector dies
                    var controlLife = e.Lifetime.ThenResurrect(() => game.AnimateWith(
                        deathFadeDuration,
                        (step, portion, dt) => {
                            lineControl.StrokeThickness = thickness * 1.LerpTo(deathFinalThicknessFactor, portion);
                            lineControl.Stroke = new SolidColorBrush(Colors.Black.LerpToTransparent(portion));
                            //lineControl.Reposition(e.Value.Line);
                        }));

                    controls.Add(lineControl, controlLife);
                },
                game.Life);
        }
        ///<summary>Handles making ellipse controls for each ball in the game, as while as showing their death animations.</summary>
        public static void SetupDrawBallsAsControls(this Game game, PerishableCollection<UIElement> controls, TimeSpan deathFadeOutDuration, double deathFinalRadiusFactor) {
            game.Balls.CurrentAndFutureItems().Subscribe(
                e => {
                    // create an ellipse control to visually represent the ball
                    var ball = e.Value;
                    var color = ball.Hue.HueToApproximateColor(period: 3);
                    var ellipseControl = new Ellipse {
                        Width = ball.Radius * 2,
                        Height = ball.Radius * 2,
                        VerticalAlignment = VerticalAlignment.Top,
                        HorizontalAlignment = HorizontalAlignment.Left,
                        Fill = new SolidColorBrush(color)
                    };

                    // 'root' balls have a black border
                    if (ball.Generation == 1) {
                        ellipseControl.StrokeThickness = 3;
                        ellipseControl.Stroke = new SolidColorBrush(Colors.Black);
                    }

                    // reposition ellipse control during each game loop, until ball is dead
                    game.LoopActions.Add(
                        step => ellipseControl.Reposition(ball.Pos, ball.Radius),
                        e.Lifetime);

                    // once ball is dead, expand and fade out the ellipse
                    var controlLifetime = e.Lifetime.ThenResurrect(() =>
                        game.AnimateWith(
                            deathFadeOutDuration,
                            (step, portion, dt) => {
                                // fade out
                                ellipseControl.Fill = new SolidColorBrush(color.LerpToTransparent(portion));
                                // expand
                                var radius = ball.Radius * 1.LerpTo(deathFinalRadiusFactor, portion);
                                ellipseControl.Reposition(ball.Pos, radius);
                            }));

                    controls.Add(ellipseControl, controlLifetime);
                },
                game.Life);
        }
    public void IsEnumeratingWhileMutatingThreadSafe() {
        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();
            TestUtil.ConcurrencyTest(
                threadCount: nt,
                callbackCount: n,
                repeatedWork: (t, i) => {
                    if (t%2 == 0) {
                        if (i%500 != 0) return;
                        var r = p.CurrentItems().OrderBy(e => e.Value).ToList();
                        (r.Count(e => e.Value%2 == 0 && !e.Lifetime.IsDead) < nt/2 * 20).AssertIsTrue();

                    } else {
                        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();
                });
        }
    }
 public void IsResultThreadSafe() {
     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();
         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));
         p.CurrentItems().OrderBy(e => e.Value).Select(e => e.Value).AssertSequenceEquals(expected);
     }
 }
 private void SetupMouseCutter(Game game, PerishableCollection<UIElement> controls) {
     // create rectangle to center under mouse
     var rotater = new RotateTransform();
     var translater = new TranslateTransform();
     var mouseTarget = new Rectangle {
         Width = 10,
         Height = 10,
         Fill = new SolidColorBrush(Colors.Black),
         IsHitTestVisible = false,
         RenderTransform = new TransformGroup {
             Children = new TransformCollection {
                 rotater,
                 translater
             }
         },
         Visibility = Visibility.Collapsed,
         RenderTransformOrigin = new Point(0.5, 0.5)
     };
     controls.Add(mouseTarget, game.Life);
     
     // make the rectangle rotate
     rotater.BeginAnimation(
         RotateTransform.AngleProperty, 
         new DoubleAnimation(0, 360, 1.Seconds()) { RepeatBehavior = RepeatBehavior.Forever });
     
     // watch mouse position over canvas, keeping the rotating rectangle centered on it
     var liveMousePos = default(Point?);
     MouseEventHandler h = (sender, arg) => {
         mouseTarget.Visibility = Visibility.Visible;
         liveMousePos = arg.GetPosition(canvas);
         translater.X = liveMousePos.Value.X - mouseTarget.Width / 2;
         translater.Y = liveMousePos.Value.Y - mouseTarget.Height / 2;
     };
     MouseEventHandler h2 = (sender, arg) => {
         mouseTarget.Visibility = Visibility.Collapsed;
         liveMousePos = null;
     };
     canvas.MouseMove += h;
     canvas.MouseLeave += h2;
     game.Life.WhenDead(() => canvas.MouseMove -= h);
     game.Life.WhenDead(() => canvas.MouseLeave -= h2);
     
     // pass along mouse positions to the game
     game.SetupMouseCutter(
         controls,
         () => liveMousePos,
         cutTolerance: 5);
 }