Fix Timer units and make setInterval repeat
The menu script passes intervals in milliseconds, but the Timer treated them as seconds, so wait_then_call(1100, open_mm) waited 1100 seconds and the menu never opened. setInterval also only fired once instead of repeating, so the button 'arrive' animation never advanced. Both are fixed, with a single scheduler thread serializing callbacks.
This commit is contained in:
@@ -11,17 +11,41 @@ import dev.quoije.umd.rco.vsmx.interpreter.VsmxNumber;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/** The Timer native object: setTimeout-style intervals for menu animations. */
|
/** The Timer native object: recurring setInterval callbacks for menu animations. */
|
||||||
public class Timer extends BaseNativeObject {
|
public class Timer extends BaseNativeObject {
|
||||||
|
|
||||||
private final VsmxInterpreter interpreter;
|
private final VsmxInterpreter interpreter;
|
||||||
|
private final Object callbackLock = new Object();
|
||||||
|
private final Map<Integer, Task> timers = new HashMap<>();
|
||||||
private int currentTimerId;
|
private int currentTimerId;
|
||||||
private final Map<Integer, Thread> timers = new HashMap<>();
|
|
||||||
|
/** One recurring timer task. */
|
||||||
|
private static final class Task {
|
||||||
|
final VsmxBaseObject object;
|
||||||
|
final VsmxBaseObject function;
|
||||||
|
final VsmxBaseObject[] params;
|
||||||
|
final long intervalNanos;
|
||||||
|
long nextRunNanos;
|
||||||
|
boolean active = true;
|
||||||
|
|
||||||
|
Task(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject[] params, long intervalNanos, long nextRunNanos) {
|
||||||
|
this.object = object;
|
||||||
|
this.function = function;
|
||||||
|
this.params = params;
|
||||||
|
this.intervalNanos = intervalNanos;
|
||||||
|
this.nextRunNanos = nextRunNanos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static VsmxNativeObject create(VsmxInterpreter interpreter) {
|
public static VsmxNativeObject create(VsmxInterpreter interpreter) {
|
||||||
Timer timer = new Timer(interpreter);
|
Timer timer = new Timer(interpreter);
|
||||||
VsmxNativeObject object = new VsmxNativeObject(interpreter, timer);
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, timer);
|
||||||
timer.setObject(object);
|
timer.setObject(object);
|
||||||
|
|
||||||
|
Thread scheduler = new Thread(timer::run, "umd-timer");
|
||||||
|
scheduler.setDaemon(true);
|
||||||
|
scheduler.start();
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,24 +53,52 @@ public class Timer extends BaseNativeObject {
|
|||||||
this.interpreter = interpreter;
|
this.interpreter = interpreter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval,
|
/** Single scheduler thread: fires due timers, serialized so the interpreter is never re-entered. */
|
||||||
VsmxBaseObject... parameters) {
|
private void run() {
|
||||||
int id = currentTimerId++;
|
while (true) {
|
||||||
long millis = interval.getIntValue() * 1000L;
|
Task due = null;
|
||||||
VsmxBaseObject[] params = parameters;
|
long waitNanos = 50_000_000L; // 50ms default idle poll
|
||||||
Thread thread = new Thread(() -> {
|
synchronized (timers) {
|
||||||
|
long now = System.nanoTime();
|
||||||
|
for (Task task : timers.values()) {
|
||||||
|
if (!task.active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (task.nextRunNanos <= now) {
|
||||||
|
task.nextRunNanos = now + task.intervalNanos;
|
||||||
|
due = task;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waitNanos = java.lang.Math.min(waitNanos, task.nextRunNanos - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (due != null) {
|
||||||
|
if (due.function instanceof VsmxFunction f) {
|
||||||
|
synchronized (callbackLock) {
|
||||||
|
interpreter.interpretFunction(f, due.object, due.params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(millis);
|
Thread.sleep(waitNanos / 1_000_000L, (int) (waitNanos % 1_000_000L));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (function instanceof VsmxFunction f) {
|
|
||||||
interpreter.interpretFunction(f, object, params);
|
|
||||||
}
|
}
|
||||||
}, "umd-timer-" + id);
|
}
|
||||||
thread.setDaemon(true);
|
|
||||||
timers.put(id, thread);
|
private VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval,
|
||||||
thread.start();
|
VsmxBaseObject... parameters) {
|
||||||
|
int id = currentTimerId++;
|
||||||
|
// The interval is expressed in milliseconds by the menu script.
|
||||||
|
long intervalNanos = java.lang.Math.max(1L, interval.getIntValue()) * 1_000_000L;
|
||||||
|
Task task = new Task(object, function, parameters, intervalNanos, System.nanoTime() + intervalNanos);
|
||||||
|
synchronized (timers) {
|
||||||
|
timers.put(id, task);
|
||||||
|
}
|
||||||
|
|
||||||
VsmxArray result = new VsmxArray(interpreter, 1);
|
VsmxArray result = new VsmxArray(interpreter, 1);
|
||||||
result.setPropertyValue(0, new VsmxNumber(interpreter, id));
|
result.setPropertyValue(0, new VsmxNumber(interpreter, id));
|
||||||
@@ -71,9 +123,8 @@ public class Timer extends BaseNativeObject {
|
|||||||
|
|
||||||
public void clearInterval(VsmxBaseObject object, VsmxBaseObject id) {
|
public void clearInterval(VsmxBaseObject object, VsmxBaseObject id) {
|
||||||
int idValue = id.getPropertyValue(0).getIntValue();
|
int idValue = id.getPropertyValue(0).getIntValue();
|
||||||
Thread thread = timers.remove(idValue);
|
synchronized (timers) {
|
||||||
if (thread != null) {
|
timers.remove(idValue);
|
||||||
thread.interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user