mirror of
				https://github.com/JakeWharton/mosaic.git
				synced 2025-11-04 06:32:26 +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