Update #52 - Fixed various issues with the client

This commit is contained in:
lax1dude
2025-06-15 21:43:43 -07:00
parent 325a6826bf
commit f3281c037f
94 changed files with 882 additions and 506 deletions

View File

@ -24,6 +24,7 @@ import java.util.Date;
import org.teavm.interop.Import;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.browser.Storage;
@ -43,6 +44,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.buffer.MemoryStack;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.ClientMain;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.TeaVMUtils;
public class PlatformApplication {
@ -174,7 +176,23 @@ public class PlatformApplication {
}
}
public static final void displayFileChooser(String mime, String ext) {
public static void setResetSettingsCallbackWASM() {
setResetSettingsCallbackWASM0().call(ClientMain::resetSettings);
}
@JSFunctor
private static interface JSWASMResetSettingsCallback extends JSObject {
void callback();
}
private static interface JSWASMResetSettingsCallbackInterface extends JSObject {
void call(JSWASMResetSettingsCallback callback);
}
@Import(module = "platformApplication", name = "setResetSettingsCallback")
private static native JSWASMResetSettingsCallbackInterface setResetSettingsCallbackWASM0();
public static void displayFileChooser(String mime, String ext) {
displayFileChooser0(BetterJSStringConverter.stringToJS(mime), BetterJSStringConverter.stringToJS(ext));
}

View File

@ -94,11 +94,26 @@ public class PlatformAudio {
logger.error("OGG file support detected as false! Using embedded JOrbis OGG decoder");
}
}
if(((WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isKeepAliveHackTeaVM()) {
byte[] silenceFile = PlatformAssets.getResourceBytes("/assets/eagler/silence_loop.wav");
if (silenceFile != null) {
MemoryStack.push();
try {
initKeepAliveHack(WASMGCDirectArrayConverter.byteArrayToStackU8Array(silenceFile));
}finally {
MemoryStack.pop();
}
}
}
}
@Import(module = "platformAudio", name = "getContext")
private static native AudioContext getContext();
@Import(module = "platformAudio", name = "initKeepAliveHack")
private static native void initKeepAliveHack(Uint8Array array);
protected static class BrowserAudioResource implements IAudioResource {
protected AudioBuffer buffer;
@ -356,11 +371,8 @@ public class PlatformAudio {
return audioctx != null;
}
@JSBody(params = { "node" }, script = "node.distanceModel = \"linear\";")
static native void setDistanceModelLinearFast(PannerNode node) ;
@JSBody(params = { "node" }, script = "node.panningModel = \"HRTF\";")
static native void setPanningModelHRTFFast(PannerNode node) ;
@Import(module = "platformAudio", name = "setupPanner")
static native void setupPanner(PannerNode node, float maxDist, float x, float y, float z);
public static IAudioHandle beginPlayback(IAudioResource track, float x, float y, float z,
float volume, float pitch, boolean repeat) {
@ -373,17 +385,10 @@ public class PlatformAudio {
src.setLoop(repeat);
PannerNode panner = audioctx.createPanner();
panner.setPosition(x, y, z);
float v1 = volume * 16.0f;
if(v1 < 16.0f) v1 = 16.0f;
panner.setMaxDistance(v1);
panner.setRolloffFactor(1.0f);
setDistanceModelLinearFast(panner);
setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360.0f);
panner.setConeOuterAngle(0.0f);
panner.setConeOuterGain(0.0f);
panner.setOrientation(0.0f, 1.0f, 0.0f);
setupPanner(panner, v1, x, y, z);
GainNode gain = audioctx.createGain();
float v2 = volume;
@ -396,7 +401,7 @@ public class PlatformAudio {
if(gameRecGain != null) {
gain.connect(gameRecGain);
}
src.start();
BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, panner, gain, pitch, repeat);

View File

@ -113,6 +113,7 @@ public class PlatformInput {
static boolean vsync = true;
static boolean vsyncSupport = false;
static boolean finish = true;
private static Map<String, LegacyKeycodeTranslator.LegacyKeycode> keyCodeTranslatorMap = null;
@ -125,6 +126,7 @@ public class PlatformInput {
fullscreenSupported = supportsFullscreen0();
vsyncSupport = isVSyncSupported0();
WASMGCClientConfigAdapter conf = (WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter();
finish = conf.isFinishOnSwapTeaVM();
useVisualViewport = conf.isUseVisualViewportTeaVM() && isVisualViewport0();
lastWasResizedWindowWidth = -2;
lastWasResizedWindowHeight = -2;
@ -656,7 +658,7 @@ public class PlatformInput {
PlatformScreenRecord.captureFrameHook();
}
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport);// && vsync
updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport, finish);
PlatformRuntime.pollJSEventsAfterSleep();
}
@ -664,7 +666,7 @@ public class PlatformInput {
private static native void updateCanvasSize(int width, int height);
@Import(module = "platformInput", name = "updatePlatformAndSleep")
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync);
private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync, boolean finish);
public static boolean isVSyncSupported() {
return vsyncSupport;

View File

@ -79,6 +79,7 @@ public class PlatformRuntime {
canvas = getCanvasElement();
printMemoryStackAddrWASMGC();
PlatformApplication.setMCServerWindowGlobal(null);
PlatformApplication.setResetSettingsCallbackWASM();
PlatformOpenGL.initContext();
PlatformInput.initContext(win, parent, canvas);

View File

@ -215,16 +215,8 @@ public class PlatformVoiceClient {
audioNode.disconnect(gain);
}
panner = PlatformAudio.audioctx.createPanner();
panner.setRolloffFactor(1f);
PlatformAudio.setDistanceModelLinearFast(panner);
PlatformAudio.setPanningModelHRTFFast(panner);
panner.setConeInnerAngle(360f);
panner.setConeOuterAngle(0f);
panner.setConeOuterGain(0f);
panner.setOrientation(0f, 1f, 0f);
panner.setPosition(0, 0, 0);
float vol = VoiceClientController.getVoiceListenVolume();
panner.setMaxDistance(vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f);
PlatformAudio.setupPanner(panner, vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f, 0, 0, 0);
gain = PlatformAudio.audioctx.createGain();
gain.getGain().setValue(vol);
audioNode.connect(gain);

View File

@ -30,22 +30,26 @@ public class BetterJSStringConverter {
@Unmanaged
public static JSString stringToJS(String input) {
if (input == null) return null;
return (JSString) JS.wrap(input);
}
@Unmanaged
@SuppressWarnings("unchecked")
public static JSArray<JSString> stringArrayToJS(String[] input) {
if (input == null) return null;
return (JSArray<JSString>) JS.wrap(input);
}
@Unmanaged
public static String stringFromJS(JSString input) {
if (input == null) return null;
return JS.unwrapString(input);
}
@Unmanaged
public static String[] stringArrayFromJS(JSArray<JSString> input) {
if (input == null) return null;
return JS.unwrapStringArray(input);
}

View File

@ -24,6 +24,7 @@ import org.teavm.jso.browser.Window;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.ContextLostError;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRoot;
import net.minecraft.client.main.Main;
@ -98,6 +99,25 @@ public class ClientMain {
}
}
/**
* Defined here to match the JS runtime
*/
public static void resetSettings() {
boolean y = false;
if (Window.confirm("Do you want to reset client settings?")) {
PlatformApplication.setLocalStorage("g", null);
PlatformApplication.setLocalStorage("p", null);
y = true;
}
if (Window.confirm("Do you want to reset servers and relays?")) {
PlatformApplication.setLocalStorage("r", null);
PlatformApplication.setLocalStorage("s", null);
y = true;
}
if (y) {
Window.alert("Settings reset.");
}
}
@Import(module = "platformRuntime", name = "getEaglercraftXOpts")
private static native JSObject getEaglerXOpts();

View File

@ -16,6 +16,7 @@
package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@ -24,16 +25,17 @@ import com.jcraft.jzlib.CRC32;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.InflaterInputStream;
import net.lax1dude.eaglercraft.v1_8.IOUtils;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer;
import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerBufferInputStream;
public class EPKLoader {
public static final void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, Map<String, byte[]> loadedFiles) throws IOException {
loadEPK(epkFile, "", loadedFiles);
}
public static final void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
public static void loadEPK(ByteBuffer epkFile, String path, Map<String, byte[]> loadedFiles) throws IOException {
int byteLength = epkFile.remaining();
int l = byteLength - 16;
if(l < 1) {
@ -43,7 +45,7 @@ public class EPKLoader {
EaglerBufferInputStream is = new EaglerBufferInputStream(epkFile);
byte[] header = new byte[8];
is.read(header);
IOUtils.readFully(is, header);
String type = readASCII(header);
if(!"EAGPKG$$".equals(type)) {
@ -65,13 +67,13 @@ public class EPKLoader {
throw new IOException("Unknown or invalid EPK version: " + vers);
}
is.skip(is.read()); // skip filename
is.skip(loadShort(is)); // skip comment
is.skip(8); // skip millis date
IOUtils.skipFully(is, loadByte(is)); // skip filename
IOUtils.skipFully(is, loadShort(is)); // skip comment
IOUtils.skipFully(is, 8); // skip millis date
int numFiles = loadInt(is);
char compressionType = (char)is.read();
char compressionType = (char)loadByte(is);
InputStream zis;
switch(compressionType) {
@ -112,11 +114,11 @@ public class EPKLoader {
if(i == 0) {
if(blockType == blockHead) {
byte[] readType = new byte[len];
zis.read(readType);
IOUtils.readFully(zis, readType);
if(!"file-type".equals(name) || !"epk/resources".equals(readASCII(readType))) {
throw new IOException("EPK is not of file-type 'epk/resources'!");
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
continue;
@ -133,7 +135,7 @@ public class EPKLoader {
int expectedCRC = loadInt(zis);
byte[] load = new byte[len - 5];
zis.read(load);
IOUtils.readFully(zis, load);
if(len > 5) {
crc32.reset();
@ -143,16 +145,16 @@ public class EPKLoader {
}
}
if(zis.read() != ':') {
if(loadByte(zis) != ':') {
throw new IOException("File '" + name + "' is incomplete");
}
loadedFiles.put(path + name, load);
}else {
zis.skip(len);
IOUtils.skipFully(zis, len);
}
if(zis.read() != '>') {
if(loadByte(zis) != '>') {
throw new IOException("Object '" + name + "' is incomplete");
}
}
@ -163,28 +165,36 @@ public class EPKLoader {
zis.close();
}
private static final int loadShort(InputStream is) throws IOException {
return (is.read() << 8) | is.read();
private static int loadByte(InputStream is) throws IOException {
int i = is.read();
if (i < 0) {
throw new EOFException();
}
return i;
}
private static final int loadInt(InputStream is) throws IOException {
return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
private static int loadShort(InputStream is) throws IOException {
return (loadByte(is) << 8) | loadByte(is);
}
private static final String readASCII(byte[] bytesIn) throws IOException {
private static int loadInt(InputStream is) throws IOException {
return (loadByte(is) << 24) | (loadByte(is) << 16) | (loadByte(is) << 8) | loadByte(is);
}
private static String readASCII(byte[] bytesIn) throws IOException {
char[] charIn = new char[bytesIn.length];
for(int i = 0; i < bytesIn.length; ++i) {
charIn[i] = (char)((int)bytesIn[i] & 0xFF);
}
return new String(charIn);
}
private static final String readASCII(InputStream bytesIn) throws IOException {
int len = bytesIn.read();
private static String readASCII(InputStream bytesIn) throws IOException {
int len = loadByte(bytesIn);
char[] charIn = new char[len];
for(int i = 0; i < len; ++i) {
charIn[i] = (char)(bytesIn.read() & 0xFF);
charIn[i] = (char)loadByte(bytesIn);
}
return new String(charIn);
}

View File

@ -82,6 +82,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
private boolean ramdiskMode = false;
private boolean singleThreadMode = false;
private boolean enforceVSync = true;
private boolean keepAliveHack = true;
private boolean finishOnSwap = true;
public void loadNative(JSObject jsObject) {
JSEaglercraftXOptsRoot eaglercraftXOpts = (JSEaglercraftXOptsRoot)jsObject;
@ -124,6 +126,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
ramdiskMode = eaglercraftXOpts.getRamdiskMode(false);
singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false);
enforceVSync = eaglercraftXOpts.getEnforceVSync(true);
keepAliveHack = eaglercraftXOpts.getKeepAliveHack(true);
finishOnSwap = eaglercraftXOpts.getFinishOnSwap(true);
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
if(hooksObj != null) {
hooks.loadHooks(hooksObj);
@ -400,6 +404,14 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
return enforceVSync;
}
public boolean isKeepAliveHackTeaVM() {
return keepAliveHack;
}
public boolean isFinishOnSwapTeaVM() {
return finishOnSwap;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
@ -446,6 +458,8 @@ public class WASMGCClientConfigAdapter implements IClientConfigAdapter {
jsonObject.put("ramdiskMode", ramdiskMode);
jsonObject.put("singleThreadMode", singleThreadMode);
jsonObject.put("enforceVSync", enforceVSync);
jsonObject.put("keepAliveHack", keepAliveHack);
jsonObject.put("finishOnSwap", finishOnSwap);
JSONArray serversArr = new JSONArray();
for(int i = 0, l = defaultServers.size(); i < l; ++i) {
DefaultServer srv = defaultServers.get(i);

View File

@ -114,10 +114,14 @@ public class WASMGCClientConfigAdapterHooks implements IClientConfigAdapterHooks
}
public void loadHooks(JSEaglercraftXOptsHooks hooks) {
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
crashHook = (CrashReportHook)hooks.getCrashReportHook();
screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook();
JSObject obj = hooks.getLocalStorageSavedHook();
saveHook = obj != null ? (LocalStorageSaveHook) obj : null;
obj = hooks.getLocalStorageLoadedHook();
loadHook = obj != null ? (LocalStorageLoadHook) obj : null;
obj = hooks.getCrashReportHook();
crashHook = obj != null ? (CrashReportHook) obj : null;
obj = hooks.getScreenChangedHook();
screenChangedHook = obj != null ? (ScreenChangeHook) obj : null;
}
}

View File

@ -172,4 +172,10 @@ public interface JSEaglercraftXOptsRoot extends JSObject {
@JSBody(params = { "def" }, script = "return (typeof this.enableEPKVersionCheck === \"boolean\") ? this.enableEPKVersionCheck : def;")
boolean getEnableEPKVersionCheck(boolean deobfStackTraces);
@JSBody(params = { "def" }, script = "return (typeof this.keepAliveHack === \"boolean\") ? this.keepAliveHack : def;")
boolean getKeepAliveHack(boolean keepAliveHack);
@JSBody(params = { "def" }, script = "return (typeof this.finishOnSwap === \"boolean\") ? this.finishOnSwap : def;")
boolean getFinishOnSwap(boolean finishOnSwap);
}

View File

@ -100,6 +100,12 @@ var isCrashed = false;
const crashReportStrings = [];
/** @type {function()|null} */
var removeEventHandlers = null;
/** @type {function()|null} */
var keepAliveCallback = null;
/** @type {function()|null} */
var showDebugConsole = null;
/** @type {function()|null} */
var resetSettings = null;
const runtimeOpts = {
localStorageNamespace: "_eaglercraftX",
@ -530,6 +536,44 @@ function deobfuscateStack(stack) {
return stackFrames;
}
/**
* @return {HTMLElement}
*/
function createToolButtons() {
const buttonResetSettings = /** @type {HTMLButtonElement} */ (document.createElement("button"));
buttonResetSettings.setAttribute("style", "margin-left:10px;");
buttonResetSettings.innerText = "Reset Settings";
buttonResetSettings.addEventListener("click", function(/** Event */ evt) {
if (resetSettings) {
resetSettings();
} else {
window.alert("Local storage has not been initialized yet");
}
});
const buttonOpenConsole = /** @type {HTMLButtonElement} */ (document.createElement("button"));
buttonOpenConsole.setAttribute("style", "margin-left:10px;");
buttonOpenConsole.innerText = "Open Debug Console";
buttonOpenConsole.addEventListener("click", function(/** Event */ evt) {
if (showDebugConsole) {
showDebugConsole();
} else {
window.alert("Debug console has not been initialized yet");
}
});
const div1 = /** @type {HTMLElement} */ (document.createElement("div"));
div1.setAttribute("style", "position:absolute;bottom:5px;right:0px;");
div1.appendChild(buttonResetSettings);
div1.appendChild(buttonOpenConsole);
const div2 = /** @type {HTMLElement} */ (document.createElement("div"));
div2.setAttribute("style", "position:relative;");
div2.appendChild(div1);
const div3 = /** @type {HTMLElement} */ (document.createElement("div"));
div3.classList.add("_eaglercraftX_crash_tools_element");
div3.setAttribute("style", "z-index:101;position:absolute;top:135px;left:10%;right:10%;height:0px;");
div3.appendChild(div2);
return div3;
}
function displayUncaughtCrashReport(error) {
const stack = error ? deobfuscateStack(error.stack) : null;
const crashContent = "Native Browser Exception\n" +
@ -657,6 +701,7 @@ function displayCrashReport(crashReport, enablePrint) {
div.classList.add("_eaglercraftX_crash_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.appendChild(document.createTextNode(strFinal));
if(removeEventHandlers) removeEventHandlers();
@ -709,6 +754,7 @@ function showIncompatibleScreen(msg) {
div.classList.add("_eaglercraftX_incompatible_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.innerHTML = "<h2><svg style=\"vertical-align:middle;margin:0px 16px 8px 8px;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\"><path stroke=\"#000000\" stroke-width=\"3\" stroke-linecap=\"square\" d=\"M1.5 8.5v34h45v-28m-3-3h-10v-3m-3-3h-10m15 6h-18v-3m-3-3h-10\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M12 21h0m0 4h0m4 0h0m0-4h0m-2 2h0m20-2h0m0 4h0m4 0h0m0-4h0m-2 2h0\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M20 30h0 m2 2h0 m2 2h0 m2 2h0 m2 -2h0 m2 -2h0 m2 -2h0\"/></svg>+ This device is incompatible with Eaglercraft&ensp;:(</h2>"
+ "<div style=\"margin-left:40px;\">"
+ "<p style=\"font-size:1.2em;\"><b style=\"font-size:1.1em;\">Issue:</b> <span style=\"color:#BB0000;\" id=\"_eaglercraftX_crashReason\"></span><br /></p>"
@ -785,14 +831,17 @@ function showContextLostScreen(msg) {
div.classList.add("_eaglercraftX_context_lost_element");
parentEl.appendChild(img);
parentEl.appendChild(div);
parentEl.appendChild(createToolButtons());
div.innerHTML = "<h2><svg style=\"vertical-align:middle;margin:0px 16px 8px 8px;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\"><path stroke=\"#000000\" stroke-width=\"3\" stroke-linecap=\"square\" d=\"M1.5 8.5v34h45v-28m-3-3h-10v-3m-3-3h-10m15 6h-18v-3m-3-3h-10\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M12 21h0m0 4h0m4 0h0m0-4h0m-2 2h0m20-2h0m0 4h0m4 0h0m0-4h0m-2 2h0\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M20 30h0 m2 2h0 m2 2h0 m2 2h0 m2 -2h0 m2 -2h0 m2 -2h0\"/></svg> + WebGL context lost!</h2>"
+ "<div style=\"margin-left:40px;\">"
+ "<p style=\"font-size:1.2em;\">Your browser has forcibly released all of the resources "
+ "allocated by the game's 3D rendering context. EaglercraftX cannot continue, please refresh "
+ "the page to restart the game.</p>"
+ "the page to restart the game, sorry for the inconvenience.</p>"
+ "<p style=\"font-size:1.2em;\">This is not a bug, it is usually caused by the browser "
+ "deciding it no longer has sufficient resources to continue rendering this page. If it "
+ "happens again, try closing your other browser tabs and windows.</p>"
+ "<p style=\"font-size:1.2em;\">If you're playing with vsync disabled, try enabling vsync "
+ "to allow the browser to control the GPU usage more precisely.</p>"
+ "<p style=\"overflow-wrap:break-word;white-space:pre-wrap;font:0.75em monospace;margin-top:1.5em;\" id=\"_eaglercraftX_contextLostTrace\"></p>"
+ "</div>";

View File

@ -328,7 +328,7 @@ function initializePlatfApplication(applicationImports) {
/** @type {HTMLElement} */
var loggerMessageContainer = null;
/** @type {string} */
const loggerLocalStorageKey = runtimeOpts.localStorageNamespace + "showDebugConsole";
const loggerLocalStorageKey = runtimeOpts.localStorageNamespace + ".showDebugConsole";
/** @type {string} */
const loggerWinUnloadEvent = runtimeOpts.fixDebugConsoleUnloadListener ? "beforeunload" : "unload";
@ -365,7 +365,7 @@ function initializePlatfApplication(applicationImports) {
showDebugConsole0();
}
function showDebugConsole() {
showDebugConsole = function() {
debugConsoleLocalStorageSet(true);
showDebugConsole0();
}
@ -480,6 +480,17 @@ function initializePlatfApplication(applicationImports) {
return faviconURL;
};
/**
* @return {Object}
*/
applicationImports["setResetSettingsCallback"] = function() {
return {
"call": function(/** function() */ cb) {
resetSettings = cb;
}
};
};
}
/**
@ -499,4 +510,5 @@ function initializeNoPlatfApplication(applicationImports) {
setUnsupportedFunc(applicationImports, platfApplicationName, "addLogMessage");
setUnsupportedFunc(applicationImports, platfApplicationName, "isShowingDebugConsole");
setUnsupportedFunc(applicationImports, platfApplicationName, "getFaviconURL");
setUnsupportedFunc(applicationImports, platfApplicationName, "setResetSettingsCallback");
}

View File

@ -25,6 +25,47 @@ function setCurrentAudioContext(audioContext, audioImports) {
return audioContext;
};
/**
* @param {Uint8Array} fileData
*/
audioImports["initKeepAliveHack"] = function(fileData) {
const copiedData = new Uint8Array(fileData.length);
copiedData.set(fileData, 0);
const copiedDataURI = URL.createObjectURL(new Blob([copiedData], {type: "audio/wav"}));
const audioElement = /** @type {HTMLAudioElement} */ (document.createElement("audio"));
audioElement.classList.add("_eaglercraftX_keepalive_hack");
audioElement.setAttribute("style", "display:none;");
audioElement.autoplay = true;
audioElement.loop = true;
const sourceElement = /** @type {HTMLSourceElement} */ (document.createElement("source"));
sourceElement.type = "audio/wav";
sourceElement.src = copiedDataURI;
audioElement.appendChild(sourceElement);
audioElement.addEventListener("seeked", function() {
// NOP, wakes up the browser's event loop
});
parentElement.appendChild(audioElement);
};
/**
* @param {PannerNode} node
* @param {number} maxDist
* @param {number} x
* @param {number} y
* @param {number} z
*/
audioImports["setupPanner"] = function(node, maxDist, x, y, z) {
node.maxDistance = maxDist;
node.rolloffFactor = 1.0;
node.panningModel = "HRTF";
node.distanceModel = "linear";
node.coneInnerAngle = 360.0;
node.coneOuterAngle = 0.0;
node.coneOuterGain = 0.0;
node.setOrientation(0.0, 1.0, 0.0);
node.setPosition(x, y, z);
};
/**
* @param {AudioBufferSourceNode} sourceNode
* @param {Object} isEnded
@ -72,6 +113,8 @@ function setNoAudioContext(audioImports) {
audioImports["getContext"] = function() {
return null;
};
setUnsupportedFunc(audioImports, platfAudioName, "setupPanner");
setUnsupportedFunc(audioImports, platfAudioName, "initKeepAliveHack");
setUnsupportedFunc(audioImports, platfAudioName, "registerIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "releaseIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "decodeAudioBrowser");

View File

@ -617,15 +617,19 @@ async function initPlatformInput(inputImports) {
/**
* @param {number} fpsLimit
* @param {number} vsync
* @param {number} finish
* @return {Promise}
*/
function updatePlatformAndSleepImpl(fpsLimit, vsync) {
function updatePlatformAndSleepImpl(fpsLimit, vsync, finish) {
reportWindowSize();
if((typeof document.visibilityState !== "string") || (document.visibilityState === "visible")) {
if(vsyncSupport && vsync) {
manualSyncTimer = 0;
return asyncRequestAnimationFrame();
}else {
if(finish) {
webglContext.finish();
}
if(fpsLimit <= 0) {
manualSyncTimer = 0;
return swapDelayImpl();