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:
2026-09-16 00:24:35 -04:00
parent 4d66cc73db
commit 54adf8d326
@@ -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); private VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval,
thread.setDaemon(true); VsmxBaseObject... parameters) {
timers.put(id, thread); int id = currentTimerId++;
thread.start(); // 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();
} }
} }
} }