1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2026-05-31 09:52:07 +00:00

Simplify event code in LuaTaskContext

We remove support for multiple event listeners, and now just use a
simple event queue again. This makes the code a little simpler, and
removes the risk of race conditions where we do do something, and it
queues the event before we call pullEvent().
This commit is contained in:
Jonathan Coates
2025-12-19 21:12:31 +00:00
parent 419d823d3b
commit 1520bebb6c
2 changed files with 33 additions and 31 deletions
@@ -10,9 +10,11 @@ import dan200.computercraft.api.lua.MethodResult
import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.core.apis.OSAPI
import dan200.computercraft.core.apis.PeripheralAPI
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration
/**
@@ -69,7 +71,8 @@ interface LuaTaskContext {
inline fun <reified T : ILuaAPI> LuaTaskContext.getApi(): T = getApi(T::class.java)
abstract class AbstractLuaTaskContext : LuaTaskContext, AutoCloseable {
private val pullEvents = mutableListOf<PullEvent>()
private val isReceiving = AtomicBoolean(false)
private val eventStream: Channel<Event> = Channel(Channel.UNLIMITED)
private val apis = mutableMapOf<Class<out ILuaAPI>, ILuaAPI>()
protected fun addApi(api: ILuaAPI) {
@@ -77,34 +80,40 @@ abstract class AbstractLuaTaskContext : LuaTaskContext, AutoCloseable {
}
protected val hasEventListeners
get() = pullEvents.isNotEmpty()
get() = isReceiving.get()
protected fun queueEvent(eventName: String?, arguments: Array<out Any?>?) {
val fullEvent: Array<out Any?> = when {
eventName == null && arguments == null -> arrayOf()
eventName != null && arguments == null -> arrayOf(eventName)
eventName == null && arguments != null -> arguments
else -> arrayOf(eventName, *arguments!!)
}
for (i in pullEvents.size - 1 downTo 0) {
val puller = pullEvents[i]
if (puller.name == null || puller.name == eventName || eventName == "terminate") {
pullEvents.removeAt(i)
puller.cont.resumeWith(Result.success(fullEvent))
}
}
eventStream.trySend(Event(eventName, arguments)).getOrThrow()
}
override fun close() {
for (pullEvent in pullEvents) pullEvent.cont.cancel()
pullEvents.clear()
eventStream.close()
}
final override fun <T : ILuaAPI> getApi(api: Class<T>): T =
api.cast(apis[api] ?: throw IllegalStateException("No API of type ${api.name}"))
final override suspend fun pullEvent(event: String?): Array<out Any?> =
suspendCancellableCoroutine { cont -> pullEvents.add(PullEvent(event, cont)) }
final override suspend fun pullEvent(event: String?): Array<out Any?> {
if (!isReceiving.compareAndSet(false, true)) {
throw IllegalStateException("Multiple listeners not currently supported")
}
private class PullEvent(val name: String?, val cont: CancellableContinuation<Array<out Any?>>)
try {
while (true) {
val received = eventStream.receive()
if (event == null || received.name == event) {
return received.full
}
}
} catch (e: ClosedReceiveChannelException) {
throw CancellationException(e)
} finally {
isReceiving.set(false)
}
}
private class Event(val name: String?, val args: Array<out Any?>?) {
val full: Array<out Any?>
get() = if (args == null) arrayOf(name) else arrayOf(name, *args)
}
}
@@ -9,24 +9,18 @@ import dan200.computercraft.api.lua.ILuaContext
import dan200.computercraft.api.lua.LuaException
import dan200.computercraft.core.apis.IAPIEnvironment
import dan200.computercraft.test.core.apis.BasicApiEnvironment
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
class LuaTaskRunner : AbstractLuaTaskContext() {
private val eventStream: Channel<Event> = Channel(Channel.UNLIMITED)
private val apis = mutableListOf<ILuaAPI>()
val environment: IAPIEnvironment = object : BasicApiEnvironment(BasicEnvironment()) {
override fun queueEvent(event: String?, vararg args: Any?) = this@LuaTaskRunner.queueEvent(event, args)
override fun shutdown() {
super.shutdown()
eventStream.close()
}
}
override val context =
ILuaContext { throw LuaException("Cannot queue main thread task") }
@@ -38,11 +32,10 @@ class LuaTaskRunner : AbstractLuaTaskContext() {
}
override fun close() {
super.close()
environment.shutdown()
}
private class Event(val name: String?, val args: Array<out Any?>)
companion object {
fun runTest(timeout: Duration = 5.seconds, fn: suspend LuaTaskRunner.() -> Unit) {
runBlocking {