From b6be45a76133af80e030c78ad45235376df6fe8e Mon Sep 17 00:00:00 2001 From: Lance Ewing Date: Sat, 13 Apr 2024 10:11:19 +0100 Subject: [PATCH] Changed HTML5/GWT platform to use web worker requestAnimationFrame to trigger AGI animation ticks, rather than relying on speed of postMessage calls. --- .../com/agifans/agile/gwt/GwtAgileRunner.java | 27 +-------- .../agifans/agile/worker/AgileWebWorker.java | 56 +++++++++++++++---- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/html/src/main/java/com/agifans/agile/gwt/GwtAgileRunner.java b/html/src/main/java/com/agifans/agile/gwt/GwtAgileRunner.java index 10fff31..04f0558 100644 --- a/html/src/main/java/com/agifans/agile/gwt/GwtAgileRunner.java +++ b/html/src/main/java/com/agifans/agile/gwt/GwtAgileRunner.java @@ -33,15 +33,6 @@ public class GwtAgileRunner extends AgileRunner { */ private Worker worker; - /** - * Indicates that the worker is currently executing the tick, i.e. a single interpretation - * cycle. This flag exists because there are some AGI commands that wait for something to - * happen before continuing. For example, a print window will stay up for a defined timeout - * period or until a key is pressed. In such cases, the thread can be performing a tick - * for the duration of what would normally be many ticks. - */ - private boolean inTick; - /** * Holds a reference to the Audio HTML element that is playing the current sound, or null * if there is no sound being played. It is not possible in AGI to play two sounds at @@ -102,13 +93,6 @@ public void onMessage(MessageEvent event) { JavaScriptObject eventObject = event.getDataAsObject(); switch (getEventType(eventObject)) { - case "TickComplete": - // Allows the next tick to be triggered. We only allow one tick at - // a time, otherwise the web worker would get a flood of Tick messages - // when it is busy waiting for a key or similar. - inTick = false; - break; - case "QuitGame": // This message is sent from the worker when the game has ended, usually // due to the user quitting the game. @@ -247,13 +231,9 @@ private void stopCurrentSound() { @Override public void animationTick() { - if (!inTick && (worker != null)) { - inTick = true; // NOTE: Set to false by "TickComplete" message. - - // Send a message to the web worker to tell it to perform an animation tick, - // but only if it isn't already in an animation tick. - worker.postObject("Tick", JavaScriptObject.createObject()); - } + // Nothing to do for GWT version. The web worker triggers itself for animation + // ticks, using the total ticks that is set by UI thread to determine when to + // run. } @Override @@ -264,7 +244,6 @@ public void stop() { stopCurrentSound(); pixelData.clearState(); variableData.clearState(); - inTick = false; stopped = true; } diff --git a/html/src/main/java/com/agifans/agile/worker/AgileWebWorker.java b/html/src/main/java/com/agifans/agile/worker/AgileWebWorker.java index 82969eb..0ce1f9d 100644 --- a/html/src/main/java/com/agifans/agile/worker/AgileWebWorker.java +++ b/html/src/main/java/com/agifans/agile/worker/AgileWebWorker.java @@ -78,6 +78,11 @@ public class AgileWebWorker extends DedicatedWorkerEntryPoint implements Message */ private OPFSGameFiles opfsGameFiles; + /** + * The total tick count at the time of the last animation tick. + */ + private int lastTotalTickCount; + /** * Incoming messages from the UI thread are for two purposes: One is to set things * up, and then once both sides are up and running, the UI thread then starts sending @@ -122,18 +127,8 @@ public void onMessage(MessageEvent event) { interpreter = new Interpreter( game, userInput, wavePlayer, savedGameStore, pixelData, variableData); - break; - - case "Tick": - try { - // Perform one animation tick. - interpreter.animationTick(); - // Then notify the UI thread that the tick is complete. - postObject("TickComplete", JavaScriptObject.createObject()); - } catch (QuitAction qa) { - // The user has quit the game, so notify the UI thread of this. - postObject("QuitGame", JavaScriptObject.createObject()); - } + lastTotalTickCount = variableData.getTotalTicks(); + performAnimationTick(0); break; default: @@ -141,6 +136,37 @@ public void onMessage(MessageEvent event) { } } + public void performAnimationTick(double timestamp) { + try { + int currentTotalTicks = variableData.getTotalTicks(); + int numOfTicksToRun = (currentTotalTicks - this.lastTotalTickCount); + this.lastTotalTickCount = currentTotalTicks; + + // Catch up with ticks, if we are behind. + while ((numOfTicksToRun-- > 0) && (variableData.getTotalTicks() == currentTotalTicks)) { + // Perform one animation tick. + interpreter.animationTick(); + } + + requestNextAnimationFrame(); + + } catch (QuitAction qa) { + // The user has quit the game, so notify the UI thread of this. + postObject("QuitGame", JavaScriptObject.createObject()); + } + } + + public native void exportPerformAnimationTick() /*-{ + var that = this; + $self.performAnimationTick = $entry(function(timestamp) { + that.@com.agifans.agile.worker.AgileWebWorker::performAnimationTick(D)(timestamp); + }); + }-*/; + + private native void requestNextAnimationFrame()/*-{ + $self.requestAnimationFrame($self.performAnimationTick); + }-*/; + private native String getEventType(JavaScriptObject obj)/*-{ return obj.name; }-*/; @@ -176,6 +202,8 @@ protected final void setOnMessage(MessageHandler messageHandler) { @Override public void onWorkerLoad() { + exportPerformAnimationTick(); + this.scope = DedicatedWorkerGlobalScope.get(); this.opfsGameFiles = new OPFSGameFiles(); @@ -183,4 +211,8 @@ public void onWorkerLoad() { this.setOnMessage(this); } + + private final native void logToJSConsole(String message)/*-{ + console.log(message); + }-*/; }