Don't round trip values in executeMainThreadTask

I've been meaning to fix this for over 6 years, and just kept
forgetting.

Previously ILuaContext.executeMainThreadTask worked by running
ILuaContext.issueMainThreadTask, pulling task_complete events, and then
returning the results.

While this makes the implementation simple, it means that the task's
results were converted into Lua values (in order to queue the event) and
then back into Java ones (when the event was pulled), before eventually
being converted into Lua once more.

Not only is this inefficient, as roundtripping isn't lossless, you
couldn't return functions or rich objects from main thread functions
(see https://github.com/dan200/ComputerCraft/issues/125).

We now store the return value on the Java side and then return that when
the receiving the task_complete event - the event no longer carries the
result. Note this does not affect methods using issueMainThreadTask!
This commit is contained in:
Jonathan Coates 2022-12-15 20:19:01 +00:00
parent 2b237332ce
commit e7fe22d4f8
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
2 changed files with 46 additions and 14 deletions

View File

@ -5,37 +5,59 @@
*/
package dan200.computercraft.api.lua;
import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
final class TaskCallback implements ILuaCallback {
import javax.annotation.Nullable;
final class TaskCallback implements ILuaCallback, LuaTask {
private final LuaTask task;
private volatile @Nullable Object[] result;
private volatile @MonotonicNonNull LuaException failure;
private final long taskId;
private final MethodResult pull = MethodResult.pullEvent("task_complete", this);
private final long task;
private TaskCallback(long task) {
private TaskCallback(ILuaContext context, LuaTask task) throws LuaException {
this.task = task;
taskId = context.issueMainThreadTask(this);
}
@Nullable
@Override
public Object[] execute() throws LuaException {
// Store the result/exception: we read these back when receiving the task_complete event.
try {
result = task.execute();
return null;
} catch (LuaException e) {
// We only care about storing LuaExceptions as we want also want to preserve custom error levels: other
// exceptions won't have this extra data!
failure = e;
throw e;
}
}
@Override
public MethodResult resume(Object[] response) throws LuaException {
if (response.length < 3 || !(response[1] instanceof Number) || !(response[2] instanceof Boolean)) {
if (response.length < 3 || !(response[1] instanceof Number eventTask) || !(response[2] instanceof Boolean isOk)) {
return pull;
}
if (((Number) response[1]).longValue() != task) return pull;
if (eventTask.longValue() != taskId) return pull;
if ((Boolean) response[2]) {
// Extract the return values from the event and return them
return MethodResult.of(Arrays.copyOfRange(response, 3, response.length));
} else if (response.length >= 4 && response[3] instanceof String) {
// Extract the error message from the event and raise it
throw new LuaException((String) response[3]);
if (isOk) {
return MethodResult.of(result);
} else if (failure != null) {
throw failure;
} else if (response.length >= 4 && response[3] instanceof String message) {
throw new LuaException(message);
} else {
throw new LuaException("error");
}
}
static MethodResult make(ILuaContext context, LuaTask func) throws LuaException {
var task = context.issueMainThreadTask(func);
return new TaskCallback(task).pull;
return new TaskCallback(context, func).pull;
}
}

View File

@ -32,6 +32,11 @@ public void testMainThreadPeripheral() {
50);
}
@Test
public void testMainThreadReturnObject() {
ComputerBootstrap.run("assert(type(main_thread.complex().go) == \"function\")", x -> x.addApi(new MainThread()), 50);
}
@Test
public void testDynamic() {
ComputerBootstrap.run(
@ -98,6 +103,11 @@ public final int go() {
return 123;
}
@LuaFunction(mainThread = true)
public final Object complex() {
return this;
}
@Override
public String getType() {
return "main_thread";