Initial draft of coroutines library.

This commit is contained in:
James Roseborough
2007-10-20 00:51:15 +00:00
parent 8e2ff119f9
commit 78eaaf0fa0
8 changed files with 233 additions and 4 deletions

View File

@@ -0,0 +1,95 @@
package lua.addon.luacompat;
import lua.GlobalState;
import lua.VM;
import lua.io.Closure;
import lua.value.LFunction;
import lua.value.LTable;
import lua.value.LThread;
public class CoroutinesLib extends LFunction {
public static void install() {
LTable lib = new LTable(0,6);
lib.put("create", new CoroutinesLib(1));
lib.put("resume", new CoroutinesLib(2));
lib.put("running", new CoroutinesLib(3));
lib.put("status", new CoroutinesLib(4));
lib.put("wrap", new CoroutinesLib(5));
lib.put("yield", new CoroutinesLib(6));
GlobalState.getGlobalsTable().put("coroutine",lib);
}
private int id = 0;
private static LThread running;
public CoroutinesLib() {
this(0);
}
private CoroutinesLib( int id ) {
this.id = id;
}
public boolean luaStackCall( VM vm ) {
switch ( id ) {
case 0: { // load lib
install();
vm.pushnil();
break;
}
case 1: { // create
Closure c = (Closure) vm.topointer(2);
vm.pushlvalue( new LThread(c) );
break;
}
case 2: {// resume
LThread t = (LThread) vm.topointer(2);
LThread prior = running;
try {
// whatever is left on the stack by the resumeFrom() implementation
// becomes return values!
running = t;
t.resumeFrom( vm, prior );
return false;
} finally {
running = prior;
}
}
case 3: { // running
if ( running != null ) {
vm.pushlvalue( running );
} else {
vm.pushnil();
}
break;
}
case 4: { // status
vm.pushstring( ((LThread) vm.topointer(2)).getStatus() );
break;
}
case 5: { // wrap
vm.error( "wrap() not supported" );
return false;
}
case 6: { // yield
if ( running == null )
vm.error("main thread can't yield");
else {
return running.yield();
}
}
case 7: { // wrapped resume
vm.error( "wrap() not supported" );
return false;
}
}
vm.insert(1);
vm.settop(1);
return false;
}
}

View File

@@ -55,6 +55,9 @@ public class LuaCompat extends LFunction {
LTable table = new LTable(); LTable table = new LTable();
installNames( pckg, TABLE_NAMES, TABLES_BASE ); installNames( pckg, TABLE_NAMES, TABLES_BASE );
globals.put( "table", pckg ); globals.put( "table", pckg );
// coroutines
CoroutinesLib.install();
} }
private static void installNames( LTable table, String[] names, int indexBase ) { private static void installNames( LTable table, String[] names, int indexBase ) {

View File

@@ -1382,7 +1382,7 @@ public interface VM {
* This function pops <code>n</code> values from the stack * This function pops <code>n</code> values from the stack
* <code>from</code>, and pushes them onto the stack <code>to</code>. * <code>from</code>, and pushes them onto the stack <code>to</code>.
*/ */
public void xmove(StackState to, int n); public void xmove(VM to, int n);
/** /**
* Yields a coroutine. <span class="apii">[-?, +?, <em>-</em>]</span> * Yields a coroutine. <span class="apii">[-?, +?, <em>-</em>]</span>

View File

@@ -3,20 +3,35 @@ package lua.io;
import lua.StackState; import lua.StackState;
import lua.VM; import lua.VM;
import lua.value.LFunction; import lua.value.LFunction;
import lua.value.LValue; import lua.value.LTable;
public class Closure extends LFunction { public class Closure extends LFunction {
public LValue env; public LTable env;
public Proto p; public Proto p;
public UpVal[] upVals; public UpVal[] upVals;
// TODO: change arg type to VM? /**
* @deprecated construct with environment instead
* @param state
* @param p
*/
public Closure(StackState state, Proto p) { public Closure(StackState state, Proto p) {
this.env = state._G; this.env = state._G;
this.p = p; this.p = p;
upVals = new UpVal[p.nups]; upVals = new UpVal[p.nups];
} }
/**
* Construct using a prototype and initial environment.
* @param p
* @param env
*/
public Closure(Proto p, LTable env) {
this.p = p;
this.env = env;
upVals = new UpVal[p.nups];
}
// called by vm when there is an OP_CALL // called by vm when there is an OP_CALL
// in this case, we are on the stack, // in this case, we are on the stack,
// and simply need to cue the VM to treat it as a stack call // and simply need to cue the VM to treat it as a stack call

View File

@@ -1,9 +1,33 @@
package lua.value; package lua.value;
import lua.Lua; import lua.Lua;
import lua.StackState;
import lua.VM;
import lua.io.Closure;
public class LThread extends LValue { public class LThread extends LValue {
private static final int STATUS_SUSPENDED = 1;
private static final int STATUS_NORMAL = 2;
private static final int STATUS_ACTIVE = 3;
private static final int STATUS_DEAD = 4;
private static final String[] NAMES = {
"suspended",
"normal",
"active",
"dead" };
private int status = STATUS_SUSPENDED;
private StackState threadVm;
public LThread(Closure c) {
// TODO: inherit globals!
threadVm = new StackState();
threadVm.pushlvalue(new Closure(c.p, threadVm._G));
}
public int luaGetType() { public int luaGetType() {
return Lua.LUA_TTHREAD; return Lua.LUA_TTHREAD;
} }
@@ -11,4 +35,74 @@ public class LThread extends LValue {
public String toJavaString() { public String toJavaString() {
return "thread: "+hashCode(); return "thread: "+hashCode();
} }
public String getStatus() {
return NAMES[status];
}
/** This needs to leave any values returned by yield in the corouting
* on the calling vm stack
* @param vm
* @param prior
*/
public void resumeFrom(VM vm, LThread prior) {
if ( status == STATUS_DEAD ) {
vm.settop(0);
vm.pushboolean(false);
vm.pushstring("cannot resume dead coroutine");
return;
}
// set prior thread to normal status while we are running
if ( prior != null )
prior.status = STATUS_NORMAL;
try {
// copy args in
if ( threadVm.cc < 0 ) {
vm.xmove(threadVm, vm.gettop() - 2);
threadVm.prepStackCall();
} else {
threadVm.settop(0);
vm.xmove(threadVm, vm.gettop() - 2);
}
// run this vm until it yields
status = STATUS_ACTIVE;
while ( threadVm.cc >= 0 && status == STATUS_ACTIVE )
threadVm.exec();
// copy return values from yielding stack state
vm.settop(0);
vm.pushboolean(true);
if ( threadVm.cc >= 0 ) {
threadVm.xmove(vm, threadVm.gettop() - 1);
} else {
threadVm.base = 0;
threadVm.xmove(vm, threadVm.gettop());
}
} catch ( Throwable t ) {
status = STATUS_DEAD;
vm.settop(0);
vm.pushboolean(false);
vm.pushstring("thread: "+t);
} finally {
if ( threadVm.cc < 0 )
status = STATUS_DEAD;
// reset prior thread status
if ( prior != null )
prior.status = STATUS_ACTIVE;
}
}
public boolean yield() {
if ( status == STATUS_ACTIVE )
status = STATUS_SUSPENDED;
return true;
}
} }

View File

@@ -60,6 +60,10 @@ public class LuaJTest extends TestCase {
runTest( "coercions" ); runTest( "coercions" );
} }
public void testCoroutines() throws IOException, InterruptedException {
runTest( "coroutines" );
}
public void testCompare() throws IOException, InterruptedException { public void testCompare() throws IOException, InterruptedException {
runTest( "compare" ); runTest( "compare" );
} }

View File

@@ -0,0 +1,18 @@
function foo (a)
print("foo", a)
return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
print("co-body", a, b)
local r = foo(a+1)
print("co-body", r)
local r, s = coroutine.yield(a+b, a-b)
print("co-body", r, s)
return b, "end"
end)
print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))

Binary file not shown.