-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add DisplayLinkClock for iOS, tvOS, and macOS (#170)
* Add DisplayLinkClock for iOS, tvOS, and macOS This commit adds two imlementations of a `MonotonicFrameClock` backed by `CADisplayLink` on iOS and tvOS and `CVDisplayLink` on macOS. watchOS does not have an equivalent that I'm aware of and therefore has been omitted. * Simplify build configuration --------- Co-authored-by: Jake Wharton <jw@squareup.com>
- Loading branch information
1 parent
4bc4044
commit 7b6bb17
Showing
5 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
molecule-runtime/src/darwinMain/kotlin/app/cash/molecule/DisplayLinkClock.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* Copyright (C) 2023 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package app.cash.molecule | ||
|
||
import androidx.compose.runtime.MonotonicFrameClock | ||
|
||
public expect object DisplayLinkClock : MonotonicFrameClock |
28 changes: 28 additions & 0 deletions
28
molecule-runtime/src/darwinTest/kotlin/app/cash/molecule/DisplayLinkClockTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright (C) 2023 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package app.cash.molecule | ||
|
||
import kotlin.test.Test | ||
import kotlinx.coroutines.test.runTest | ||
|
||
class DisplayLinkClockTest { | ||
|
||
@Test fun `DisplayLinkClock delivers a single frame`() = runTest { | ||
DisplayLinkClock.withFrameNanos { | ||
// If this function does not time out the test passes. | ||
} | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
molecule-runtime/src/macosMain/kotlin/app/cash/molecule/DisplayLinkClock.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* Copyright (C) 2023 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package app.cash.molecule | ||
|
||
import androidx.compose.runtime.BroadcastFrameClock | ||
import androidx.compose.runtime.MonotonicFrameClock | ||
import kotlinx.cinterop.alloc | ||
import kotlinx.cinterop.nativeHeap | ||
import kotlinx.cinterop.ptr | ||
import kotlinx.cinterop.staticCFunction | ||
import kotlinx.cinterop.value | ||
import platform.CoreVideo.CVDisplayLinkCreateWithActiveCGDisplays | ||
import platform.CoreVideo.CVDisplayLinkRefVar | ||
import platform.CoreVideo.CVDisplayLinkSetOutputCallback | ||
import platform.CoreVideo.CVDisplayLinkStart | ||
import platform.CoreVideo.CVDisplayLinkStop | ||
import platform.CoreVideo.kCVReturnSuccess | ||
|
||
public actual object DisplayLinkClock : MonotonicFrameClock { | ||
|
||
private val clock = BroadcastFrameClock { | ||
// One or more awaiters have appeared. Start the DisplayLink clock callback so that awaiters | ||
// get dispatched on the next available frame. | ||
checkDisplayLink(CVDisplayLinkStart(displayLink.value)) | ||
} | ||
|
||
// We alloc directly to nativeHeap because this singleton object lives for the duration of the | ||
// process. We don't care about cleanup and therefore never free this. | ||
private val displayLink = nativeHeap.alloc<CVDisplayLinkRefVar>() | ||
|
||
init { | ||
checkDisplayLink(CVDisplayLinkCreateWithActiveCGDisplays(displayLink.ptr)) | ||
checkDisplayLink( | ||
CVDisplayLinkSetOutputCallback( | ||
displayLink.value, | ||
staticCFunction { _, _, _, _, _, _ -> | ||
clock.sendFrame(0L) | ||
|
||
// A frame was delivered. Stop the DisplayLink callback. It will get started again | ||
// when new frame awaiters appear. | ||
CVDisplayLinkStop(displayLink.value) | ||
}, | ||
null, | ||
), | ||
) | ||
} | ||
|
||
override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { | ||
return clock.withFrameNanos(onFrame) | ||
} | ||
} | ||
|
||
private fun checkDisplayLink(code: Int) { | ||
check(code == kCVReturnSuccess) { "Could not initialize CVDisplayLink. Error code $code." } | ||
} |
51 changes: 51 additions & 0 deletions
51
molecule-runtime/src/quartzCoreMain/kotlin/app/cash/molecule/DisplayLinkClock.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright (C) 2023 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package app.cash.molecule | ||
|
||
import androidx.compose.runtime.BroadcastFrameClock | ||
import androidx.compose.runtime.MonotonicFrameClock | ||
import kotlinx.cinterop.ObjCAction | ||
import platform.Foundation.NSRunLoop | ||
import platform.Foundation.NSSelectorFromString | ||
import platform.QuartzCore.CADisplayLink | ||
|
||
public actual object DisplayLinkClock : MonotonicFrameClock { | ||
|
||
@Suppress("unused") // This registers a DisplayLink listener. | ||
private val displayLink: CADisplayLink = CADisplayLink.displayLinkWithTarget( | ||
target = this, | ||
selector = NSSelectorFromString(this::tickClock.name), | ||
) | ||
|
||
private val clock = BroadcastFrameClock { | ||
// We only want to listen to the DisplayLink run loop if we have frame awaiters. | ||
displayLink.addToRunLoop(NSRunLoop.currentRunLoop, NSRunLoop.currentRunLoop.currentMode) | ||
} | ||
|
||
override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { | ||
return clock.withFrameNanos(onFrame) | ||
} | ||
|
||
// The following function must remain public to be a valid candidate for the call to | ||
// NSSelectorString above. | ||
@ObjCAction public fun tickClock() { | ||
clock.sendFrame(0L) | ||
|
||
// Remove the DisplayLink from the run loop. It will get added again if new frame awaiters | ||
// appear. | ||
displayLink.removeFromRunLoop(NSRunLoop.currentRunLoop, NSRunLoop.currentRunLoop.currentMode) | ||
} | ||
} |