feat(scripting): support bridge reload
- add module.onBridgeConnected - add isBridgeAlive in ipc binding
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user