Unverified Commit dc374a75 authored by Igor Demin's avatar Igor Demin Committed by GitHub

Merge pull request #58 from JetBrains/frame-dispatcher-test

FrameDispatcherTest
parents 4db4fbb9 da710bd1
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.3.72" kotlin("jvm") version "1.3.72"
application application
...@@ -36,6 +38,7 @@ dependencies { ...@@ -36,6 +38,7 @@ dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom")) implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1")
implementation("org.jetbrains.skiko:skiko-jvm-runtime-$target:$version") implementation("org.jetbrains.skiko:skiko-jvm-runtime-$target:$version")
testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
...@@ -72,4 +75,8 @@ tasks.withType<Test> { ...@@ -72,4 +75,8 @@ tasks.withType<Test> {
systemProperty("sun.java2d.dpiaware", "false") systemProperty("sun.java2d.dpiaware", "false")
systemProperty("sun.java2d.uiScale", "1") systemProperty("sun.java2d.uiScale", "1")
} }
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
} }
\ No newline at end of file
package org.jetbrains.skiko
import kotlinx.coroutines.*
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Assert.assertEquals
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class FrameDispatcherTest {
private var frameCount = 0
@Test
fun `shouldn't call onFrame after the creating`() = test {
FrameDispatcher(scope = this) {
frameCount++
}
advanceUntilIdle()
assertEquals(0, frameCount)
}
@Test
fun `scheduleFrame after creating`() = test {
val frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
}
frameDispatcher.scheduleFrame()
advanceUntilIdle()
assertEquals(1, frameCount)
}
@Test
fun `scheduleFrame multiple times after creating`() = test {
val frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
}
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
advanceUntilIdle()
assertEquals(1, frameCount)
}
@Test
fun `scheduleFrame second time after first onFrame`() = test {
val frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
}
frameDispatcher.scheduleFrame()
advanceUntilIdle()
frameDispatcher.scheduleFrame()
advanceUntilIdle()
assertEquals(2, frameCount)
}
@Test
fun `scheduleFrame second time twice after first onFrame`() = test {
val frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
}
frameDispatcher.scheduleFrame()
advanceUntilIdle()
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
advanceUntilIdle()
assertEquals(2, frameCount)
}
@Suppress("JoinDeclarationAndAssignment")
@Test
fun `scheduleFrame during onFrame`() = test {
lateinit var frameDispatcher: FrameDispatcher
frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
frameDispatcher.scheduleFrame()
}
frameDispatcher.scheduleFrame()
yield()
assertEquals(1, frameCount)
yield()
assertEquals(2, frameCount)
yield()
assertEquals(3, frameCount)
yield()
assertEquals(4, frameCount)
}
@Suppress("JoinDeclarationAndAssignment")
@Test
fun `scheduleFrame multiple times during onFrame`() = test {
lateinit var frameDispatcher: FrameDispatcher
frameDispatcher = FrameDispatcher(scope = this) {
frameCount++
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
frameDispatcher.scheduleFrame()
}
frameDispatcher.scheduleFrame()
yield()
assertEquals(1, frameCount)
yield()
assertEquals(2, frameCount)
yield()
assertEquals(3, frameCount)
yield()
assertEquals(4, frameCount)
}
@Suppress("JoinDeclarationAndAssignment")
@Test
fun `cancel coroutine scope`() = test {
val scope = CoroutineScope(coroutineContext)
lateinit var frameDispatcher: FrameDispatcher
frameDispatcher = FrameDispatcher(scope) {
frameCount++
frameDispatcher.scheduleFrame()
}
frameDispatcher.scheduleFrame()
yield()
assertEquals(1, frameCount)
yield()
assertEquals(2, frameCount)
yield()
assertEquals(3, frameCount)
scope.cancel()
yield()
assertEquals(3, frameCount)
advanceUntilIdle()
assertEquals(3, frameCount)
}
private fun test(
block: suspend TestCoroutineScope.() -> Unit
) = runBlockingTest {
pauseDispatcher()
val job = Job()
TestCoroutineScope(coroutineContext + job).block()
job.cancel()
}
}
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
* Dispatch frame after call of [scheduleFrame] * Dispatch frame after call of [scheduleFrame].
*
* After the creating there should be no scheduled frame in the frame loop.
*/ */
class FrameDispatcher( class FrameDispatcher(
context: CoroutineContext, scope: CoroutineScope,
private val onFrame: suspend () -> Unit private val onFrame: suspend () -> Unit
) { ) {
constructor(
context: CoroutineContext,
onFrame: suspend () -> Unit
) : this(
CoroutineScope(context),
onFrame
)
private var needFrame = CompletableDeferred<Unit>() private var needFrame = CompletableDeferred<Unit>()
private val job = GlobalScope.launch(context) { private val job = scope.launch {
while (true) { while (true) {
needFrame.await() needFrame.await()
needFrame = CompletableDeferred() needFrame = CompletableDeferred()
...@@ -28,6 +38,14 @@ class FrameDispatcher( ...@@ -28,6 +38,14 @@ class FrameDispatcher(
job.cancel() job.cancel()
} }
/**
* Schedule next frame to render in the frame loop.
*
* Multiple calls of scheduleFrame before beginning of the frame will cause only one onFrame.
*
* Multiple calls of scheduleFrame after beginning of the frame but before its ending
* will schedule next single onFrame after the current one.
*/
fun scheduleFrame() { fun scheduleFrame() {
needFrame.complete(Unit) needFrame.complete(Unit)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment