partial vsync implementation, immediate continue hack

This commit is contained in:
lax1dude 2024-12-07 22:01:03 -08:00
parent 511e95a067
commit d862a08a4a
13 changed files with 506 additions and 80 deletions

View File

@ -59,15 +59,10 @@ public class EAGMinecraftServer extends MinecraftServer {
this.tick();
lastTick = SysUtil.steadyTimeMillis();
} else {
boolean mustYield = false;
while (delta >= 50L) {
if(mustYield) {
SysUtil.sleep(1); // allow some async
}
if (delta >= 50l) {
delta -= 50L;
lastTick = SysUtil.steadyTimeMillis();
lastTick += 50l;
this.tick();
mustYield = true;
}
}

View File

@ -749,6 +749,8 @@ public class IntegratedServer {
sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket01StopServer.ID));
currentProcess = null;
}
}else {
SysUtil.sleep(50);
}
}
@ -767,7 +769,7 @@ public class IntegratedServer {
mainLoop();
SysUtil.sleep(1); // allow some async to occur
SysUtil.immediateContinue();
}
// yee

View File

@ -0,0 +1,36 @@
package net.lax1dude.eaglercraft.sp;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.workers.MessagePort;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
@JSClass
public class MessageChannel implements JSObject {
@JSBody(params = { }, script = "return (typeof MessageChannel !== \"undefined\");")
public static native boolean supported();
@JSProperty
public native MessagePort getPort1();
@JSProperty
public native MessagePort getPort2();
}

View File

@ -4,6 +4,10 @@ import org.teavm.interop.Async;
import org.teavm.interop.AsyncCallback;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSString;
import org.teavm.jso.dom.events.EventListener;
import org.teavm.jso.dom.events.MessageEvent;
import org.teavm.platform.Platform;
import org.teavm.platform.PlatformRunnable;
@ -45,4 +49,110 @@ public class SysUtil {
}
}
private static boolean hasCheckedImmediateContinue = false;
private static MessageChannel immediateContinueChannel = null;
private static Runnable currentContinueHack = null;
private static final Object immediateContLock = new Object();
private static final JSString emptyJSString = JSString.valueOf("");
public static void immediateContinue() {
if(!hasCheckedImmediateContinue) {
hasCheckedImmediateContinue = true;
checkImmediateContinueSupport();
}
if(immediateContinueChannel != null) {
immediateContinueTeaVM();
}else {
sleep(0);
}
}
@Async
private static native void immediateContinueTeaVM();
private static void immediateContinueTeaVM(final AsyncCallback<Void> cb) {
synchronized(immediateContLock) {
if(currentContinueHack != null) {
cb.error(new IllegalStateException("Worker thread is already waiting for an immediate continue callback!"));
return;
}
currentContinueHack = () -> {
cb.complete(null);
};
try {
immediateContinueChannel.getPort2().postMessage(emptyJSString);
}catch(Throwable t) {
System.err.println("Caught error posting immediate continue, using setTimeout instead");
Window.setTimeout(() -> cb.complete(null), 0);
}
}
}
private static void checkImmediateContinueSupport() {
try {
immediateContinueChannel = null;
if(!MessageChannel.supported()) {
System.err.println("Fast immediate continue will be disabled for server context due to MessageChannel being unsupported");
return;
}
immediateContinueChannel = new MessageChannel();
immediateContinueChannel.getPort1().addEventListener("message", new EventListener<MessageEvent>() {
@Override
public void handleEvent(MessageEvent evt) {
Runnable toRun;
synchronized(immediateContLock) {
toRun = currentContinueHack;
currentContinueHack = null;
}
if(toRun != null) {
toRun.run();
}
}
});
immediateContinueChannel.getPort1().start();
immediateContinueChannel.getPort2().start();
final boolean[] checkMe = new boolean[1];
checkMe[0] = false;
currentContinueHack = () -> {
checkMe[0] = true;
};
immediateContinueChannel.getPort2().postMessage(emptyJSString);
if(checkMe[0]) {
currentContinueHack = null;
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
System.err.println("Fast immediate continue will be disabled for server context due to actually continuing immediately");
return;
}
sleep(10);
currentContinueHack = null;
if(!checkMe[0]) {
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
System.err.println("Fast immediate continue will be disabled for server context due to startup check failing");
}
}catch(Throwable t) {
System.err.println("Fast immediate continue will be disabled for server context due to exceptions");
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
}
}
private static void safeShutdownChannel(MessageChannel chan) {
try {
chan.getPort1().close();
}catch(Throwable tt) {
}
try {
chan.getPort2().close();
}catch(Throwable tt) {
}
}
}

View File

@ -81,8 +81,6 @@ import net.lax1dude.eaglercraft.RelayQuery.VersionMismatch;
import net.lax1dude.eaglercraft.RelayWorldsQuery;
import net.lax1dude.eaglercraft.ServerQuery;
import net.lax1dude.eaglercraft.Voice;
import net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2.ProgramGL;
import net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2.RateLimit;
import net.lax1dude.eaglercraft.adapter.lwjgl.GameWindowListener;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds.LocalWorld;
@ -971,12 +969,16 @@ public class EaglerAdapterImpl2 {
public static final boolean shouldShutdown() {
return Display.isCloseRequested();
}
public static final void updateDisplay() {
Display.update();
public static final void updateDisplay(int fpsLimit, boolean vsync) {
if(vsync) {
Display.setVSyncEnabled(true);
Display.update();
}else {
Display.setVSyncEnabled(false);
Display.sync(fpsLimit);
Display.update();
}
}
public static final void setVSyncEnabled(boolean p1) {
Display.setVSyncEnabled(p1);
}
public static final void enableRepeatEvents(boolean b) {
Keyboard.enableRepeatEvents(b);
}
@ -1005,17 +1007,14 @@ public class EaglerAdapterImpl2 {
e.printStackTrace();
}
}
public static final void syncDisplay(int performanceToFps) {
Display.sync(performanceToFps);
}
private static final Set<String> rateLimitedAddresses = new HashSet();
private static final Set<String> blockedAddresses = new HashSet();
private static final Set<String> rateLimitedAddresses = new HashSet<>();
private static final Set<String> blockedAddresses = new HashSet<>();
private static WebSocketClient clientSocket = null;
private static final Object socketSync = new Object();
private static LinkedList<byte[]> readPackets = new LinkedList();
private static LinkedList<byte[]> readPackets = new LinkedList<>();
private static class EaglerSocketClient extends WebSocketClient {
@ -1465,7 +1464,7 @@ public class EaglerAdapterImpl2 {
public static final float getVoiceSpeakVolume() {
return volumeSpeak;
}
private static final Set<String> emptySet = new HashSet();
private static final Set<String> emptySet = new HashSet<>();
public static final Set<String> getVoiceListening() {
return emptySet;
}
@ -1478,7 +1477,7 @@ public class EaglerAdapterImpl2 {
public static final Set<String> getVoiceMuted() {
return emptySet;
}
private static final List<String> emptyList = new ArrayList();
private static final List<String> emptyList = new ArrayList<>();
public static final List<String> getVoiceRecent() {
return emptyList;
}
@ -1546,8 +1545,8 @@ public class EaglerAdapterImpl2 {
private static class ServerQueryImpl extends WebSocketClient implements ServerQuery {
private final LinkedList<QueryResponse> queryResponses = new LinkedList();
private final LinkedList<byte[]> queryResponsesBytes = new LinkedList();
private final LinkedList<QueryResponse> queryResponses = new LinkedList<>();
private final LinkedList<byte[]> queryResponsesBytes = new LinkedList<>();
private final String type;
private boolean open;
private boolean alive;

View File

@ -99,7 +99,8 @@ public class EarlyLoadScreen {
_wglDrawArrays(_wGL_TRIANGLES, 0, 6);
_wglDisableVertexAttribArray(0);
_wglFlush();
updateDisplay();
updateDisplay(0, false);
sleep(20);
_wglUseProgram(null);
_wglBindBuffer(_wGL_ARRAY_BUFFER, null);
@ -156,7 +157,8 @@ public class EarlyLoadScreen {
_wglDrawArrays(_wGL_TRIANGLES, 0, 6);
_wglDisableVertexAttribArray(0);
_wglFlush();
updateDisplay();
updateDisplay(0, false);
sleep(20);
_wglUseProgram(null);
_wglBindBuffer(_wGL_ARRAY_BUFFER, null);

View File

@ -1613,4 +1613,33 @@ public class EaglerAdapterGL30 extends EaglerAdapterImpl2 {
return ret;
}
public static boolean sync(int limitFramerate, long[] timerPtr) {
boolean limitFPS = limitFramerate > 0 && limitFramerate < 1000;
boolean blocked = false;
if(limitFPS) {
if(timerPtr[0] == 0l) {
timerPtr[0] = steadyTimeMillis();
}else {
long millis = steadyTimeMillis();
long frameMillis = (1000l / limitFramerate);
long frameTime = millis - timerPtr[0];
if(frameTime > 2000l || frameTime < 0l) {
frameTime = frameMillis;
timerPtr[0] = millis;
}else {
timerPtr[0] += frameMillis;
}
if(frameTime >= 0l && frameTime < frameMillis) {
sleep((int)(frameMillis - frameTime));
blocked = true;
}
}
}else {
timerPtr[0] = 0l;
}
return blocked;
}
}

View File

@ -243,6 +243,7 @@ public class Minecraft implements Runnable {
public boolean lanState = false;
public boolean yeeState = false;
public boolean checkGLErrors = false;
public Minecraft() {
this.tempDisplayHeight = 480;
@ -404,7 +405,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glPopMatrix();
EaglerAdapter.glFlush();
EaglerAdapter.updateDisplay();
updateDisplay();
long t = t1 + 17 + 17*i - EaglerAdapter.steadyTimeMillis();
if(t > 0) {
@ -437,7 +438,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glPopMatrix();
EaglerAdapter.glFlush();
EaglerAdapter.updateDisplay();
updateDisplay();
long t = t1 + 17 + 17*i - EaglerAdapter.steadyTimeMillis();
if(t > 0) {
@ -468,7 +469,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glPopMatrix();
EaglerAdapter.glFlush();
EaglerAdapter.updateDisplay();
updateDisplay();
long t = t1 + 17 + 17*i - EaglerAdapter.steadyTimeMillis();
if(t > 0) {
@ -478,7 +479,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glClear(EaglerAdapter.GL_COLOR_BUFFER_BIT | EaglerAdapter.GL_DEPTH_BUFFER_BIT);
EaglerAdapter.glFlush();
EaglerAdapter.updateDisplay();
updateDisplay();
EaglerAdapter.sleep(100);
@ -521,7 +522,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glEnable(EaglerAdapter.GL_ALPHA_TEST);
EaglerAdapter.glAlphaFunc(EaglerAdapter.GL_GREATER, 0.1F);
EaglerAdapter.glFlush();
EaglerAdapter.updateDisplay();
updateDisplay();
EaglerAdapter.optimize();
}
@ -603,6 +604,7 @@ public class Minecraft implements Runnable {
* string.
*/
public void checkGLError(String par1Str) {
if(!checkGLErrors) return;
int var2;
@ -712,7 +714,7 @@ public class Minecraft implements Runnable {
EaglerAdapter.glEnable(EaglerAdapter.GL_TEXTURE_2D);
if (!EaglerAdapter.isKeyDown(65)) {
EaglerAdapter.updateDisplay();
updateDisplay();
}
if (this.thePlayer != null && this.thePlayer.isEntityInsideOpaqueBlock()) {
@ -781,11 +783,6 @@ public class Minecraft implements Runnable {
chunkGeometryUpdates = 0;
secondTimer = EaglerAdapter.steadyTimeMillis();
}
this.mcProfiler.startSection("syncDisplay");
if (this.func_90020_K() > 0) {
EaglerAdapter.syncDisplay(EntityRenderer.performanceToFps(this.func_90020_K()));
}
if(isGonnaTakeDatScreenShot) {
isGonnaTakeDatScreenShot = false;
@ -1104,6 +1101,11 @@ public class Minecraft implements Runnable {
this.voiceOverlay.setResolution(var4, var5);
}
public void updateDisplay() {
int i = this.func_90020_K();
EaglerAdapter.updateDisplay(i > 0 ? EntityRenderer.performanceToFps(i) : 0, false);
}
private boolean wasPaused = false;
/**

View File

@ -101,13 +101,13 @@ public class GuiConnecting extends GuiScreen {
private void showDisconnectScreen(String e) {
RateLimit l = EaglerAdapter.getRateLimitStatus();
if(l == RateLimit.NOW_LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipNowLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipNowLocked", "disconnect.endOfStream"));
}else if(l == RateLimit.LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipLocked", "disconnect.endOfStream"));
}else if(l == RateLimit.BLOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipBlocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipBlocked", "disconnect.endOfStream"));
}else if(l == RateLimit.FAILED_POSSIBLY_LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipFailedPossiblyLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "disconnect.ipFailedPossiblyLocked", "disconnect.endOfStream"));
}else {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "connect.failed", "disconnect.genericReason", "could not connect to "+uri, e));
}

View File

@ -137,7 +137,7 @@ public class LoadingScreenRenderer implements IProgressUpdate {
this.mc.fontRenderer.drawStringWithShadow(this.currentlyDisplayedText, (var5 - this.mc.fontRenderer.getStringWidth(this.currentlyDisplayedText)) / 2, var6 / 2 - 4 - 16, 16777215);
this.mc.fontRenderer.drawStringWithShadow(this.field_73727_a, (var5 - this.mc.fontRenderer.getStringWidth(this.field_73727_a)) / 2, var6 / 2 - 4 + 8, 16777215);
EaglerAdapter.updateDisplay();
this.mc.updateDisplay();
}
}
}

View File

@ -119,20 +119,20 @@ public class NetClientHandler extends NetHandler {
RateLimit r = EaglerAdapter.getRateLimitStatus();
if(r != null) {
if(r == RateLimit.NOW_LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipNowLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipNowLocked", "disconnect.endOfStream"));
}else if(r == RateLimit.LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipLocked", "disconnect.endOfStream"));
}else if(r == RateLimit.BLOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipBlocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipBlocked", "disconnect.endOfStream"));
}else if(r == RateLimit.FAILED_POSSIBLY_LOCKED) {
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipFailedPossiblyLocked", "disconnect.endOfStream", null));
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.ratelimit.ipFailedPossiblyLocked", "disconnect.endOfStream"));
}else {
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.disconnected", "RateLimit." + r.name(), null));
this.mc.displayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.disconnected", "RateLimit." + r.name()));
}
}else {
if(!(this.mc.currentScreen instanceof GuiDisconnected) && !(this.mc.currentScreen instanceof GuiScreenSingleplayerException) &&
!(this.mc.currentScreen instanceof GuiScreenSingleplayerLoading)) {
this.mc.stopServerAndDisplayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.disconnected", "disconnect.endOfStream", null));
this.mc.stopServerAndDisplayGuiScreen(new GuiDisconnected(backToMenu(), "disconnect.disconnected", "disconnect.endOfStream"));
}
}
this.disconnected = true;

View File

@ -32,6 +32,7 @@ import org.teavm.interop.AsyncCallback;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.ajax.ReadyStateChangeHandler;
import org.teavm.jso.ajax.XMLHttpRequest;
import org.teavm.jso.browser.Storage;
@ -39,6 +40,7 @@ import org.teavm.jso.browser.TimerHandler;
import org.teavm.jso.browser.Window;
import org.teavm.jso.canvas.CanvasRenderingContext2D;
import org.teavm.jso.canvas.ImageData;
import org.teavm.jso.core.JSString;
import org.teavm.jso.dom.css.CSSStyleDeclaration;
import org.teavm.jso.dom.events.ErrorEvent;
import org.teavm.jso.dom.events.Event;
@ -108,10 +110,12 @@ import net.lax1dude.eaglercraft.Voice;
import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftLANClient;
import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftLANServer;
import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftVoiceClient;
import net.lax1dude.eaglercraft.adapter.teavm.MessageChannel;
import net.lax1dude.eaglercraft.adapter.teavm.SelfDefence;
import net.lax1dude.eaglercraft.adapter.teavm.WebGL2RenderingContext;
import net.lax1dude.eaglercraft.adapter.teavm.WebGLQuery;
import net.lax1dude.eaglercraft.adapter.teavm.WebGLVertexArray;
import net.lax1dude.eaglercraft.glemu.EaglerAdapterGL30;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket00Handshake;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds;
@ -243,6 +247,13 @@ public class EaglerAdapterImpl2 {
private static String[] identifier = new String[0];
private static String integratedServerScript = "worker_bootstrap.js";
private static boolean anisotropicFilteringSupported = false;
private static boolean vsyncSupport = false;
private static int vsyncTimeout = -1;
private static boolean useDelayOnSwap = false;
private static MessageChannel immediateContinueChannel = null;
private static Runnable currentMsgChannelContinueHack = null;
private static final EagsFileChooser fileChooser = initFileChooser();
public static final String[] getIdentifier() {
return identifier;
@ -395,6 +406,18 @@ public class EaglerAdapterImpl2 {
});
onBeforeCloseRegister();
checkImmediateContinueSupport();
vsyncTimeout = -1;
vsyncSupport = false;
try {
asyncRequestAnimationFrame();
vsyncSupport = true;
}catch(Throwable t) {
System.err.println("VSync is not supported on this browser!");
}
initFileChooser();
EarlyLoadScreen.paintScreen();
@ -452,37 +475,58 @@ public class EaglerAdapterImpl2 {
}
}, 5000);
}
@JSBody(params = { }, script = "return window.startVoiceClient();")
private static native EaglercraftVoiceClient startVoiceClient();
private static interface EagsFileChooser extends JSObject {
@JSProperty
HTMLElement getInputElement();
void openFileChooser(String ext, String mime);
@JSProperty
ArrayBuffer getGetFileChooserResult();
@JSProperty
void setGetFileChooserResult(ArrayBuffer val);
@JSProperty
String getGetFileChooserResultName();
@JSProperty
void setGetFileChooserResultName(String str);
}
@JSBody(params = { }, script =
"window.eagsFileChooser = {\r\n" +
"var ret = {\r\n" +
"inputElement: null,\r\n" +
"openFileChooser: function(ext, mime){\r\n" +
"var el = window.eagsFileChooser.inputElement = document.createElement(\"input\");\r\n" +
"var el = ret.inputElement = document.createElement(\"input\");\r\n" +
"el.type = \"file\";\r\n" +
"el.multiple = false;\r\n" +
"el.addEventListener(\"change\", function(evt){\r\n" +
"var f = window.eagsFileChooser.inputElement.files;\r\n" +
"var f = ret.inputElement.files;\r\n" +
"if(f.length == 0){\r\n" +
"window.eagsFileChooser.getFileChooserResult = null;\r\n" +
"ret.getFileChooserResult = null;\r\n" +
"}else{\r\n" +
"(async function(){\r\n" +
"window.eagsFileChooser.getFileChooserResult = await f[0].arrayBuffer();\r\n" +
"window.eagsFileChooser.getFileChooserResultName = f[0].name;\r\n" +
"})();\r\n" +
"f[0].arrayBuffer().then(function(res) {\r\n" +
"ret.getFileChooserResult = res;\r\n" +
"ret.getFileChooserResultName = f[0].name;\r\n" +
"});\r\n" +
"}\r\n" +
"});\r\n" +
"window.eagsFileChooser.getFileChooserResult = null;\r\n" +
"window.eagsFileChooser.getFileChooserResultName = null;\r\n" +
"ret.getFileChooserResult = null;\r\n" +
"ret.getFileChooserResultName = null;\r\n" +
"el.accept = \".\" + ext;\r\n" +
"el.click();\r\n" +
"},\r\n" +
"getFileChooserResult: null,\r\n" +
"getFileChooserResultName: null\r\n" +
"};")
private static native void initFileChooser();
"}; return ret;")
private static native EagsFileChooser initFileChooser();
public static final void destroyContext() {
@ -1695,7 +1739,13 @@ public class EaglerAdapterImpl2 {
public static final boolean shouldShutdown() {
return false;
}
public static final void updateDisplay() {
public static final boolean isVSyncSupported() {
return vsyncSupport;
}
@JSBody(params = { "doc" }, script = "return (typeof doc.visibilityState !== \"string\") || (doc.visibilityState === \"visible\");")
private static native boolean getVisibilityState(JSObject doc);
private static final long[] syncTimer = new long[1];
public static final void updateDisplay(int fpsLimit, boolean vsync) {
double r = win.getDevicePixelRatio();
int w = parent.getClientWidth();
int h = parent.getClientHeight();
@ -1713,7 +1763,172 @@ public class EaglerAdapterImpl2 {
webgl.blitFramebuffer(0, 0, backBufferWidth, backBufferHeight, 0, 0, w2, h2, COLOR_BUFFER_BIT, NEAREST);
webgl.bindFramebuffer(FRAMEBUFFER, backBuffer.obj);
resizeBackBuffer(w2, h2);
sleep(1);
if(getVisibilityState(win.getDocument())) {
if(vsyncSupport && vsync) {
syncTimer[0] = 0l;
asyncRequestAnimationFrame();
}else {
if(fpsLimit <= 0) {
syncTimer[0] = 0l;
swapDelayTeaVM();
}else {
if(!EaglerAdapterGL30.sync(fpsLimit, syncTimer)) {
swapDelayTeaVM();
}
}
}
}else {
syncTimer[0] = 0l;
sleep(50);
}
}
@Async
private static native void asyncRequestAnimationFrame();
private static void asyncRequestAnimationFrame(AsyncCallback<Void> cb) {
if(vsyncTimeout != -1) {
cb.error(new IllegalStateException("Already waiting for vsync!"));
return;
}
final boolean[] hasTimedOut = new boolean[] { false };
final int[] timeout = new int[] { -1 };
Window.requestAnimationFrame((d) -> {
if(!hasTimedOut[0]) {
hasTimedOut[0] = true;
if(vsyncTimeout != -1) {
if(vsyncTimeout == timeout[0]) {
try {
Window.clearTimeout(vsyncTimeout);
}catch(Throwable t) {
}
vsyncTimeout = -1;
}
cb.complete(null);
}
}
});
vsyncTimeout = timeout[0] = Window.setTimeout(() -> {
if(!hasTimedOut[0]) {
hasTimedOut[0] = true;
if(vsyncTimeout != -1) {
vsyncTimeout = -1;
cb.complete(null);
}
}
}, 50);
}
private static final void swapDelayTeaVM() {
if(!useDelayOnSwap && immediateContinueChannel != null) {
immediateContinueTeaVM0();
}else {
sleep(0);
}
}
public static final void immediateContinue() {
if(immediateContinueChannel != null) {
immediateContinueTeaVM0();
}else {
sleep(0);
}
}
private static final JSString emptyJSString = JSString.valueOf("");
@Async
private static native void immediateContinueTeaVM0();
private static void immediateContinueTeaVM0(final AsyncCallback<Void> cb) {
if(currentMsgChannelContinueHack != null) {
cb.error(new IllegalStateException("Main thread is already waiting for an immediate continue callback!"));
return;
}
currentMsgChannelContinueHack = () -> {
cb.complete(null);
};
try {
immediateContinueChannel.getPort2().postMessage(emptyJSString);
}catch(Throwable t) {
currentMsgChannelContinueHack = null;
System.err.println("Caught error posting immediate continue, using setTimeout instead");
Window.setTimeout(() -> cb.complete(null), 0);
}
}
private static final int IMMEDIATE_CONT_SUPPORTED = 0;
private static final int IMMEDIATE_CONT_FAILED_NOT_ASYNC = 1;
private static final int IMMEDIATE_CONT_FAILED_NOT_CONT = 2;
private static final int IMMEDIATE_CONT_FAILED_EXCEPTIONS = 3;
private static void checkImmediateContinueSupport() {
immediateContinueChannel = null;
int stat = checkImmediateContinueSupport0();
if(stat == IMMEDIATE_CONT_SUPPORTED) {
return;
}else if(stat == IMMEDIATE_CONT_FAILED_NOT_ASYNC) {
System.err.println("MessageChannel fast immediate continue hack is incompatible with this browser due to actually continuing immediately!");
}else if(stat == IMMEDIATE_CONT_FAILED_NOT_CONT) {
System.err.println("MessageChannel fast immediate continue hack is incompatible with this browser due to startup check failing!");
}else if(stat == IMMEDIATE_CONT_FAILED_EXCEPTIONS) {
System.err.println("MessageChannel fast immediate continue hack is incompatible with this browser due to exceptions!");
}
immediateContinueChannel = null;
}
private static int checkImmediateContinueSupport0() {
try {
if(!MessageChannel.supported()) {
return IMMEDIATE_CONT_SUPPORTED;
}
immediateContinueChannel = new MessageChannel();
immediateContinueChannel.getPort1().addEventListener("message", new EventListener<MessageEvent>() {
@Override
public void handleEvent(MessageEvent evt) {
Runnable toRun = currentMsgChannelContinueHack;
currentMsgChannelContinueHack = null;
if(toRun != null) {
toRun.run();
}
}
});
immediateContinueChannel.getPort1().start();
immediateContinueChannel.getPort2().start();
final boolean[] checkMe = new boolean[1];
checkMe[0] = false;
currentMsgChannelContinueHack = () -> {
checkMe[0] = true;
};
immediateContinueChannel.getPort2().postMessage(emptyJSString);
if(checkMe[0]) {
currentMsgChannelContinueHack = null;
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
return IMMEDIATE_CONT_FAILED_NOT_ASYNC;
}
sleep(10);
currentMsgChannelContinueHack = null;
if(!checkMe[0]) {
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
return IMMEDIATE_CONT_FAILED_NOT_CONT;
}else {
return IMMEDIATE_CONT_SUPPORTED;
}
}catch(Throwable t) {
currentMsgChannelContinueHack = null;
if(immediateContinueChannel != null) {
safeShutdownChannel(immediateContinueChannel);
}
immediateContinueChannel = null;
return IMMEDIATE_CONT_FAILED_EXCEPTIONS;
}
}
private static void safeShutdownChannel(MessageChannel chan) {
try {
chan.getPort1().close();
}catch(Throwable tt) {
}
try {
chan.getPort2().close();
}catch(Throwable tt) {
}
}
public static final void setupBackBuffer() {
backBuffer = _wglCreateFramebuffer();
@ -1740,9 +1955,6 @@ public class EaglerAdapterImpl2 {
public static final float getContentScaling() {
return (float)win.getDevicePixelRatio();
}
public static final void setVSyncEnabled(boolean p1) {
}
public static final void enableRepeatEvents(boolean b) {
enableRepeatEvents = b;
}
@ -1776,9 +1988,6 @@ public class EaglerAdapterImpl2 {
}
public static final void setDisplaySize(int x, int y) {
}
public static final void syncDisplay(int performanceToFps) {
}
private static final DateFormat dateFormatSS = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
@ -2001,11 +2210,13 @@ public class EaglerAdapterImpl2 {
@JSBody(params = { }, script = "window.onbeforeunload = function(){javaMethods.get('net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2.onWindowUnload()V').invoke();return false;};")
private static native void onBeforeCloseRegister();
@JSBody(params = { "ext", "mime" }, script = "window.eagsFileChooser.openFileChooser(ext, mime);")
public static native void openFileChooser(String ext, String mime);
public static final void openFileChooser(String ext, String mime) {
fileChooser.openFileChooser(ext, mime);
}
@JSBody(params = { }, script = "return window.eagsFileChooser.getFileChooserResult != null;")
public static final native boolean getFileChooserResultAvailable();
public static final boolean getFileChooserResultAvailable() {
return fileChooser.getGetFileChooserResult() != null;
}
public static final byte[] getFileChooserResult() {
ArrayBuffer b = getFileChooserResult0();
@ -2022,12 +2233,16 @@ public class EaglerAdapterImpl2 {
getFileChooserResult0();
}
@JSBody(params = { }, script = "var ret = window.eagsFileChooser.getFileChooserResult; window.eagsFileChooser.getFileChooserResult = null; return ret;")
private static native ArrayBuffer getFileChooserResult0();
private static final ArrayBuffer getFileChooserResult0() {
ArrayBuffer ret = fileChooser.getGetFileChooserResult();
fileChooser.setGetFileChooserResultName(null);
return ret;
}
public static final String getFileChooserResultName() {
return fileChooser.getGetFileChooserResultName();
}
@JSBody(params = { }, script = "var ret = window.eagsFileChooser.getFileChooserResultName; window.eagsFileChooser.getFileChooserResultName = null; return ret;")
public static native String getFileChooserResultName();
public static final void setListenerPos(float x, float y, float z, float vx, float vy, float vz, float pitch, float yaw) {
float var2 = MathHelper.cos(-yaw * 0.017453292F);
float var3 = MathHelper.sin(-yaw * 0.017453292F);

View File

@ -0,0 +1,36 @@
package net.lax1dude.eaglercraft.adapter.teavm;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.workers.MessagePort;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
@JSClass
public class MessageChannel implements JSObject {
@JSBody(params = { }, script = "return (typeof MessageChannel !== \"undefined\");")
public static native boolean supported();
@JSProperty
public native MessagePort getPort1();
@JSProperty
public native MessagePort getPort2();
}