feat(scripting): ipc global channels

This commit is contained in:
rhunk 2023-09-17 19:35:36 +02:00
parent 98b3f2bfc9
commit 19a0ab8398
6 changed files with 99 additions and 64 deletions

View File

@ -1,19 +0,0 @@
package me.rhunk.snapenhance.scripting
import java.util.concurrent.ConcurrentHashMap
class IRemoteIPC : IPCInterface() {
private val listeners = ConcurrentHashMap<String, MutableSet<Listener>>()
fun removeListener(eventName: String, listener: Listener) {
listeners[eventName]?.remove(listener)
}
override fun on(eventName: String, listener: Listener) {
listeners.getOrPut(eventName) { mutableSetOf() }.add(listener)
}
override fun emit(eventName: String, args: Array<out String?>) {
listeners[eventName]?.toList()?.forEach { it(args) }
}
}

View File

@ -0,0 +1,53 @@
package me.rhunk.snapenhance.scripting
import android.os.DeadObjectException
import me.rhunk.snapenhance.bridge.scripting.IPCListener
import me.rhunk.snapenhance.core.logger.AbstractLogger
import me.rhunk.snapenhance.scripting.type.ModuleInfo
import java.util.concurrent.ConcurrentHashMap
typealias IPCListeners = ConcurrentHashMap<String, MutableMap<String, MutableSet<IPCListener>>> // channel, eventName -> listeners
class RemoteManagerIPC(
private val moduleInfo: ModuleInfo,
private val logger: AbstractLogger,
private val ipcListeners: IPCListeners = ConcurrentHashMap(),
) : IPCInterface() {
companion object {
private const val TAG = "RemoteManagerIPC"
}
override fun on(eventName: String, listener: Listener) {
onBroadcast(moduleInfo.name, eventName, listener)
}
override fun emit(eventName: String, vararg args: String?) {
emit(moduleInfo.name, eventName, *args)
}
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(object: IPCListener.Stub() {
override fun onMessage(args: Array<out String?>) {
try {
listener(args)
} catch (doe: DeadObjectException) {
ipcListeners[channel]?.get(eventName)?.remove(this)
} catch (t: Throwable) {
logger.error("Failed to receive message for channel: $channel, event: $eventName", t, TAG)
}
}
})
}
override fun broadcast(channel: String, eventName: String, vararg args: String?) {
ipcListeners[channel]?.get(eventName)?.toList()?.forEach {
try {
it.onMessage(args)
} catch (doe: DeadObjectException) {
ipcListeners[channel]?.get(eventName)?.remove(it)
} catch (t: Throwable) {
logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG)
}
}
}
}

View File

@ -1,7 +1,6 @@
package me.rhunk.snapenhance.scripting
import android.net.Uri
import android.os.DeadObjectException
import androidx.documentfile.provider.DocumentFile
import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.bridge.scripting.IPCListener
@ -14,14 +13,10 @@ class RemoteScriptManager(
private val context: RemoteSideContext,
) : IScripting.Stub() {
val runtime = ScriptRuntime(context.log)
val remoteIpc = IRemoteIPC()
private fun getScriptFolder()
= DocumentFile.fromTreeUri(context.androidContext, Uri.parse(context.config.root.scripting.moduleFolder.get()))
//private fun hasHotReload() = context.config.root.scripting.hotReload.get()
private val reloadListeners = mutableListOf<ReloadListener>()
private val cachedModuleInfo = mutableMapOf<String, ModuleInfo>()
private val ipcListeners = IPCListeners()
fun sync() {
getScriptFileNames().forEach { name ->
@ -40,8 +35,8 @@ class RemoteScriptManager(
}
fun init() {
runtime.buildModuleObject = {
putConst("ipc", this, remoteIpc)
runtime.buildModuleObject = { module ->
putConst("ipc", this, RemoteManagerIPC(module.moduleInfo, context.log, ipcListeners))
}
sync()
@ -56,6 +51,8 @@ class RemoteScriptManager(
return context.androidContext.contentResolver.openInputStream(file.uri)?.use(callback) ?: callback(null)
}
private fun getScriptFolder() = DocumentFile.fromTreeUri(context.androidContext, Uri.parse(context.config.root.scripting.moduleFolder.get()))
private fun getScriptFileNames(): List<String> {
return (getScriptFolder() ?: return emptyList()).listFiles().filter { it.name?.endsWith(".js") ?: false }.map { it.name!! }
}
@ -78,23 +75,15 @@ class RemoteScriptManager(
reloadListeners.add(listener)
}
override fun registerIPCListener(eventName: String, listener: IPCListener) {
remoteIpc.on(eventName, object: Listener {
override fun invoke(args: Array<out String?>) {
try {
listener.onMessage(args)
} catch (e: DeadObjectException) {
remoteIpc.removeListener(eventName, this)
} catch (t: Throwable) {
context.log.error("Failed to invoke $eventName", t)
}
}
})
override fun registerIPCListener(channel: String, eventName: String, listener: IPCListener) {
ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(listener)
}
override fun sendIPCMessage(eventName: String, args: Array<out String>) {
override fun sendIPCMessage(channel: String, eventName: String, args: Array<out String>) {
runCatching {
remoteIpc.emit(eventName, args)
ipcListeners[channel]?.get(eventName)?.toList()?.forEach {
it.onMessage(args)
}
}.onFailure {
context.log.error("Failed to send message for $eventName", it)
}

View File

@ -10,7 +10,7 @@ interface IScripting {
void registerReloadListener(ReloadListener listener);
void registerIPCListener(String eventName, IPCListener listener);
void registerIPCListener(String channel, String eventName, IPCListener listener);
void sendIPCMessage(String eventName, in String[] args);
void sendIPCMessage(String channel, String eventName, in String[] args);
}

View File

@ -5,8 +5,13 @@ typealias Listener = (Array<out String?>) -> Unit
abstract class IPCInterface {
abstract fun on(eventName: String, listener: Listener)
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?)
@Suppress("unused")
fun emit(eventName: String) = emit(eventName, *emptyArray())
@Suppress("unused")
fun emit(channel: String, eventName: String) = broadcast(channel, eventName, *emptyArray())
}

View File

@ -18,7 +18,6 @@ class CoreScriptRuntime(
private val scriptHookers = mutableListOf<ScriptHooker>()
fun connect(scriptingInterface: IScripting) {
scriptingInterface.apply {
registerReloadListener(object: ReloadListener.Stub() {
@ -27,31 +26,39 @@ class CoreScriptRuntime(
}
})
ipcInterface = object: IPCInterface() {
override fun on(eventName: String, listener: Listener) {
registerIPCListener(eventName, object: IPCListener.Stub() {
override fun onMessage(args: Array<out String?>) {
listener(args)
}
})
}
buildModuleObject = { module ->
putConst("ipc", this, object: IPCInterface() {
override fun onBroadcast(channel: String, eventName: String, listener: Listener) {
registerIPCListener(channel, eventName, object: IPCListener.Stub() {
override fun onMessage(args: Array<out String?>) {
listener(args)
}
})
}
override fun emit(eventName: String, args: Array<out String?>) {
sendIPCMessage(eventName, args)
override fun on(eventName: String, listener: Listener) {
onBroadcast(module.moduleInfo.name, eventName, listener)
}
override fun emit(eventName: String, vararg args: String?) {
broadcast(module.moduleInfo.name, eventName, *args)
}
override fun broadcast(channel: String, eventName: String, vararg args: String?) {
sendIPCMessage(channel, eventName, args)
}
})
putFunction("findClass") {
val className = it?.get(0).toString()
classLoader.loadClass(className)
}
putConst("hooker", this, ScriptHooker(module.moduleInfo, logger, classLoader).also {
scriptHookers.add(it)
})
}
}
buildModuleObject = { module ->
putConst("ipc", this, ipcInterface)
putFunction("findClass") {
val className = it?.get(0).toString()
classLoader.loadClass(className)
}
putConst("hooker", this, ScriptHooker(module.moduleInfo, logger, classLoader).also {
scriptHookers.add(it)
})
}
scriptingInterface.enabledScripts.forEach { path ->
runCatching {