mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-11-02 21:40:06 +08:00
Get Compose building and running on the JVM
This commit is contained in:
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,3 +1,7 @@
|
||||
[submodule "yoga/upstream"]
|
||||
path = yoga/upstream
|
||||
url = https://github.com/facebook/yoga
|
||||
[submodule "compose/upstream"]
|
||||
path = compose/upstream
|
||||
url = https://android.googlesource.com/platform/frameworks/support
|
||||
branch = androidx-master-dev
|
||||
|
||||
16
build.gradle
16
build.gradle
@ -1,8 +1,10 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0'
|
||||
classpath "dev.nokee.jni-library:dev.nokee.jni-library.gradle.plugin:0.4.0"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
@ -10,5 +12,19 @@ buildscript {
|
||||
subprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
url 'https://kotlin.bintray.com/kotlinx'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinCompile).configureEach { task ->
|
||||
task.kotlinOptions {
|
||||
freeCompilerArgs += [
|
||||
'-progressive',
|
||||
'-Xopt-in=kotlin.RequiresOptIn',
|
||||
'-Xopt-in=androidx.compose.runtime.ExperimentalComposeApi',
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
compose/build.gradle
Normal file
51
compose/build.gradle
Normal file
@ -0,0 +1,51 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.multiplatform'
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
kotlin {
|
||||
srcDir 'upstream/compose/runtime/runtime/src/commonMain/kotlin'
|
||||
srcDir 'upstream/compose/runtime/runtime-dispatch/src/commonMain/kotlin'
|
||||
}
|
||||
dependencies {
|
||||
api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
|
||||
api 'org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.3'
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
kotlin {
|
||||
srcDir 'upstream/compose/runtime/runtime/src/jvmMain/kotlin'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
kotlinPlugin
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kotlinPlugin 'androidx.compose:compose-compiler:1.0.0-alpha02'
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
useIR = true
|
||||
}
|
||||
|
||||
def pluginConfiguration = configurations.kotlinPlugin
|
||||
dependsOn(pluginConfiguration)
|
||||
doFirst {
|
||||
if (!file('upstream/.git').exists()) {
|
||||
throw new RuntimeException(
|
||||
"Missing 'upstream' git submodule clone. Did you run 'git submodule update --init'?")
|
||||
}
|
||||
|
||||
if (!pluginConfiguration.isEmpty()) {
|
||||
kotlinOptions.freeCompilerArgs +=
|
||||
"-Xplugin=${pluginConfiguration.files.first()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package androidx.compose.runtime
|
||||
|
||||
// TODO The notion of a global EmbeddingContext actual should not exist! It breaks concurrent
|
||||
// usage on multiple threads which target multiple threads. This should always be pulled from the
|
||||
// Recomposer.
|
||||
var yoloGlobalEmbeddingContext: EmbeddingContext? = null
|
||||
|
||||
actual fun EmbeddingContext(): EmbeddingContext = yoloGlobalEmbeddingContext!!
|
||||
|
||||
private val keyInfo = mutableMapOf<Int, String>()
|
||||
|
||||
private fun findSourceKey(key: Any): Int? =
|
||||
when (key) {
|
||||
is Int -> key
|
||||
is JoinedKey -> {
|
||||
key.left?.let { findSourceKey(it) } ?: key.right?.let { findSourceKey(it) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
internal actual fun recordSourceKeyInfo(key: Any) {
|
||||
val sk = findSourceKey(key)
|
||||
sk?.let {
|
||||
keyInfo.getOrPut(sk, {
|
||||
val stack = Thread.currentThread().stackTrace
|
||||
// On Android the frames looks like:
|
||||
// 0: getThreadStackTrace() (native method)
|
||||
// 1: getStackTrace()
|
||||
// 2: recordSourceKey()
|
||||
// 3: start()
|
||||
// 4: startGroup() or startNode()
|
||||
// 5: non-inline call/emit?
|
||||
// 5 or 6: <calling method>
|
||||
// On a desktop VM this looks like:
|
||||
// 0: getStackTrace()
|
||||
// 1: recordSourceKey()
|
||||
// 2: start()
|
||||
// 3: startGroup() or startNode()
|
||||
// 4: non-inline call/emit?
|
||||
// 4 or 5: <calling method>
|
||||
// If the stack method at 4 is startGroup assume we want 5 instead.
|
||||
val frameNumber = stack[4].let {
|
||||
if (it.methodName == "startGroup" || it.methodName == "startNode") 5 else 4
|
||||
}
|
||||
val frame = stack[frameNumber].let {
|
||||
if (it.methodName == "call" || it.methodName == "emit")
|
||||
stack[frameNumber + 1]
|
||||
else
|
||||
stack[frameNumber]
|
||||
}
|
||||
"${frame.className}.${frame.methodName} (${frame.fileName}:${frame.lineNumber})"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
actual fun keySourceInfoOf(key: Any): String? = keyInfo[key]
|
||||
actual fun resetSourceInfo(): Unit = keyInfo.clear()
|
||||
|
||||
internal actual object Trace {
|
||||
actual fun beginSection(name: String) {
|
||||
}
|
||||
actual fun endSection() {
|
||||
}
|
||||
}
|
||||
|
||||
actual annotation class MainThread
|
||||
actual annotation class CheckResult(actual val suggest: String)
|
||||
@ -0,0 +1,6 @@
|
||||
package androidx.compose.runtime.dispatch
|
||||
|
||||
actual val DefaultMonotonicFrameClock: MonotonicFrameClock get() {
|
||||
throw UnsupportedOperationException(
|
||||
"No default MonotonicFrameClock! You must include one in your CoroutineContext.")
|
||||
}
|
||||
1
compose/upstream
Submodule
1
compose/upstream
Submodule
Submodule compose/upstream added at 31022a2dda
26
mosaic/build.gradle
Normal file
26
mosaic/build.gradle
Normal file
@ -0,0 +1,26 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
configurations {
|
||||
kotlinPlugin
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compose')
|
||||
|
||||
kotlinPlugin 'androidx.compose:compose-compiler:1.0.0-alpha02'
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
useIR = true
|
||||
}
|
||||
|
||||
def pluginConfiguration = configurations.kotlinPlugin
|
||||
dependsOn(pluginConfiguration)
|
||||
doFirst {
|
||||
if (!pluginConfiguration.isEmpty()) {
|
||||
kotlinOptions.freeCompilerArgs +=
|
||||
"-Xplugin=${pluginConfiguration.files.first()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
149
mosaic/src/main/kotlin/example/Main.kt
Normal file
149
mosaic/src/main/kotlin/example/Main.kt
Normal file
@ -0,0 +1,149 @@
|
||||
package example
|
||||
|
||||
import androidx.compose.runtime.AbstractApplier
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.EmbeddingContext
|
||||
import androidx.compose.runtime.FrameManager
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Recomposer
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionFor
|
||||
import androidx.compose.runtime.dispatch.BroadcastFrameClock
|
||||
import androidx.compose.runtime.dispatch.MonotonicFrameClock
|
||||
import androidx.compose.runtime.emit
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.withMutableSnapshot
|
||||
import androidx.compose.runtime.yoloGlobalEmbeddingContext
|
||||
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
|
||||
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
sealed class Node {
|
||||
abstract val children: MutableList<Node>
|
||||
}
|
||||
|
||||
class Line : Node() {
|
||||
var value: String = ""
|
||||
override fun toString() = value
|
||||
|
||||
override val children: MutableList<Node>
|
||||
get() = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
class Lines : Node() {
|
||||
override val children = mutableListOf<Node>()
|
||||
override fun toString() = buildString {
|
||||
children.forEachIndexed { index, node ->
|
||||
if (index > 0) {
|
||||
append('\n')
|
||||
}
|
||||
append(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NodeApplier(root: Node) : AbstractApplier<Node>(root) {
|
||||
override fun insert(index: Int, instance: Node) {
|
||||
root.children.add(index, instance)
|
||||
}
|
||||
|
||||
override fun remove(index: Int, count: Int) {
|
||||
root.children.remove(index, count)
|
||||
}
|
||||
|
||||
override fun move(from: Int, to: Int, count: Int) {
|
||||
root.children.move(from, to, count)
|
||||
}
|
||||
|
||||
override fun onClear() {
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
runBlocking {
|
||||
val mainThread = Thread.currentThread()
|
||||
|
||||
val clock = BroadcastFrameClock()
|
||||
val composeContext = coroutineContext + clock
|
||||
|
||||
val embeddingContext = object : EmbeddingContext {
|
||||
override fun isMainThread(): Boolean {
|
||||
return Thread.currentThread() === mainThread
|
||||
}
|
||||
|
||||
override fun mainThreadCompositionContext(): CoroutineContext {
|
||||
return composeContext
|
||||
}
|
||||
}
|
||||
yoloGlobalEmbeddingContext = embeddingContext
|
||||
|
||||
val recomposer = Recomposer(embeddingContext)
|
||||
|
||||
val lines = Lines()
|
||||
val applier = NodeApplier(lines)
|
||||
|
||||
val composition = compositionFor(Any(), applier, recomposer)
|
||||
composeContext[Job]!!.invokeOnCompletion {
|
||||
composition.dispose()
|
||||
}
|
||||
|
||||
withContext(composeContext) {
|
||||
launch(start = UNDISPATCHED) {
|
||||
recomposer.runRecomposeAndApplyChanges()
|
||||
}
|
||||
|
||||
val counts = mutableStateOf(0)
|
||||
composition.setContent {
|
||||
val count by remember { counts }
|
||||
Line("The count is $count")
|
||||
}
|
||||
|
||||
launch(start = UNDISPATCHED) {
|
||||
while (true) {
|
||||
clock.sendFrame(System.nanoTime())
|
||||
println(lines)
|
||||
delay(500)
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 1..10) {
|
||||
delay(1_000)
|
||||
withMutableSnapshot {
|
||||
counts.value = i
|
||||
}
|
||||
}
|
||||
// Wait for final frame.
|
||||
clock.withFrameNanos { }
|
||||
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Line(value: String) {
|
||||
emit<Line, NodeApplier>(::Line) {
|
||||
set(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
rootProject.name = 'mosaic'
|
||||
|
||||
include ':compose'
|
||||
include ':mosaic'
|
||||
include ':yoga'
|
||||
include ':yoga:core'
|
||||
include ':yoga:java'
|
||||
|
||||
Reference in New Issue
Block a user