Be a little more rigorous in KotlinLuaMachine's threading

This commit is contained in:
Jonathan Coates 2022-10-30 11:44:01 +00:00
parent 5ee5b11995
commit 1a87175ae7
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
4 changed files with 34 additions and 9 deletions

View File

@ -14,8 +14,6 @@ public enum UploadResult
CONSUMED,
ERROR;
public static final ITextComponent SUCCESS_TITLE = new TranslationTextComponent( "gui.computercraft.upload.success" );
public static final ITextComponent FAILED_TITLE = new TranslationTextComponent( "gui.computercraft.upload.failed" );
public static final ITextComponent COMPUTER_OFF_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.computer_off" );
public static final ITextComponent TOO_MUCH_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.too_much" );

View File

@ -116,8 +116,6 @@
"gui.computercraft.tooltip.turn_off.key": "Hold Ctrl+S",
"gui.computercraft.tooltip.terminate": "Stop the currently running code",
"gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T",
"gui.computercraft.upload.success": "Upload Succeeded",
"gui.computercraft.upload.success.msg": "%d files uploaded.",
"gui.computercraft.upload.failed": "Upload Failed",
"gui.computercraft.upload.failed.computer_off": "You must turn the computer on before uploading files.",
"gui.computercraft.upload.failed.too_much": "Your files are too large to be uploaded.",

View File

@ -5,11 +5,9 @@
import dan200.computercraft.core.lua.ILuaMachine
import dan200.computercraft.core.lua.MachineEnvironment
import dan200.computercraft.core.lua.MachineResult
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import java.io.InputStream
import kotlin.coroutines.CoroutineContext
/**
* An [ILuaMachine] which runs Kotlin functions instead.
@ -26,7 +24,7 @@ override fun handleEvent(eventName: String?, arguments: Array<out Any>?): Machin
queueEvent(eventName, arguments)
} else {
val task = getTask()
if (task != null) CoroutineScope(Dispatchers.Unconfined + CoroutineName("Computer")).launch { task() }
if (task != null) CoroutineScope(NeverDispatcher() + CoroutineName("Computer")).launch { task() }
}
return MachineResult.OK
@ -38,4 +36,20 @@ override fun printExecutionState(out: StringBuilder) {}
* Get the next task to execute on this computer.
*/
protected abstract fun getTask(): (suspend KotlinLuaMachine.() -> Unit)?
/**
* A [CoroutineDispatcher] which only allows resuming from the computer thread. In practice, this means the only
* way to yield is with [pullEvent].
*/
private class NeverDispatcher : CoroutineDispatcher() {
private val expectedGroup = Thread.currentThread().threadGroup
override fun dispatch(context: CoroutineContext, block: Runnable) {
if (Thread.currentThread().threadGroup != expectedGroup) {
throw UnsupportedOperationException("Cannot perform arbitrary yields")
}
block.run()
}
}
}

View File

@ -4,9 +4,11 @@
import dan200.computercraft.api.lua.ILuaContext
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 kotlin.time.Duration
/**
* The context for tasks which consume Lua objects.
@ -40,6 +42,19 @@ suspend fun pullEvent(event: String? = null): Array<out Any?>
/** Call a peripheral method. */
suspend fun LuaTaskContext.callPeripheral(name: String, method: String, vararg args: Any?): Array<out Any?>? =
getApi<PeripheralAPI>().call(context, ObjectArguments(name, method, *args)).await()
/**
* Sleep for the given duration. This uses the internal computer clock, so won't be accurate.
*/
suspend fun LuaTaskContext.sleep(duration: Duration) {
val timer = getApi<OSAPI>().startTimer(duration.inWholeMilliseconds / 1000.0)
while (true) {
val event = pullEvent("timer")
if (event[0] == "timer" && event[1] is Number && (event[1] as Number).toInt() == timer) {
return
}
}
}
}
/** Get a registered API. */