コード例 #1
0
        ///<summary>Handles cutting connectors with a moving kill point.</summary>
        public static void SetupMouseCutter(this Game game,
                                            PerishableCollection <UIElement> controls,
                                            Func <Point?> liveMousePosition,
                                            double cutTolerance)
        {
            var lastUsedMousePos = liveMousePosition();

            game.LoopActions.Add(
                step => {
                // get a path between last and current mouse positions, if any
                var prev         = lastUsedMousePos;
                var cur          = liveMousePosition();
                lastUsedMousePos = cur;
                if (!prev.HasValue || !cur.HasValue)
                {
                    return;
                }
                var cutPath = new LineSegment(prev.Value, cur.Value);

                // cut any connectors that crossed the cut path
                foreach (var cut in from e in game.Connectors.CurrentItems()
                         let connector = e.Value
                                         let endPath1 = new LineSegment(connector.Parent.LastPos, connector.Parent.Pos)
                                                        let endPath2 = new LineSegment(connector.Child.LastPos, connector.Child.Pos)
                                                                       let pt = cutPath.TryGetCut(endPath1, endPath2)
                                                                                where pt != null
                                                                                select new { pt, connector })
                {
                    cut.connector.CutPoint = cut.pt.Item1;
                    cut.connector.CutDir   = cut.pt.Item2.Normal();
                    cut.connector.Child.Life.EndLifetime();
                }
            },
                game.Life);
        }
コード例 #2
0
 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);
     }
 }
コード例 #3
0
 ///<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;
 }
コード例 #4
0
    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);
        }
    }
コード例 #5
0
    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);
    }
コード例 #6
0
        ///<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);
        }
コード例 #7
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);
 }
コード例 #8
0
    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);
    }
コード例 #9
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);
        }
コード例 #10
0
        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);
        }
コード例 #11
0
    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();
    }
コード例 #12
0
    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);
    }
コード例 #13
0
    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);
    }
コード例 #14
0
    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();
    }
コード例 #15
0
 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();
             }
         });
     }
 }
コード例 #16
0
        ///<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);
        }
コード例 #17
0
    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();
    }
コード例 #18
0
 public void Link(PerishableCollection<UIElement> controls, IObservable<TimeSpan> pulse, Lifetime life)
 {
     _link(controls, pulse, life);
 }
コード例 #19
0
        ///<summary>Handles cutting connectors with a moving kill point.</summary>
        public static void SetupMouseCutter(this Game game,
                                            PerishableCollection<UIElement> controls,
                                            Func<Point?> liveMousePosition,
                                            double cutTolerance) {
            var lastUsedMousePos = liveMousePosition();
            game.LoopActions.Add(
                step => {
                    // get a path between last and current mouse positions, if any
                    var prev = lastUsedMousePos;
                    var cur = liveMousePosition();
                    lastUsedMousePos = cur;
                    if (!prev.HasValue || !cur.HasValue) return;
                    var cutPath = new LineSegment(prev.Value, cur.Value);

                    // cut any connectors that crossed the cut path
                    foreach (var cut in from e in game.Connectors.CurrentItems()
                                        let connector = e.Value
                                        let endPath1 = new LineSegment(connector.Parent.LastPos, connector.Parent.Pos)
                                        let endPath2 = new LineSegment(connector.Child.LastPos, connector.Child.Pos)
                                        let pt = cutPath.TryGetCut(endPath1, endPath2)
                                        where pt != null
                                        select new { pt, connector} ) {
                        cut.connector.CutPoint = cut.pt.Item1;
                        cut.connector.CutDir = cut.pt.Item2.Normal();
                        cut.connector.Child.Life.EndLifetime();
                    }
                },
                game.Life);
        }
コード例 #20
0
        ///<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);
        }
コード例 #21
0
        ///<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);
        }
コード例 #22
0
        ///<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);
        }
コード例 #23
0
        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?
            });
        }
コード例 #24
0
    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();
                });
        }
    }
コード例 #25
0
        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?
            });
        }
コード例 #26
0
 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);
 }