|
12 | 12 | import java.util.Objects; |
13 | 13 | import java.util.Set; |
14 | 14 | import java.util.concurrent.CountDownLatch; |
| 15 | +import java.util.concurrent.CopyOnWriteArrayList; |
15 | 16 | import java.util.concurrent.TimeUnit; |
16 | 17 | import java.util.function.Consumer; |
17 | 18 |
|
@@ -49,6 +50,7 @@ public static void main(String[] args) throws Exception { |
49 | 50 | testAuthoringEdgeCasesAndPlayerGuardRails(); |
50 | 51 | testCompiledJsonGuardsAndPlaybackLifecycle(); |
51 | 52 | testPlaybackNavigationAndTimer(); |
| 53 | + testConcurrentControlCommands(); |
52 | 54 | testLargeGeneratedScript(); |
53 | 55 | System.out.println("ManagedCode.Tps Java tests passed."); |
54 | 56 | } |
@@ -258,6 +260,59 @@ private static void testPlaybackNavigationAndTimer() throws InterruptedException |
258 | 260 | } |
259 | 261 | } |
260 | 262 |
|
| 263 | + private static void testConcurrentControlCommands() throws InterruptedException { |
| 264 | + ManagedCodeTps.TpsCompilationResult compilation = ManagedCodeTps.TpsRuntime.compileTps(""" |
| 265 | + ## [Intro] |
| 266 | + ### [Lead] |
| 267 | + Ready now please stay focused for this longer playback sample. |
| 268 | + ### [Close] |
| 269 | + Done soon after another phrase lands safely. |
| 270 | + """); |
| 271 | + ManagedCodeTps.TpsPlaybackSession session = new ManagedCodeTps.TpsPlaybackSession(compilation.script(), new ManagedCodeTps.TpsPlaybackSessionOptions(1_000, null, null, null, false)); |
| 272 | + try { |
| 273 | + session.play(); |
| 274 | + |
| 275 | + CountDownLatch start = new CountDownLatch(1); |
| 276 | + CountDownLatch completed = new CountDownLatch(4); |
| 277 | + List<Throwable> failures = new CopyOnWriteArrayList<>(); |
| 278 | + |
| 279 | + for (int lane = 0; lane < 4; lane += 1) { |
| 280 | + final int laneId = lane; |
| 281 | + Thread thread = new Thread(() -> { |
| 282 | + try { |
| 283 | + start.await(3, TimeUnit.SECONDS); |
| 284 | + for (int iteration = 0; iteration < 40; iteration += 1) { |
| 285 | + switch ((laneId + iteration) % 6) { |
| 286 | + case 0 -> session.seek((iteration * 37) % Math.max(1, compilation.script().totalDurationMs())); |
| 287 | + case 1 -> session.nextWord(); |
| 288 | + case 2 -> session.previousWord(); |
| 289 | + case 3 -> session.nextBlock(); |
| 290 | + case 4 -> session.previousBlock(); |
| 291 | + default -> session.setSpeedOffsetWpm(((laneId * 5) + iteration) % 41 - 20); |
| 292 | + } |
| 293 | + } |
| 294 | + } catch (Throwable exception) { |
| 295 | + failures.add(exception); |
| 296 | + } finally { |
| 297 | + completed.countDown(); |
| 298 | + } |
| 299 | + }); |
| 300 | + thread.start(); |
| 301 | + } |
| 302 | + |
| 303 | + start.countDown(); |
| 304 | + assertTrue(completed.await(3, TimeUnit.SECONDS), "concurrent control commands should complete"); |
| 305 | + assertTrue(failures.isEmpty(), "concurrent control commands should not fail: " + failures); |
| 306 | + |
| 307 | + ManagedCodeTps.TpsPlaybackSnapshot snapshot = session.snapshot(); |
| 308 | + assertTrue(snapshot.state().elapsedMs() >= 0 && snapshot.state().elapsedMs() <= compilation.script().totalDurationMs(), "elapsedMs should stay in bounds"); |
| 309 | + assertTrue(snapshot.state().currentWordIndex() >= -1 && snapshot.state().currentWordIndex() < compilation.script().words().size(), "currentWordIndex should stay in bounds"); |
| 310 | + assertTrue(snapshot.tempo().effectiveBaseWpm() >= ManagedCodeTps.TpsSpec.MINIMUM_WPM && snapshot.tempo().effectiveBaseWpm() <= ManagedCodeTps.TpsSpec.MAXIMUM_WPM, "effectiveBaseWpm should stay in bounds"); |
| 311 | + } finally { |
| 312 | + session.dispose(); |
| 313 | + } |
| 314 | + } |
| 315 | + |
261 | 316 | private static void testLargeGeneratedScript() { |
262 | 317 | StringBuilder builder = new StringBuilder(); |
263 | 318 | builder.append("---\nbase_wpm: 140\n---\n\n"); |
|
0 commit comments