Skip to content

Commit

Permalink
Changed HTML5/GWT platform to use web worker requestAnimationFrame to…
Browse files Browse the repository at this point in the history
… trigger AGI animation ticks, rather than relying on speed of postMessage calls.
  • Loading branch information
lanceewing committed Apr 13, 2024
1 parent 5f3ba96 commit b6be45a
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 36 deletions.
27 changes: 3 additions & 24 deletions html/src/main/java/com/agifans/agile/gwt/GwtAgileRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -264,7 +244,6 @@ public void stop() {
stopCurrentSound();
pixelData.clearState();
variableData.clearState();
inTick = false;
stopped = true;
}

Expand Down
56 changes: 44 additions & 12 deletions html/src/main/java/com/agifans/agile/worker/AgileWebWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,25 +127,46 @@ 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:
// Unknown message. Ignore.
}
}

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) {
[email protected]::performAnimationTick(D)(timestamp);
});
}-*/;

private native void requestNextAnimationFrame()/*-{
$self.requestAnimationFrame($self.performAnimationTick);
}-*/;

private native String getEventType(JavaScriptObject obj)/*-{
return obj.name;
}-*/;
Expand Down Expand Up @@ -176,11 +202,17 @@ protected final void setOnMessage(MessageHandler messageHandler) {

@Override
public void onWorkerLoad() {
exportPerformAnimationTick();

this.scope = DedicatedWorkerGlobalScope.get();
this.opfsGameFiles = new OPFSGameFiles();

this.importScript("/opfs-saved-games.js");

this.setOnMessage(this);
}

private final native void logToJSConsole(String message)/*-{
console.log(message);
}-*/;
}

0 comments on commit b6be45a

Please sign in to comment.