commit 664c6211ca937383730d381eb264850d249db711
parent 47efdf165df5da90f7e66757e7bfbac16ff681ba
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 18 Aug 2024 22:33:01 +0200
feat(scripting): support bridge reload
- add module.onBridgeConnected
- add isBridgeAlive in ipc binding
Diffstat:
10 files changed, 98 insertions(+), 42 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteScriptManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteScriptManager.kt
@@ -204,14 +204,17 @@ class RemoteScriptManager(
ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(listener)
}
- override fun sendIPCMessage(channel: String, eventName: String, args: Array<out String>) {
- runCatching {
- ipcListeners[channel]?.get(eventName)?.toList()?.forEach {
+ override fun sendIPCMessage(channel: String, eventName: String, args: Array<out String>): Int {
+ var dispatchCount = 0
+ ipcListeners[channel]?.get(eventName)?.toList()?.forEach {
+ runCatching {
it.onMessage(args)
+ dispatchCount++
+ }.onFailure {
+ context.log.error("Failed to send message for $eventName", it)
}
- }.onFailure {
- context.log.error("Failed to send message for $eventName", it)
}
+ return dispatchCount
}
override fun configTransaction(
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ManagerIPC.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ManagerIPC.kt
@@ -19,8 +19,8 @@ class ManagerIPC(
onBroadcast(context.moduleInfo.name, eventName, listener)
}
- override fun emit(eventName: String, vararg args: String?) {
- emit(context.moduleInfo.name, eventName, *args)
+ override fun emit(eventName: String, vararg args: String?): Int {
+ return broadcast(context.moduleInfo.name, eventName, *args)
}
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
@@ -37,15 +37,18 @@ class ManagerIPC(
})
}
- override fun broadcast(channel: String, eventName: String, vararg args: String?) {
+ override fun broadcast(channel: String, eventName: String, vararg args: String?): Int {
+ var dispatchCount = 0
ipcListeners[channel]?.get(eventName)?.toList()?.forEach {
try {
it.onMessage(args)
+ dispatchCount++
} catch (doe: DeadObjectException) {
ipcListeners[channel]?.get(eventName)?.remove(it)
} catch (t: Throwable) {
context.runtime.logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG)
}
}
+ return dispatchCount
}
}
\ No newline at end of file
diff --git a/common/src/main/aidl/me/rhunk/snapenhance/bridge/scripting/IScripting.aidl b/common/src/main/aidl/me/rhunk/snapenhance/bridge/scripting/IScripting.aidl
@@ -10,7 +10,7 @@ interface IScripting {
oneway void registerIPCListener(String channel, String eventName, IPCListener listener);
- oneway void sendIPCMessage(String channel, String eventName, in String[] args);
+ int sendIPCMessage(String channel, String eventName, in String[] args);
@nullable String configTransaction(String module, String action, @nullable String key, @nullable String value, boolean save);
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/JSModule.kt
@@ -230,6 +230,7 @@ class JSModule(
}
}
}
+
fun registerBindings(vararg bindings: AbstractBinding) {
bindings.forEach {
moduleBindings[it.name] = it.apply {
@@ -238,6 +239,20 @@ class JSModule(
}
}
+ fun onBridgeConnected(reloaded: Boolean = false) {
+ if (reloaded) {
+ moduleBindings.values.forEach { binding ->
+ runCatching {
+ binding.onBridgeReloaded()
+ }.onFailure {
+ scriptRuntime.logger.error("Failed to call onBridgeConnected for binding ${binding.name}", it)
+ }
+ }
+ }
+
+ callFunction("module.onBridgeConnected", reloaded)
+ }
+
@Suppress("UNCHECKED_CAST")
fun <T : Any> getBinding(clazz: KClass<T>): T? {
return moduleBindings.values.find { clazz.isInstance(it) } as? T
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/bindings/AbstractBinding.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/bindings/AbstractBinding.kt
@@ -6,8 +6,19 @@ abstract class AbstractBinding(
) {
lateinit var context: BindingsContext
+ private val bridgeReloadList = mutableListOf<() -> Unit>()
+
+ fun bridgeAutoReload(block: () -> Unit) {
+ bridgeReloadList += block
+ block()
+ }
+
open fun onInit() {}
+ open fun onBridgeReloaded() {
+ bridgeReloadList.forEach { it() }
+ }
+
open fun onDispose() {}
abstract fun getObject(): Any
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/IPCInterface.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/scripting/impl/IPCInterface.kt
@@ -2,6 +2,7 @@ package me.rhunk.snapenhance.common.scripting.impl
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
+import org.mozilla.javascript.annotations.JSFunction
typealias Listener = (List<String?>) -> Unit
@@ -10,8 +11,8 @@ abstract class IPCInterface : AbstractBinding("ipc", BindingSide.COMMON) {
abstract fun onBroadcast(channel: String, eventName: String, listener: Listener)
- abstract fun emit(eventName: String, vararg args: String?)
- abstract fun broadcast(channel: String, eventName: String, vararg args: String?)
+ abstract fun emit(eventName: String, vararg args: String?): Int
+ abstract fun broadcast(channel: String, eventName: String, vararg args: String?): Int
@Suppress("unused")
fun emit(eventName: String) = emit(eventName, *emptyArray())
@@ -20,4 +21,7 @@ abstract class IPCInterface : AbstractBinding("ipc", BindingSide.COMMON) {
broadcast(channel, eventName, *emptyArray())
override fun getObject() = this
+
+ @JSFunction("isBridgeAlive")
+ fun isBridgeAlive() = context.runtime.scripting.asBinder().pingBinder()
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt
@@ -164,7 +164,7 @@ class SnapEnhance {
//if mappings aren't loaded, we can't initialize features
if (!mappings.isMappingsLoaded) return
features.init()
- scriptRuntime.connect(bridgeClient.getScriptingInterface())
+ scriptRuntime.init()
scriptRuntime.eachModule { callFunction("module.onSnapApplicationLoad", androidContext) }
}
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/bridge/BridgeClient.kt
@@ -44,10 +44,15 @@ class BridgeClient(
private val onConnectedCallbacks = mutableListOf<suspend () -> Unit>()
private var cacheSnapEnhanceApkPath: String? = null
- fun addOnConnectedCallback(callback: suspend () -> Unit) {
+ fun addOnConnectedCallback(initNow: Boolean = false, callback: suspend () -> Unit) {
synchronized(onConnectedCallbacks) {
onConnectedCallbacks.add(callback)
}
+ initNow.takeIf { it && this::service.isInitialized }?.let {
+ runBlocking {
+ callback()
+ }
+ }
}
private fun resumeContinuation(state: Boolean) {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/scripting/CoreScriptRuntime.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/scripting/CoreScriptRuntime.kt
@@ -1,7 +1,6 @@
package me.rhunk.snapenhance.core.scripting
import me.rhunk.snapenhance.bridge.scripting.AutoReloadListener
-import me.rhunk.snapenhance.bridge.scripting.IScripting
import me.rhunk.snapenhance.common.logger.AbstractLogger
import me.rhunk.snapenhance.common.scripting.ScriptRuntime
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
@@ -16,33 +15,47 @@ class CoreScriptRuntime(
private val modContext: ModContext,
logger: AbstractLogger,
): ScriptRuntime(modContext.androidContext, logger) {
- fun connect(scriptingInterface: IScripting) {
- scripting = scriptingInterface
- scriptingInterface.apply {
- buildModuleObject = { module ->
- putConst("currentSide", this, BindingSide.CORE.key)
- module.registerBindings(
- CoreScriptConfig(),
- CoreIPC(),
- CoreScriptHooker(),
- CoreMessaging(modContext),
- CoreEvents(modContext),
- )
- }
+ // we assume that the bridge is reloaded the next time we connect to it
+ private var isBridgeReloaded = false
+
+ fun init() {
+ buildModuleObject = { module ->
+ putConst("currentSide", this, BindingSide.CORE.key)
+ module.registerBindings(
+ CoreScriptConfig(),
+ CoreIPC(),
+ CoreScriptHooker(),
+ CoreMessaging(modContext),
+ CoreEvents(modContext),
+ )
+ }
+
+ modContext.bridgeClient.addOnConnectedCallback(initNow = true) {
+ scripting = modContext.bridgeClient.getScriptingInterface()
- enabledScripts.forEach { path ->
- runCatching {
- load(path, scriptingInterface.getScriptContent(path))
- }.onFailure {
- logger.error("Failed to load script $path", it)
+ if (!isBridgeReloaded) {
+ scripting.enabledScripts.forEach { path ->
+ runCatching {
+ load(path, scripting.getScriptContent(path))
+ }.onFailure {
+ logger.error("Failed to load script $path", it)
+ }
}
}
- registerAutoReloadListener(object : AutoReloadListener.Stub() {
+ scripting.registerAutoReloadListener(object : AutoReloadListener.Stub() {
override fun restartApp() {
modContext.softRestartApp()
}
})
+
+ eachModule {
+ onBridgeConnected(reloaded = isBridgeReloaded)
+ }
+
+ if (!isBridgeReloaded) {
+ isBridgeReloaded = true
+ }
}
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/scripting/impl/CoreIPC.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/scripting/impl/CoreIPC.kt
@@ -6,22 +6,24 @@ import me.rhunk.snapenhance.common.scripting.impl.Listener
class CoreIPC : IPCInterface() {
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
- context.runtime.scripting.registerIPCListener(channel, eventName, object: IPCListener.Stub() {
- override fun onMessage(args: Array<out String?>) {
- listener(args.toList())
- }
- })
+ bridgeAutoReload {
+ context.runtime.scripting.registerIPCListener(channel, eventName, object: IPCListener.Stub() {
+ override fun onMessage(args: Array<out String?>) {
+ listener(args.toList())
+ }
+ })
+ }
}
override fun on(eventName: String, listener: Listener) {
onBroadcast(context.moduleInfo.name, eventName, listener)
}
- override fun emit(eventName: String, vararg args: String?) {
- broadcast(context.moduleInfo.name, eventName, *args)
+ override fun emit(eventName: String, vararg args: String?): Int {
+ return broadcast(context.moduleInfo.name, eventName, *args)
}
- override fun broadcast(channel: String, eventName: String, vararg args: String?) {
- context.runtime.scripting.sendIPCMessage(channel, eventName, args)
+ override fun broadcast(channel: String, eventName: String, vararg args: String?): Int {
+ return runCatching { context.runtime.scripting.sendIPCMessage(channel, eventName, args) }.getOrNull() ?: 0
}
}
\ No newline at end of file