feat(scripting): support bridge reload

- add module.onBridgeConnected
- add isBridgeAlive in ipc binding
This commit is contained in:
rhunk
2024-08-18 22:33:01 +02:00
parent 47efdf165d
commit 664c6211ca
10 changed files with 98 additions and 42 deletions

View File

@@ -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(

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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) }
}
}

View File

@@ -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) {

View File

@@ -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
enabledScripts.forEach { path ->
runCatching {
load(path, scriptingInterface.getScriptContent(path))
}.onFailure {
logger.error("Failed to load script $path", it)
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()
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
}
}
}
}

View File

@@ -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
}
}