Skip to content

Commit

Permalink
refactor: enhance lifecycle management of Rime
Browse files Browse the repository at this point in the history
Migrate the abilities of RimeWrapper to RimeDaemon, the real Rime instance will be kept in RimeDaemon and shared via RimeSession
  • Loading branch information
WhiredPlanck committed Apr 13, 2024
1 parent 0657eae commit ba9d0d8
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 185 deletions.
77 changes: 45 additions & 32 deletions app/src/main/java/com/osfans/trime/core/Rime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.osfans.trime.data.AppPrefs
import com.osfans.trime.data.DataManager
import com.osfans.trime.data.opencc.OpenCCDictManager
import com.osfans.trime.data.schema.SchemaManager
import com.osfans.trime.util.appContext
import com.osfans.trime.util.isStorageAvailable
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
Expand All @@ -32,19 +34,57 @@ import kotlin.system.measureTimeMillis
*
* @see [librime](https://github.com/rime/librime)
*/
class Rime(fullCheck: Boolean) : RimeApi {
class Rime : RimeApi, RimeLifecycleOwner {
private val lifecycleImpl = RimeLifecycleImpl()
override val lifecycle get() = lifecycleImpl

override val notificationFlow = notificationFlow_.asSharedFlow()
override val stateFlow = lifecycle.stateFlow

override val isReady: Boolean
get() = lifecycle.stateFlow.value == RimeLifecycle.State.READY

fun startup(fullCheck: Boolean) {
if (lifecycle.stateFlow.value != RimeLifecycle.State.STOPPED) {
Timber.w("Skip starting rime: not at stopped state!")
return
}
if (appContext.isStorageAvailable()) {
isHandlingRimeNotification = false

init {
startup(fullCheck)
DataManager.dirFireChange()
DataManager.sync()

val sharedDataDir = AppPrefs.defaultInstance().profile.sharedDataDir
val userDataDir = AppPrefs.defaultInstance().profile.userDataDir

lifecycleImpl.emitState(RimeLifecycle.State.STARTING)
Timber.i("Starting up Rime APIs ...")
startupRime(sharedDataDir, userDataDir, fullCheck)

Timber.i("Initializing schema stuffs after starting up ...")
SchemaManager.init(getCurrentRimeSchema())
updateStatus()
OpenCCDictManager.buildOpenCCDict()
lifecycleImpl.emitState(RimeLifecycle.State.READY)
}
}

fun finalize() {
if (lifecycle.stateFlow.value != RimeLifecycle.State.READY) {
Timber.w("Skip stopping rime: not at ready state!")
return
}
exitRime()
lifecycleImpl.emitState(RimeLifecycle.State.STOPPED)
}

companion object {
private var instance: Rime? = null

@JvmStatic
fun getInstance(fullCheck: Boolean = false): Rime {
if (instance == null) instance = Rime(fullCheck)
fun getInstance(): Rime {
if (instance == null) instance = Rime()
return instance!!
}

Expand All @@ -61,33 +101,6 @@ class Rime(fullCheck: Boolean) : RimeApi {
System.loadLibrary("rime_jni")
}

private fun startup(fullCheck: Boolean) {
isHandlingRimeNotification = false

DataManager.sync()

val sharedDataDir = AppPrefs.defaultInstance().profile.sharedDataDir
val userDataDir = AppPrefs.defaultInstance().profile.userDataDir

Timber.i("Starting up Rime APIs ...")
startupRime(sharedDataDir, userDataDir, fullCheck)

Timber.i("Initializing schema stuffs after starting up ...")
SchemaManager.init(getCurrentRimeSchema())
updateStatus()
}

fun destroy() {
exitRime()
instance = null
}

fun deploy() {
destroy()
getInstance(true)
OpenCCDictManager.buildOpenCCDict()
}

fun updateStatus() {
SchemaManager.updateSwitchOptions()
measureTimeMillis {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/osfans/trime/core/RimeApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ import kotlinx.coroutines.flow.SharedFlow

interface RimeApi {
val notificationFlow: SharedFlow<RimeNotification<*>>

val stateFlow: SharedFlow<RimeLifecycle.State>

val isReady: Boolean
}
94 changes: 94 additions & 0 deletions app/src/main/java/com/osfans/trime/core/RimeLifecycle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.osfans.trime.core

import android.os.Handler
import android.os.Looper
import androidx.core.os.HandlerCompat
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import java.util.Collections

class RimeLifecycleImpl : RimeLifecycle {
private val _stateFlow = MutableStateFlow(RimeLifecycle.State.STOPPED)
override val stateFlow = _stateFlow.asStateFlow()

override val handler = HandlerCompat.createAsync(Looper.getMainLooper())
override val runnableList: MutableList<Runnable> = Collections.synchronizedList(mutableListOf<Runnable>())

fun emitState(state: RimeLifecycle.State) {
when (state) {
RimeLifecycle.State.STARTING -> {
checkAtState(RimeLifecycle.State.STOPPED)
_stateFlow.value = RimeLifecycle.State.STARTING
}
RimeLifecycle.State.READY -> {
checkAtState(RimeLifecycle.State.STARTING)
_stateFlow.value = RimeLifecycle.State.READY
}
RimeLifecycle.State.STOPPED -> {
checkAtState(RimeLifecycle.State.READY)
_stateFlow.value = RimeLifecycle.State.STOPPED
}
}
}

private fun checkAtState(state: RimeLifecycle.State) =
takeIf { (_stateFlow.value == state) }
?: throw IllegalStateException("Currently not at $state! Actual state is ${_stateFlow.value}")
}

interface RimeLifecycle {
val stateFlow: StateFlow<State>
val handler: Handler
val runnableList: MutableList<Runnable>

enum class State {
STARTING,
READY,
STOPPED,
}
}

interface RimeLifecycleOwner {
val lifecycle: RimeLifecycle
val handler get() = lifecycle.handler
}

fun RimeLifecycle.whenAtState(
state: RimeLifecycle.State,
block: () -> Unit,
) {
runnableList.add(Runnable { block() })
if (stateFlow.value == state) {
handler.post(runnableList.removeFirst())
} else {
StateDelegate(this, state).run(block)
}
}

inline fun RimeLifecycle.whenReady(noinline block: () -> Unit) = whenAtState(RimeLifecycle.State.READY, block)

private class StateDelegate(val lifecycle: RimeLifecycle, val state: RimeLifecycle.State) {
private var job: Job? = null

init {
job =
lifecycle.stateFlow.onEach {
if (it == state) {
while (lifecycle.runnableList.isNotEmpty()) {
lifecycle.handler.post(lifecycle.runnableList.removeFirst())
}
}
}.launchIn(MainScope())
}

fun <T> run(block: () -> T): T {
job?.cancel()
job = null
return block()
}
}
32 changes: 30 additions & 2 deletions app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package com.osfans.trime.daemon

import com.osfans.trime.core.Rime
import com.osfans.trime.core.RimeApi
import com.osfans.trime.core.RimeLifecycle
import com.osfans.trime.core.whenReady
import kotlinx.coroutines.runBlocking
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

object RimeDaemon {
private val realRime by lazy { Rime.getInstance() }
private val realRime by lazy { Rime() }

private val rimeImpl by lazy { object : RimeApi by realRime {} }

Expand All @@ -28,13 +30,30 @@ object RimeDaemon {
ensureEstablished {
runBlocking { block(rimeImpl) }
}

override fun runOnReady(block: RimeApi.() -> Unit) {
ensureEstablished {
realRime.lifecycle.whenReady { block(rimeImpl) }
}
}

override fun runIfReady(block: RimeApi.() -> Unit) {
ensureEstablished {
if (realRime.isReady) {
realRime.lifecycle.handler.post { block(rimeImpl) }
}
}
}
}

fun createSession(name: String): RimeSession =
lock.withLock {
if (name in sessions) {
return@withLock sessions.getValue(name)
}
if (realRime.lifecycle.stateFlow.value == RimeLifecycle.State.STOPPED) {
realRime.startup(false)
}
val session = establish(name)
sessions[name] = session
return@withLock session
Expand All @@ -47,7 +66,16 @@ object RimeDaemon {
}
sessions -= name
if (sessions.isEmpty()) {
Rime.destroy()
realRime.finalize()
}
}

/**
* Restart Rime instance to deploy while keep the session
*/
fun restartRime(fullCheck: Boolean = false) =
lock.withLock {
realRime.finalize()
realRime.startup(fullCheck)
}
}
18 changes: 16 additions & 2 deletions app/src/main/java/com/osfans/trime/daemon/RimeSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@ package com.osfans.trime.daemon
import com.osfans.trime.core.RimeApi

/**
* A Interface to run different operations on RimeApi
* A interface to run different operations on RimeApi
*/
interface RimeSession {

/**
* Run an operation immediately
* The suspended [block] will be executed in caller's thread.
* Use this function only for non-blocking operations like
* accessing [RimeApi.notificationFlow].
*/
fun <T> run(block: suspend RimeApi.() -> T): T

/**
* Run an operation immediately if rime is at ready state.
* Otherwise, caller will be suspended until rime is ready and operation is done.
* The [block] will be executed in main thread.
* Client should use this function in most cases.
*/
fun runOnReady(block: RimeApi.() -> Unit)

/**
* Run an operation if rime is at ready state.
* Otherwise, do nothing.
* The [block] will be executed in main thread.
*/
fun runIfReady(block: RimeApi.() -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.ToastUtils
import com.osfans.trime.R
import com.osfans.trime.core.Rime
import com.osfans.trime.daemon.RimeDaemon
import com.osfans.trime.data.AppPrefs
import com.osfans.trime.ime.core.RimeWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
Expand All @@ -55,15 +55,15 @@ class IntentReceiver : BroadcastReceiver(), CoroutineScope by MainScope() {
COMMAND_DEPLOY ->
launch {
withContext(Dispatchers.IO) {
RimeWrapper.deploy()
RimeDaemon.restartRime()
}
ToastUtils.showLong(R.string.deploy_finish)
}
COMMAND_SYNC ->
launch {
withContext(Dispatchers.IO) {
Rime.syncRimeUserData()
RimeWrapper.deploy()
RimeDaemon.restartRime()
}
}
COMMAND_TIMING_SYNC ->
Expand Down Expand Up @@ -106,7 +106,7 @@ class IntentReceiver : BroadcastReceiver(), CoroutineScope by MainScope() {
}

Rime.syncRimeUserData()
RimeWrapper.deploy()
RimeDaemon.restartRime()
wakeLock.release() // 释放唤醒锁
}
}
Expand Down
Loading

0 comments on commit ba9d0d8

Please sign in to comment.