Fixed issue: #71

This commit is contained in:
UnlegitDqrk
2026-03-02 14:22:54 +01:00
parent 9cb10a390f
commit 39ff4f204d
90 changed files with 324 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,31 @@
import org.luaj.vm2.Globals;
import org.luaj.vm2.libs.jse.JsePlatform;
import org.luaj.vm2.libs.jse.LuaScheduler;
public class SampleLuaScheduler {
public static void main(String[] args) throws Exception {
Globals globals = JsePlatform.standardGlobals();
LuaScheduler scheduler = new LuaScheduler(globals);
globals.load(
"spawn(function()\n" +
" print('task1 start')\n" +
" wait(250)\n" +
" print('task1 after wait')\n" +
"end)\n" +
"spawn(function()\n" +
" print('task2 start')\n" +
" yield()\n" +
" print('task2 next tick')\n" +
"end)\n",
"sample_scheduler.lua"
).call();
while (scheduler.hasPendingTasks()) {
if (scheduler.runReadyTasks() == 0) {
Thread.sleep(scheduler.millisUntilNextTask());
}
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,224 @@
package org.luaj.vm2.libs.jse;
import java.util.Comparator;
import java.util.PriorityQueue;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaError;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaThread;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.libs.VarArgFunction;
/**
* Cooperative host-side scheduler for Lua tasks that use explicit wait/yield calls.
* <p>
* This does not change luaj's coroutine implementation. It provides a small host
* API for scheduling resumptions and timer-driven waits.
*/
public class LuaScheduler {
public interface Clock {
long nowMillis();
}
private static final Clock SYSTEM_CLOCK = new Clock() {
public long nowMillis() {
return System.currentTimeMillis();
}
};
private static final LuaString WAIT_SIGNAL = LuaValue.valueOf("__luaj_scheduler_wait");
private static final LuaString YIELD_SIGNAL = LuaValue.valueOf("__luaj_scheduler_yield");
private final Globals globals;
private final Clock clock;
private final PriorityQueue<ScheduledTask> queue = new PriorityQueue<ScheduledTask>(11, new Comparator<ScheduledTask>() {
public int compare(ScheduledTask a, ScheduledTask b) {
if (a.wakeAtMillis != b.wakeAtMillis) {
return a.wakeAtMillis < b.wakeAtMillis ? -1 : 1;
}
if (a.eligiblePass != b.eligiblePass) {
return a.eligiblePass < b.eligiblePass ? -1 : 1;
}
if (a.sequence == b.sequence) {
return 0;
}
return a.sequence < b.sequence ? -1 : 1;
}
});
private long sequence;
private long pass;
public LuaScheduler(Globals globals) {
this(globals, SYSTEM_CLOCK);
}
public LuaScheduler(Globals globals, Clock clock) {
this.globals = globals;
this.clock = clock;
installIntoGlobals();
}
public Globals getGlobals() {
return globals;
}
public ScheduledTask spawn(String script, String chunkname) {
return spawn(globals.load(script, chunkname));
}
public ScheduledTask spawn(LuaValue function) {
return spawn(function, LuaValue.NONE);
}
public ScheduledTask spawn(LuaValue function, Varargs args) {
ScheduledTask task = new ScheduledTask(new LuaThread(globals, function.checkfunction()));
task.pendingArgs = args;
schedule(task, clock.nowMillis(), 0);
return task;
}
public boolean hasPendingTasks() {
return !queue.isEmpty();
}
public long millisUntilNextTask() {
ScheduledTask next = queue.peek();
if (next == null) {
return -1;
}
long delay = next.wakeAtMillis - clock.nowMillis();
return delay > 0 ? delay : 0;
}
public int runReadyTasks() {
long now = clock.nowMillis();
long currentPass = ++pass;
int ran = 0;
while (true) {
ScheduledTask task = queue.peek();
if (task == null || task.wakeAtMillis > now || task.eligiblePass > currentPass) {
return ran;
}
queue.poll();
if (task.done) {
continue;
}
resumeTask(task, now, currentPass);
ran++;
}
}
private void resumeTask(ScheduledTask task, long now, long currentPass) {
Varargs args = task.pendingArgs;
task.pendingArgs = LuaValue.NONE;
Varargs result = task.thread.resume(args);
if (!result.arg1().toboolean()) {
task.done = true;
task.failure = new LuaError(result.arg(2).tojstring());
return;
}
LuaValue signal = result.arg(2);
if (signal.raweq(WAIT_SIGNAL)) {
long delay = result.arg(3).optlong(0);
schedule(task, now + Math.max(0, delay), currentPass + 1);
return;
}
if (signal.raweq(YIELD_SIGNAL)) {
task.lastYield = result.subargs(3);
schedule(task, now, currentPass + 1);
return;
}
if ("dead".equals(task.thread.getStatus())) {
task.done = true;
task.result = result.subargs(2);
return;
}
task.lastYield = result.subargs(2);
schedule(task, now, currentPass + 1);
}
private void schedule(ScheduledTask task, long wakeAtMillis, long eligiblePass) {
task.wakeAtMillis = wakeAtMillis;
task.eligiblePass = eligiblePass;
task.sequence = sequence++;
queue.add(task);
}
private void installIntoGlobals() {
globals.set("wait", new WaitFunction());
globals.set("yield", new YieldFunction());
globals.set("spawn", new SpawnFunction());
}
private final class WaitFunction extends VarArgFunction {
public Varargs invoke(Varargs args) {
long delay = args.arg(1).optlong(0);
return globals.yield(LuaValue.varargsOf(WAIT_SIGNAL, LuaValue.valueOf(Math.max(0, delay))));
}
}
private final class YieldFunction extends VarArgFunction {
public Varargs invoke(Varargs args) {
return globals.yield(LuaValue.varargsOf(YIELD_SIGNAL, args));
}
}
private final class SpawnFunction extends VarArgFunction {
public Varargs invoke(Varargs args) {
ScheduledTask task = spawn(args.checkfunction(1), args.subargs(2));
return task.thread;
}
}
public static final class ScheduledTask {
private final LuaThread thread;
private Varargs pendingArgs = LuaValue.NONE;
private Varargs result = LuaValue.NONE;
private Varargs lastYield = LuaValue.NONE;
private LuaError failure;
private boolean done;
private long wakeAtMillis;
private long eligiblePass;
private long sequence;
private ScheduledTask(LuaThread thread) {
this.thread = thread;
}
public LuaThread getThread() {
return thread;
}
public boolean isDone() {
return done;
}
public boolean isFailed() {
return failure != null;
}
public LuaError getFailure() {
return failure;
}
public Varargs getResult() {
return result;
}
public Varargs getLastYield() {
return lastYield;
}
public long getWakeAtMillis() {
return wakeAtMillis;
}
}
}

View File

@@ -0,0 +1,69 @@
package org.luaj.vm2.libs.jse;
import junit.framework.TestCase;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
public class LuaSchedulerTest extends TestCase {
public void testWaitAndYieldRunCooperatively() {
TestClock clock = new TestClock();
Globals globals = JsePlatform.standardGlobals();
LuaScheduler scheduler = new LuaScheduler(globals, clock);
globals.load(
"hits = {}\n" +
"spawn(function()\n" +
" table.insert(hits, 'a')\n" +
" wait(10)\n" +
" table.insert(hits, 'b')\n" +
" yield('pause')\n" +
" table.insert(hits, 'c')\n" +
"end)\n",
"scheduler.lua"
).call();
assertEquals(1, scheduler.runReadyTasks());
LuaTable hits = globals.get("hits").checktable();
assertEquals("a", hits.get(1).tojstring());
assertTrue(hits.get(2).isnil());
clock.now = 9;
assertEquals(0, scheduler.runReadyTasks());
assertTrue(hits.get(2).isnil());
clock.now = 10;
assertEquals(1, scheduler.runReadyTasks());
assertEquals("b", hits.get(2).tojstring());
assertTrue(hits.get(3).isnil());
assertEquals(1, scheduler.runReadyTasks());
assertEquals("c", hits.get(3).tojstring());
assertFalse(scheduler.hasPendingTasks());
}
public void testSpawnReturnsThreadHandle() {
TestClock clock = new TestClock();
Globals globals = JsePlatform.standardGlobals();
LuaScheduler scheduler = new LuaScheduler(globals, clock);
LuaValue thread = globals.load(
"return spawn(function() wait(1) end)\n",
"spawn.lua"
).call();
assertTrue(thread.isthread());
assertEquals(1, scheduler.runReadyTasks());
assertTrue(scheduler.hasPendingTasks());
}
static final class TestClock implements LuaScheduler.Clock {
long now;
public long nowMillis() {
return now;
}
}
}