A game framework written with osu! in mind.
at master 458 lines 13 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 NUnit.Framework; 6using osu.Framework.Threading; 7using osu.Framework.Timing; 8 9namespace osu.Framework.Tests.Threading 10{ 11 [TestFixture] 12 public class SchedulerTest 13 { 14 private Scheduler scheduler; 15 16 private bool fromMainThread; 17 18 [SetUp] 19 public void Setup() 20 { 21 scheduler = new Scheduler(() => fromMainThread, new StopwatchClock(true)); 22 } 23 24 [Test] 25 public void TestScheduleOnce([Values(false, true)] bool fromMainThread, [Values(false, true)] bool forceScheduled) 26 { 27 this.fromMainThread = fromMainThread; 28 29 int invocations = 0; 30 31 var del = scheduler.Add(() => invocations++, forceScheduled); 32 33 if (fromMainThread && !forceScheduled) 34 { 35 Assert.AreEqual(1, invocations); 36 Assert.That(del, Is.Null); 37 } 38 else 39 { 40 Assert.AreEqual(0, invocations); 41 Assert.That(del, Is.Not.Null); 42 } 43 44 scheduler.Update(); 45 Assert.AreEqual(1, invocations); 46 47 // Ensures that the delegate runs only once in the scheduled/not on main thread branch 48 scheduler.Update(); 49 Assert.AreEqual(1, invocations); 50 } 51 52 [Test] 53 public void TestCancelScheduledDelegate() 54 { 55 int invocations = 0; 56 57 ScheduledDelegate del; 58 scheduler.Add(del = new ScheduledDelegate(() => invocations++)); 59 60 del.Cancel(); 61 62 scheduler.Update(); 63 Assert.AreEqual(0, invocations); 64 } 65 66 [Test] 67 public void TestAddCancelledDelegate() 68 { 69 int invocations = 0; 70 71 ScheduledDelegate del = new ScheduledDelegate(() => invocations++); 72 del.Cancel(); 73 74 scheduler.Add(del); 75 76 scheduler.Update(); 77 Assert.AreEqual(0, invocations); 78 } 79 80 [Test] 81 public void TestCustomScheduledDelegateRunsOnce() 82 { 83 int invocations = 0; 84 85 scheduler.Add(new ScheduledDelegate(() => invocations++)); 86 87 scheduler.Update(); 88 Assert.AreEqual(1, invocations); 89 90 scheduler.Update(); 91 Assert.AreEqual(1, invocations); 92 } 93 94 [Test] 95 public void TestDelayedDelegatesAreScheduledWithCorrectTime() 96 { 97 var clock = new StopwatchClock(); 98 scheduler.UpdateClock(clock); 99 100 ScheduledDelegate del = scheduler.AddDelayed(() => { }, 1000); 101 102 Assert.AreEqual(1000, del.ExecutionTime); 103 104 clock.Seek(500); 105 del = scheduler.AddDelayed(() => { }, 1000); 106 107 Assert.AreEqual(1500, del.ExecutionTime); 108 } 109 110 [Test] 111 public void TestDelayedDelegateDoesNotRunUntilExpectedTime() 112 { 113 var clock = new StopwatchClock(); 114 scheduler.UpdateClock(clock); 115 116 int invocations = 0; 117 118 scheduler.AddDelayed(() => invocations++, 1000); 119 120 clock.Seek(1000); 121 scheduler.AddDelayed(() => invocations++, 1000); 122 123 for (double d = 0; d <= 2500; d += 100) 124 { 125 clock.Seek(d); 126 scheduler.Update(); 127 128 int expectedInvocations = 0; 129 130 if (d >= 1000) 131 expectedInvocations++; 132 if (d >= 2000) 133 expectedInvocations++; 134 135 Assert.AreEqual(expectedInvocations, invocations); 136 } 137 } 138 139 [Test] 140 public void TestCancelDelayedDelegate() 141 { 142 var clock = new StopwatchClock(); 143 scheduler.UpdateClock(clock); 144 145 int invocations = 0; 146 147 ScheduledDelegate del; 148 scheduler.Add(del = new ScheduledDelegate(() => invocations++, 1000)); 149 del.Cancel(); 150 151 clock.Seek(1500); 152 scheduler.Update(); 153 Assert.AreEqual(0, invocations); 154 } 155 156 [Test] 157 public void TestCancelDelayedDelegateDuringRun() 158 { 159 var clock = new StopwatchClock(); 160 scheduler.UpdateClock(clock); 161 162 int invocations = 0; 163 164 ScheduledDelegate del = null; 165 166 scheduler.Add(del = new ScheduledDelegate(() => 167 { 168 invocations++; 169 170 // ReSharper disable once AccessToModifiedClosure 171 del?.Cancel(); 172 }, 1000)); 173 174 Assert.AreEqual(ScheduledDelegate.RunState.Waiting, del.State); 175 176 clock.Seek(1500); 177 scheduler.Update(); 178 Assert.AreEqual(1, invocations); 179 180 Assert.AreEqual(ScheduledDelegate.RunState.Cancelled, del.State); 181 182 clock.Seek(2500); 183 scheduler.Update(); 184 Assert.AreEqual(1, invocations); 185 } 186 187 [Test] 188 public void TestRepeatingDelayedDelegate() 189 { 190 var clock = new StopwatchClock(); 191 scheduler.UpdateClock(clock); 192 193 int invocations = 0; 194 195 scheduler.Add(new ScheduledDelegate(() => invocations++, 500, 500)); 196 scheduler.Add(new ScheduledDelegate(() => invocations++, 1000, 500)); 197 198 for (double d = 0; d <= 2500; d += 100) 199 { 200 clock.Seek(d); 201 scheduler.Update(); 202 203 int expectedInvocations = 0; 204 205 if (d >= 500) 206 expectedInvocations += 1 + (int)((d - 500) / 500); 207 if (d >= 1000) 208 expectedInvocations += 1 + (int)((d - 1000) / 500); 209 210 Assert.AreEqual(expectedInvocations, invocations); 211 } 212 } 213 214 [Test] 215 public void TestZeroDelayRepeatingDelegate() 216 { 217 var clock = new StopwatchClock(); 218 scheduler.UpdateClock(clock); 219 220 int invocations = 0; 221 222 Assert.Zero(scheduler.TotalPendingTasks); 223 224 scheduler.Add(new ScheduledDelegate(() => invocations++, 500, 0)); 225 226 Assert.AreEqual(1, scheduler.TotalPendingTasks); 227 228 int expectedInvocations = 0; 229 230 for (double d = 0; d <= 2500; d += 100) 231 { 232 clock.Seek(d); 233 scheduler.Update(); 234 235 if (d >= 500) 236 expectedInvocations++; 237 238 Assert.AreEqual(expectedInvocations, invocations); 239 Assert.AreEqual(1, scheduler.TotalPendingTasks); 240 } 241 } 242 243 [TestCase(false)] 244 [TestCase(true)] 245 public void TestRepeatingDelayedDelegateCatchUp(bool performCatchUp) 246 { 247 var clock = new StopwatchClock(); 248 scheduler.UpdateClock(clock); 249 250 int invocations = 0; 251 252 scheduler.Add(new ScheduledDelegate(() => invocations++, 500, 500) 253 { 254 PerformRepeatCatchUpExecutions = performCatchUp 255 }); 256 257 for (double d = 0; d <= 10000; d += 2000) 258 { 259 clock.Seek(d); 260 261 for (int i = 0; i < 10; i++) 262 // allow catch-up to potentially occur. 263 scheduler.Update(); 264 265 int expectedInovations; 266 267 if (performCatchUp) 268 expectedInovations = (int)(d / 500); 269 else 270 expectedInovations = (int)(d / 2000); 271 272 Assert.AreEqual(expectedInovations, invocations); 273 } 274 } 275 276 [Test] 277 public void TestCancelAfterRepeatReschedule() 278 { 279 var clock = new StopwatchClock(); 280 scheduler.UpdateClock(clock); 281 282 int invocations = 0; 283 284 ScheduledDelegate del; 285 scheduler.Add(del = new ScheduledDelegate(() => invocations++, 500, 500)); 286 287 Assert.AreEqual(ScheduledDelegate.RunState.Waiting, del.State); 288 289 clock.Seek(750); 290 scheduler.Update(); 291 Assert.AreEqual(1, invocations); 292 293 Assert.AreEqual(ScheduledDelegate.RunState.Complete, del.State); 294 295 del.Cancel(); 296 297 Assert.AreEqual(ScheduledDelegate.RunState.Cancelled, del.State); 298 299 clock.Seek(1250); 300 scheduler.Update(); 301 Assert.AreEqual(1, invocations); 302 303 Assert.AreEqual(ScheduledDelegate.RunState.Cancelled, del.State); 304 } 305 306 [Test] 307 public void TestAddOnce() 308 { 309 int invocations = 0; 310 311 void action() => invocations++; 312 313 scheduler.AddOnce(action); 314 scheduler.AddOnce(action); 315 316 scheduler.Update(); 317 Assert.AreEqual(1, invocations); 318 } 319 320 [Test] 321 public void TestPerUpdateTask() 322 { 323 int invocations = 0; 324 325 scheduler.AddDelayed(() => invocations++, 0, true); 326 Assert.AreEqual(0, invocations); 327 328 scheduler.Update(); 329 Assert.AreEqual(1, invocations); 330 331 scheduler.Update(); 332 Assert.AreEqual(2, invocations); 333 } 334 335 [Test] 336 public void TestScheduleFromInsideDelegate([Values(false, true)] bool forceScheduled) 337 { 338 const int max_reschedules = 3; 339 340 fromMainThread = !forceScheduled; 341 342 int reschedules = 0; 343 344 scheduleTask(); 345 346 if (forceScheduled) 347 { 348 for (int i = 0; i <= max_reschedules; i++) 349 { 350 scheduler.Update(); 351 Assert.AreEqual(Math.Min(max_reschedules, i + 1), reschedules); 352 } 353 } 354 else 355 Assert.AreEqual(max_reschedules, reschedules); 356 357 void scheduleTask() => scheduler.Add(() => 358 { 359 if (reschedules == max_reschedules) 360 return; 361 362 reschedules++; 363 scheduleTask(); 364 }, forceScheduled); 365 } 366 367 [Test] 368 public void TestInvokeBeforeSchedulerRun() 369 { 370 int invocations = 0; 371 372 ScheduledDelegate del = new ScheduledDelegate(() => invocations++); 373 374 scheduler.Add(del); 375 Assert.AreEqual(0, invocations); 376 377 del.RunTask(); 378 Assert.AreEqual(1, invocations); 379 380 scheduler.Update(); 381 Assert.AreEqual(1, invocations); 382 } 383 384 [Test] 385 public void TestInvokeAfterSchedulerRun() 386 { 387 int invocations = 0; 388 389 ScheduledDelegate del = new ScheduledDelegate(() => invocations++); 390 391 scheduler.Add(del); 392 Assert.AreEqual(0, invocations); 393 394 scheduler.Update(); 395 Assert.AreEqual(1, invocations); 396 397 Assert.Throws<InvalidOperationException>(del.RunTask); 398 Assert.AreEqual(1, invocations); 399 } 400 401 [Test] 402 public void TestInvokeBeforeScheduleUpdate() 403 { 404 int invocations = 0; 405 ScheduledDelegate del; 406 scheduler.Add(del = new ScheduledDelegate(() => invocations++)); 407 Assert.AreEqual(0, invocations); 408 del.RunTask(); 409 Assert.AreEqual(1, invocations); 410 scheduler.Update(); 411 Assert.AreEqual(1, invocations); 412 } 413 414 [Test] 415 public void TestRepeatAlreadyCompletedSchedule() 416 { 417 int invocations = 0; 418 var del = new ScheduledDelegate(() => invocations++); 419 del.RunTask(); 420 Assert.AreEqual(1, invocations); 421 Assert.Throws<InvalidOperationException>(() => scheduler.Add(del)); 422 scheduler.Update(); 423 Assert.AreEqual(1, invocations); 424 } 425 426 /// <summary> 427 /// Tests that delegates added from inside a scheduled callback don't get executed when the scheduled callback cancels a prior intermediate task. 428 /// 429 /// Delegate 1 - Added at the start. 430 /// Delegate 2 - Added at the start, cancelled by Delegate 1. 431 /// Delegate 3 - Added during Delegate 1 callback, should not get executed. 432 /// </summary> 433 [Test] 434 public void TestDelegateAddedInCallbackNotExecutedAfterIntermediateCancelledDelegate() 435 { 436 int invocations = 0; 437 438 // Delegate 2 439 var cancelled = new ScheduledDelegate(() => { }); 440 441 // Delegate 1 442 scheduler.Add(() => 443 { 444 invocations++; 445 446 cancelled.Cancel(); 447 448 // Delegate 3 449 scheduler.Add(() => invocations++); 450 }); 451 452 scheduler.Add(cancelled); 453 454 scheduler.Update(); 455 Assert.That(invocations, Is.EqualTo(1)); 456 } 457 } 458}