mirror of
https://github.com/JakeWharton/mosaic.git
synced 2025-11-11 18:56:27 +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"]
|
[submodule "yoga/upstream"]
|
||||||
path = yoga/upstream
|
path = yoga/upstream
|
||||||
url = https://github.com/facebook/yoga
|
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 {
|
buildscript {
|
||||||
dependencies {
|
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"
|
classpath "dev.nokee.jni-library:dev.nokee.jni-library.gradle.plugin:0.4.0"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,5 +12,19 @@ buildscript {
|
|||||||
subprojects {
|
subprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
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'
|
rootProject.name = 'mosaic'
|
||||||
|
|
||||||
|
include ':compose'
|
||||||
|
include ':mosaic'
|
||||||
include ':yoga'
|
include ':yoga'
|
||||||
include ':yoga:core'
|
include ':yoga:core'
|
||||||
include ':yoga:java'
|
include ':yoga:java'
|
||||||
|
|||||||
Reference in New Issue
Block a user