A game framework written with osu! in mind.
at master 691 lines 28 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Linq; 7using NUnit.Framework; 8using osu.Framework.Allocation; 9using osu.Framework.Graphics; 10using osu.Framework.Graphics.Containers; 11using osu.Framework.Graphics.Shapes; 12using osu.Framework.Graphics.Sprites; 13using osu.Framework.Graphics.Transforms; 14using osu.Framework.Graphics.Visualisation; 15using osu.Framework.Utils; 16using osu.Framework.Timing; 17using osuTK; 18using osuTK.Graphics; 19 20namespace osu.Framework.Tests.Visual.Drawables 21{ 22 public class TestSceneTransformRewinding : FrameworkTestScene 23 { 24 private const double interval = 250; 25 private const int interval_count = 4; 26 27 private static double intervalAt(int sequence) => interval * sequence; 28 29 private ManualClock manualClock; 30 private FramedClock manualFramedClock; 31 32 [SetUp] 33 public void SetUp() => Schedule(() => 34 { 35 Clear(); 36 manualClock = new ManualClock(); 37 manualFramedClock = new FramedClock(manualClock); 38 }); 39 40 [Test] 41 public void BasicScale() 42 { 43 boxTest(box => 44 { 45 box.Scale = Vector2.One; 46 box.ScaleTo(0, interval * 4); 47 }); 48 49 checkAtTime(250, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); 50 checkAtTime(500, box => Precision.AlmostEquals(box.Scale.X, 0.5f)); 51 checkAtTime(750, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 52 checkAtTime(1000, box => Precision.AlmostEquals(box.Scale.X, 0f)); 53 54 checkAtTime(500, box => Precision.AlmostEquals(box.Scale.X, 0.5f)); 55 checkAtTime(250, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); 56 57 AddAssert("check transform count", () => box.Transforms.Count() == 1); 58 } 59 60 [Test] 61 public void ScaleSequence() 62 { 63 boxTest(box => 64 { 65 box.Scale = Vector2.One; 66 67 box.ScaleTo(0.75f, interval).Then() 68 .ScaleTo(0.5f, interval).Then() 69 .ScaleTo(0.25f, interval).Then() 70 .ScaleTo(0, interval); 71 }); 72 73 int i = 0; 74 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); 75 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.5f)); 76 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 77 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0f)); 78 79 checkAtTime(interval * (i -= 2), box => Precision.AlmostEquals(box.Scale.X, 0.5f)); 80 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.75f)); 81 82 AddAssert("check transform count", () => box.Transforms.Count() == 4); 83 } 84 85 [Test] 86 public void BasicMovement() 87 { 88 boxTest(box => 89 { 90 box.Scale = new Vector2(0.25f); 91 box.Anchor = Anchor.TopLeft; 92 box.Origin = Anchor.TopLeft; 93 94 box.MoveTo(new Vector2(0.75f, 0), interval).Then() 95 .MoveTo(new Vector2(0.75f, 0.75f), interval).Then() 96 .MoveTo(new Vector2(0, 0.75f), interval).Then() 97 .MoveTo(new Vector2(0), interval); 98 }); 99 100 int i = 0; 101 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.X, 0.75f)); 102 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Y, 0.75f)); 103 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.X, 0f)); 104 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Y, 0f)); 105 106 checkAtTime(interval * (i -= 2), box => Precision.AlmostEquals(box.Y, 0.75f)); 107 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.X, 0.75f)); 108 109 AddAssert("check transform count", () => box.Transforms.Count() == 4); 110 } 111 112 [Test] 113 public void MoveSequence() 114 { 115 boxTest(box => 116 { 117 box.Scale = new Vector2(0.25f); 118 box.Anchor = Anchor.TopLeft; 119 box.Origin = Anchor.TopLeft; 120 121 box.ScaleTo(0.5f, interval).MoveTo(new Vector2(0.5f), interval).Then() 122 .ScaleTo(0.1f, interval).MoveTo(new Vector2(0, 0.75f), interval).Then() 123 .ScaleTo(1f, interval).MoveTo(new Vector2(0, 0), interval).Then() 124 .FadeTo(0, interval); 125 }); 126 127 int i = 0; 128 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.X, 0.5f) && Precision.AlmostEquals(box.Scale.X, 0.5f)); 129 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Y, 0.75f) && Precision.AlmostEquals(box.Scale.X, 0.1f)); 130 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.X, 0f)); 131 checkAtTime(interval * (i += 2), box => Precision.AlmostEquals(box.Alpha, 0f)); 132 133 checkAtTime(interval * (i - 2), box => Precision.AlmostEquals(box.Alpha, 1f)); 134 135 AddAssert("check transform count", () => box.Transforms.Count() == 7); 136 } 137 138 [Test] 139 public void MoveCancelSequence() 140 { 141 boxTest(box => 142 { 143 box.Scale = new Vector2(0.25f); 144 box.Anchor = Anchor.TopLeft; 145 box.Origin = Anchor.TopLeft; 146 147 box.ScaleTo(0.5f, interval).Then().ScaleTo(1, interval); 148 149 Scheduler.AddDelayed(() => { box.ScaleTo(new Vector2(0.1f), interval); }, interval / 2); 150 }); 151 152 int i = 0; 153 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 154 checkAtTime(interval * ++i, box => !Precision.AlmostEquals(box.Scale.X, 0.5f)); 155 156 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.1f)); 157 158 AddAssert("check transform count", () => box.Transforms.Count() == 2); 159 } 160 161 [Test] 162 public void SameTypeInType() 163 { 164 boxTest(box => 165 { 166 box.ScaleTo(0.5f, interval * 4); 167 box.Delay(interval * 2).ScaleTo(1, interval); 168 }); 169 170 int i = 0; 171 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 172 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.3125f)); 173 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.375f)); 174 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 1)); 175 176 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.375f)); 177 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.3125f)); 178 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 179 180 AddAssert("check transform count", () => box.Transforms.Count() == 2); 181 } 182 183 [Test] 184 public void SameTypeInPartialOverlap() 185 { 186 boxTest(box => 187 { 188 box.ScaleTo(0.5f, interval * 2); 189 box.Delay(interval).ScaleTo(1, interval * 2); 190 }); 191 192 int i = 0; 193 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Scale.X, 0.25f)); 194 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.375f)); 195 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 0.6875f)); 196 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 1)); 197 checkAtTime(interval * ++i, box => Precision.AlmostEquals(box.Scale.X, 1)); 198 199 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 1)); 200 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.6875f)); 201 checkAtTime(interval * --i, box => Precision.AlmostEquals(box.Scale.X, 0.375f)); 202 203 AddAssert("check transform count", () => box.Transforms.Count() == 2); 204 } 205 206 [Test] 207 public void StartInMiddleOfSequence() 208 { 209 boxTest(box => 210 { 211 box.Alpha = 0; 212 box.Delay(interval * 2).FadeInFromZero(interval); 213 box.ScaleTo(0.9f, interval * 4); 214 }, 750); 215 216 checkAtTime(interval * 3, box => Precision.AlmostEquals(box.Alpha, 1)); 217 checkAtTime(interval * 4, box => Precision.AlmostEquals(box.Alpha, 1) && Precision.AlmostEquals(box.Scale.X, 0.9f)); 218 checkAtTime(interval * 2, box => Precision.AlmostEquals(box.Alpha, 0) && Precision.AlmostEquals(box.Scale.X, 0.575f)); 219 220 AddAssert("check transform count", () => box.Transforms.Count() == 3); 221 } 222 223 [Test] 224 public void RewindBetweenDisparateValues() 225 { 226 boxTest(box => 227 { 228 box.Alpha = 0; 229 }); 230 231 // move forward to future point in time before adding transforms. 232 checkAtTime(interval * 4, _ => true); 233 234 AddStep("add transforms", () => 235 { 236 using (box.BeginAbsoluteSequence(0)) 237 { 238 box.FadeOutFromOne(interval); 239 box.Delay(interval * 3).FadeOutFromOne(interval); 240 241 // FadeOutFromOne adds extra transforms which disallow testing this scenario, so we remove them. 242 box.RemoveTransform(box.Transforms.ElementAt(2)); 243 box.RemoveTransform(box.Transforms.ElementAt(0)); 244 } 245 }); 246 247 checkAtTime(0, box => Precision.AlmostEquals(box.Alpha, 1)); 248 checkAtTime(interval * 1, box => Precision.AlmostEquals(box.Alpha, 0)); 249 checkAtTime(interval * 2, box => Precision.AlmostEquals(box.Alpha, 0)); 250 checkAtTime(interval * 3, box => Precision.AlmostEquals(box.Alpha, 1)); 251 checkAtTime(interval * 4, box => Precision.AlmostEquals(box.Alpha, 0)); 252 253 // importantly, this should be 0 not 1, reading from the EndValue of the first FadeOutFromOne transform. 254 checkAtTime(interval * 2, box => Precision.AlmostEquals(box.Alpha, 0)); 255 } 256 257 [Test] 258 public void AddPastTransformFromFutureWhenNotInHierarchy() 259 { 260 AddStep("seek clock to 1000", () => manualClock.CurrentTime = interval * 4); 261 262 AddStep("create box", () => 263 { 264 box = createBox(); 265 box.Clock = manualFramedClock; 266 box.RemoveCompletedTransforms = false; 267 268 manualFramedClock.ProcessFrame(); 269 using (box.BeginAbsoluteSequence(0)) 270 box.Delay(interval * 2).FadeOut(interval); 271 }); 272 273 AddStep("seek clock to 0", () => manualClock.CurrentTime = 0); 274 275 AddStep("add box", () => 276 { 277 Add(new AnimationContainer 278 { 279 Child = box, 280 ExaminableDrawable = box, 281 }); 282 }); 283 284 checkAtTime(interval * 2, box => Precision.AlmostEquals(box.Alpha, 1)); 285 checkAtTime(interval * 3, box => Precision.AlmostEquals(box.Alpha, 0)); 286 } 287 288 [Test] 289 public void AddPastTransformFromFuture() 290 { 291 boxTest(box => 292 { 293 box.Alpha = 0; 294 }); 295 296 // move forward to future point in time before adding transforms. 297 checkAtTime(interval * 4, _ => true); 298 299 AddStep("add transforms", () => 300 { 301 using (box.BeginAbsoluteSequence(0)) 302 { 303 box.FadeOutFromOne(interval); 304 box.Delay(interval * 3).FadeInFromZero(interval); 305 306 // FadeOutFromOne adds extra transforms which disallow testing this scenario, so we remove them. 307 box.RemoveTransform(box.Transforms.ElementAt(2)); 308 box.RemoveTransform(box.Transforms.ElementAt(0)); 309 } 310 }); 311 312 AddStep("add one more transform in the middle", () => 313 { 314 using (box.BeginAbsoluteSequence(interval * 2)) 315 box.FadeIn(interval * 0.5); 316 }); 317 318 checkAtTime(interval * 2, box => Precision.AlmostEquals(box.Alpha, 0)); 319 checkAtTime(interval * 2.5, box => Precision.AlmostEquals(box.Alpha, 1)); 320 } 321 322 [Test] 323 public void LoopSequence() 324 { 325 boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }); 326 327 const int count = 4; 328 329 for (int i = 0; i <= count; i++) 330 { 331 if (i > 0) checkAtTime(interval * i - 1, box => Precision.AlmostEquals(box.Rotation, 90f, 1)); 332 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Rotation, 0)); 333 } 334 335 AddAssert("check transform count", () => box.Transforms.Count() == 10); 336 337 for (int i = count; i >= 0; i--) 338 { 339 if (i > 0) checkAtTime(interval * i - 1, box => Precision.AlmostEquals(box.Rotation, 90f, 1)); 340 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Rotation, 0)); 341 } 342 } 343 344 [Test] 345 public void StartInMiddleOfLoopSequence() 346 { 347 boxTest(box => { box.RotateTo(0).RotateTo(90, interval).Loop(); }, 750); 348 349 checkAtTime(750, box => Precision.AlmostEquals(box.Rotation, 0f)); 350 351 AddAssert("check transform count", () => box.Transforms.Count() == 8); 352 353 const int count = 4; 354 355 for (int i = 0; i <= count; i++) 356 { 357 if (i > 0) checkAtTime(interval * i - 1, box => Precision.AlmostEquals(box.Rotation, 90f, 1)); 358 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Rotation, 0)); 359 } 360 361 AddAssert("check transform count", () => box.Transforms.Count() == 10); 362 363 for (int i = count; i >= 0; i--) 364 { 365 if (i > 0) checkAtTime(interval * i - 1, box => Precision.AlmostEquals(box.Rotation, 90f, 1)); 366 checkAtTime(interval * i, box => Precision.AlmostEquals(box.Rotation, 0)); 367 } 368 } 369 370 [Test] 371 public void TestSimultaneousTransformsOutOfOrder() 372 { 373 boxTest(box => 374 { 375 using (box.BeginAbsoluteSequence(0)) 376 { 377 box.MoveToX(0.5f, 4 * interval); 378 box.Delay(interval).MoveToY(0.5f, 2 * interval); 379 } 380 }); 381 382 checkAtTime(0, box => Precision.AlmostEquals(box.Position, new Vector2(0))); 383 checkAtTime(interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.125f, 0))); 384 checkAtTime(2 * interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.25f, 0.25f))); 385 checkAtTime(3 * interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.375f, 0.5f))); 386 checkAtTime(4 * interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.5f))); 387 checkAtTime(3 * interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.375f, 0.5f))); 388 checkAtTime(2 * interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.25f, 0.25f))); 389 checkAtTime(interval, box => Precision.AlmostEquals(box.Position, new Vector2(0.125f, 0))); 390 checkAtTime(0, box => Precision.AlmostEquals(box.Position, new Vector2(0))); 391 } 392 393 [Test] 394 public void TestMultipleTransformTargets() 395 { 396 boxTest(box => 397 { 398 box.Delay(500).MoveTo(new Vector2(0, 0.25f), 500); 399 box.MoveToY(0.5f, 250); 400 }); 401 402 checkAtTime(double.MinValue, box => box.Y == 0); 403 checkAtTime(0, box => box.Y == 0); 404 checkAtTime(250, box => box.Y == 0.5f); 405 checkAtTime(750, box => box.Y == 0.375f); 406 checkAtTime(1000, box => box.Y == 0.25f); 407 checkAtTime(1500, box => box.Y == 0.25f); 408 checkAtTime(1250, box => box.Y == 0.25f); 409 checkAtTime(750, box => box.Y == 0.375f); 410 } 411 412 [Test] 413 public void TestMoveToOffsetRespectsRelevantTransforms() 414 { 415 boxTest(box => 416 { 417 box.MoveToY(0.25f, 250); 418 box.Delay(500).MoveToOffset(new Vector2(0, 0.25f), 250); 419 }); 420 421 checkAtTime(0, box => box.Y == 0); 422 checkAtTime(250, box => box.Y == 0.25f); 423 checkAtTime(500, box => box.Y == 0.25f); 424 checkAtTime(750, box => box.Y == 0.5f); 425 } 426 427 [Test] 428 public void TestMoveToOffsetRespectsTransformsOrder() 429 { 430 boxTest(box => 431 { 432 box.Delay(500).MoveToOffset(new Vector2(0, 0.25f), 250); 433 box.MoveToY(0.25f, 250); 434 }); 435 436 checkAtTime(0, box => box.Y == 0); 437 checkAtTime(250, box => box.Y == 0.25f); 438 checkAtTime(500, box => box.Y == 0.25f); 439 checkAtTime(750, box => box.Y == 0.5f); 440 } 441 442 private Box box; 443 444 private void checkAtTime(double time, Func<Box, bool> assert) 445 { 446 AddAssert($"check at time {time}", () => 447 { 448 manualClock.CurrentTime = time; 449 450 box.Clock = manualFramedClock; 451 box.UpdateSubTree(); 452 453 return assert(box); 454 }); 455 } 456 457 private void boxTest(Action<Box> action, int startTime = 0) 458 { 459 AddStep("add box", () => 460 { 461 Add(new AnimationContainer(startTime) 462 { 463 Child = box = createBox(), 464 ExaminableDrawable = box, 465 }); 466 467 action(box); 468 }); 469 } 470 471 private static Box createBox() 472 { 473 return new Box 474 { 475 Anchor = Anchor.Centre, 476 Origin = Anchor.Centre, 477 RelativeSizeAxes = Axes.Both, 478 RelativePositionAxes = Axes.Both, 479 Scale = new Vector2(0.25f), 480 }; 481 } 482 483 private class AnimationContainer : Container 484 { 485 public override bool RemoveCompletedTransforms => false; 486 protected override Container<Drawable> Content => content; 487 private readonly Container content; 488 private readonly SpriteText minTimeText; 489 private readonly SpriteText currentTimeText; 490 private readonly SpriteText maxTimeText; 491 private readonly Tick seekingTick; 492 private readonly WrappingTimeContainer wrapping; 493 public Box ExaminableDrawable; 494 private readonly FlowContainer<DrawableTransform> transforms; 495 496 public AnimationContainer(int startTime = 0) 497 { 498 Anchor = Anchor.Centre; 499 Origin = Anchor.Centre; 500 RelativeSizeAxes = Axes.Both; 501 InternalChild = wrapping = new WrappingTimeContainer(startTime) 502 { 503 RelativeSizeAxes = Axes.Both, 504 Children = new Drawable[] 505 { 506 new Container 507 { 508 FillMode = FillMode.Fit, 509 RelativeSizeAxes = Axes.Both, 510 Anchor = Anchor.Centre, 511 Origin = Anchor.Centre, 512 Size = new Vector2(0.6f), 513 Children = new Drawable[] 514 { 515 new Box 516 { 517 RelativeSizeAxes = Axes.Both, 518 Colour = Color4.DarkGray, 519 }, 520 content = new Container 521 { 522 RelativeSizeAxes = Axes.Both, 523 Masking = true, 524 }, 525 } 526 }, 527 transforms = new FillFlowContainer<DrawableTransform> 528 { 529 Anchor = Anchor.CentreLeft, 530 Origin = Anchor.CentreLeft, 531 Spacing = Vector2.One, 532 RelativeSizeAxes = Axes.X, 533 AutoSizeAxes = Axes.Y, 534 Width = 0.2f, 535 }, 536 new Container 537 { 538 Anchor = Anchor.TopCentre, 539 Origin = Anchor.TopCentre, 540 RelativeSizeAxes = Axes.Both, 541 Size = new Vector2(0.8f, 0.1f), 542 Children = new Drawable[] 543 { 544 minTimeText = new SpriteText 545 { 546 Anchor = Anchor.BottomLeft, 547 Origin = Anchor.TopLeft, 548 }, 549 currentTimeText = new SpriteText 550 { 551 RelativePositionAxes = Axes.X, 552 Anchor = Anchor.BottomLeft, 553 Origin = Anchor.BottomCentre, 554 Y = -10, 555 }, 556 maxTimeText = new SpriteText 557 { 558 Anchor = Anchor.BottomRight, 559 Origin = Anchor.TopRight, 560 }, 561 seekingTick = new Tick(0, false), 562 new Tick(0), 563 new Tick(1), 564 new Tick(2), 565 new Tick(3), 566 new Tick(4), 567 } 568 } 569 } 570 }; 571 } 572 573 private List<Transform> displayedTransforms; 574 575 protected override void Update() 576 { 577 base.Update(); 578 double time = wrapping.Time.Current; 579 minTimeText.Text = wrapping.MinTime.ToString("n0"); 580 currentTimeText.Text = time.ToString("n0"); 581 seekingTick.X = currentTimeText.X = (float)(time / (wrapping.MaxTime - wrapping.MinTime)); 582 maxTimeText.Text = wrapping.MaxTime.ToString("n0"); 583 maxTimeText.Colour = time > wrapping.MaxTime ? Color4.Gray : wrapping.Time.Elapsed > 0 ? Color4.Blue : Color4.Red; 584 minTimeText.Colour = time < wrapping.MinTime ? Color4.Gray : content.Time.Elapsed > 0 ? Color4.Blue : Color4.Red; 585 586 if (displayedTransforms == null || !ExaminableDrawable.Transforms.SequenceEqual(displayedTransforms)) 587 { 588 transforms.Clear(); 589 foreach (var t in ExaminableDrawable.Transforms) 590 transforms.Add(new DrawableTransform(t, 15)); 591 displayedTransforms = new List<Transform>(ExaminableDrawable.Transforms); 592 } 593 } 594 595 private class Tick : Box 596 { 597 private readonly int tick; 598 private readonly bool colouring; 599 600 public Tick(int tick, bool colouring = true) 601 { 602 this.tick = tick; 603 this.colouring = colouring; 604 Anchor = Anchor.BottomLeft; 605 Origin = Anchor.BottomCentre; 606 Size = new Vector2(1, 10); 607 Colour = Color4.White; 608 RelativePositionAxes = Axes.X; 609 X = (float)tick / interval_count; 610 } 611 612 protected override void Update() 613 { 614 base.Update(); 615 if (colouring) 616 Colour = Time.Current > tick * interval ? Color4.Yellow : Color4.White; 617 } 618 } 619 } 620 621 private class WrappingTimeContainer : Container 622 { 623 // Padding, in milliseconds, at each end of maxima of the clock time 624 private const double time_padding = 50; 625 public double MinTime => clock.MinTime + time_padding; 626 public double MaxTime => clock.MaxTime - time_padding; 627 private readonly ReversibleClock clock; 628 629 public WrappingTimeContainer(double startTime) 630 { 631 clock = new ReversibleClock(startTime); 632 } 633 634 [BackgroundDependencyLoader] 635 private void load() 636 { 637 // Replace the game clock, but keep it as a reference 638 clock.SetSource(Clock); 639 Clock = clock; 640 } 641 642 protected override void LoadComplete() 643 { 644 base.LoadComplete(); 645 clock.MinTime = -time_padding; 646 clock.MaxTime = intervalAt(interval_count) + time_padding; 647 } 648 649 private class ReversibleClock : IFrameBasedClock 650 { 651 private readonly double startTime; 652 public double MinTime; 653 public double MaxTime = 1000; 654 private OffsetClock offsetClock; 655 private IFrameBasedClock trackingClock; 656 private bool reversed; 657 658 public ReversibleClock(double startTime) 659 { 660 this.startTime = startTime; 661 } 662 663 public void SetSource(IFrameBasedClock trackingClock) 664 { 665 this.trackingClock = trackingClock; 666 667 offsetClock = new OffsetClock(trackingClock) { Offset = -trackingClock.CurrentTime + startTime }; 668 } 669 670 public double CurrentTime { get; private set; } 671 public double Rate => offsetClock.Rate; 672 public bool IsRunning => offsetClock.IsRunning; 673 public double ElapsedFrameTime => (reversed ? -1 : 1) * trackingClock.ElapsedFrameTime; 674 public double FramesPerSecond => trackingClock.FramesPerSecond; 675 public FrameTimeInfo TimeInfo => new FrameTimeInfo { Current = CurrentTime, Elapsed = ElapsedFrameTime }; 676 677 public void ProcessFrame() 678 { 679 // There are two iterations, when iteration % 2 == 0 : not reversed 680 int iteration = (int)(offsetClock.CurrentTime / (MaxTime - MinTime)); 681 reversed = iteration % 2 == 1; 682 double iterationTime = offsetClock.CurrentTime % (MaxTime - MinTime); 683 if (reversed) 684 CurrentTime = MaxTime - iterationTime; 685 else 686 CurrentTime = MinTime + iterationTime; 687 } 688 } 689 } 690 } 691}