///<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); }
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); } }
///<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 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 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>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>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() { 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); }
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 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 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 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 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 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 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 Link(PerishableCollection<UIElement> controls, IObservable<TimeSpan> pulse, Lifetime life) { _link(controls, pulse, life); }
///<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); }
///<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); }
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 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(); }); } }
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? }); }
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); }