net.sandius.rembulan.runtime.Coroutine Maven / Gradle / Ivy
/*
* Copyright 2016 Miroslav Janíček
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sandius.rembulan.runtime;
import net.sandius.rembulan.Ordering;
import net.sandius.rembulan.util.Check;
import net.sandius.rembulan.util.Cons;
/**
* A Lua coroutine.
*
* This class does not expose any public API; to manipulate {@code Coroutine} objects,
* use the following methods in {@link ExecutionContext}:
*
* - to create a new coroutine, use {@link ExecutionContext#newCoroutine(LuaFunction)};
* - to get coroutine status, use {@link ExecutionContext#getCoroutineStatus(Coroutine)};
* - to resume a coroutine, use {@link ExecutionContext#resume(Coroutine, Object[])};
* - to yield from a coroutine, use {@link ExecutionContext#yield(Object[])}.
*
*
* Note on equality: according to §3.4.4 of the Lua Reference Manual,
* coroutines {@code a} and {@code b} are expected to be equal if and only if they are
* the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares
* coroutines using {@link Object#equals(Object)}. Exercise caution when overriding
* {@code equals()}.
*/
public final class Coroutine {
// paused call stack: up-to-date only iff coroutine is not running
private Cons callStack;
private Status status;
Coroutine(Object body) {
this.callStack = new Cons<>(new ResumeInfo(BootstrapResumable.INSTANCE, body));
this.status = Status.SUSPENDED;
}
/**
* Coroutine status.
*/
public enum Status {
/**
* The status of a suspended coroutine, i.e., a coroutine that may be resumed.
*/
SUSPENDED,
/**
* The status of a running coroutine, i.e., a coroutine that is currently executing.
*/
RUNNING,
/**
* The status of a coroutine that is resuming another coroutine.
*/
NORMAL,
/**
* The status of a dead coroutine, i.e., a coroutine that has finished execution.
*/
DEAD
}
synchronized Status getStatus() {
return status;
}
private static class BootstrapResumable implements Resumable {
static final BootstrapResumable INSTANCE = new BootstrapResumable();
@Override
public void resume(ExecutionContext context, Object target) throws ResolvedControlThrowable {
try {
Dispatch.call(context, target, context.getReturnBuffer().getAsArray());
}
catch (UnresolvedControlThrowable ct) {
throw ct.resolve();
}
}
}
// (RUNNING, SUSPENDED) -> (NORMAL, RUNNING)
static Cons _resume(final Coroutine a, final Coroutine b, Cons cs) {
Check.notNull(a);
Check.notNull(b);
Check.notNull(cs);
synchronized (a) {
if (a.status == Status.RUNNING) {
synchronized (b) {
if (b.status == Status.SUSPENDED) {
Cons result = b.callStack;
a.callStack = cs;
b.callStack = null;
a.status = Status.NORMAL;
b.status = Status.RUNNING;
return result;
}
else {
if (b.status == Status.DEAD) {
throw Errors.resumeDeadCoroutine();
}
else {
throw Errors.resumeNonSuspendedCoroutine();
}
}
}
}
else {
throw new IllegalStateException("resuming coroutine not in running state");
}
}
}
// (NORMAL, RUNNING) -> (RUNNING, SUSPENDED)
static Cons _yield(final Coroutine a, final Coroutine b, Cons cs) {
synchronized (a) {
if (a.status == Status.NORMAL) {
synchronized (b) {
if (b.status == Status.RUNNING) {
Cons result = a.callStack;
a.callStack = null;
b.callStack = cs;
a.status = Status.RUNNING;
b.status = b.callStack != null ? Status.SUSPENDED : Status.DEAD;
return result;
}
else {
throw new IllegalCoroutineStateException("yielding coroutine not in running state");
}
}
}
else {
throw new IllegalCoroutineStateException("yielding coroutine not in normal state");
}
}
}
// (NORMAL, RUNNING) -> (RUNNING, DEAD)
static Cons _return(Coroutine a, Coroutine b) {
return _yield(a, b, null);
}
synchronized Cons unpause() {
// TODO: check status?
status = Status.RUNNING;
Cons result = callStack;
callStack = null;
return result;
}
synchronized void pause(Cons callStack) {
// TODO: check status?
this.callStack = callStack;
}
}