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(); this.tick();
lastTick = SysUtil.steadyTimeMillis(); lastTick = SysUtil.steadyTimeMillis();
} else { } else {
boolean mustYield = false; if (delta >= 50l) {
while (delta >= 50L) {
if(mustYield) {
SysUtil.sleep(1); // allow some async
}
delta -= 50L; delta -= 50L;
lastTick = SysUtil.steadyTimeMillis(); lastTick += 50l;
this.tick(); this.tick();
mustYield = true;
} }
} }

View File

@ -749,6 +749,8 @@ public class IntegratedServer {
sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket01StopServer.ID)); sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket01StopServer.ID));
currentProcess = null; currentProcess = null;
} }
}else {
SysUtil.sleep(50);
} }
} }
@ -767,7 +769,7 @@ public class IntegratedServer {
mainLoop(); mainLoop();
SysUtil.sleep(1); // allow some async to occur SysUtil.immediateContinue();
} }
// yee // 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.interop.AsyncCallback;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject; 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.Platform;
import org.teavm.platform.PlatformRunnable; 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.RelayWorldsQuery;
import net.lax1dude.eaglercraft.ServerQuery; import net.lax1dude.eaglercraft.ServerQuery;
import net.lax1dude.eaglercraft.Voice; 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.adapter.lwjgl.GameWindowListener;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket; import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds.LocalWorld; import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds.LocalWorld;
@ -971,11 +969,15 @@ public class EaglerAdapterImpl2 {
public static final boolean shouldShutdown() { public static final boolean shouldShutdown() {
return Display.isCloseRequested(); return Display.isCloseRequested();
} }
public static final void updateDisplay() { 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(); Display.update();
} }
public static final void setVSyncEnabled(boolean p1) {
Display.setVSyncEnabled(p1);
} }
public static final void enableRepeatEvents(boolean b) { public static final void enableRepeatEvents(boolean b) {
Keyboard.enableRepeatEvents(b); Keyboard.enableRepeatEvents(b);
@ -1005,17 +1007,14 @@ public class EaglerAdapterImpl2 {
e.printStackTrace(); 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> rateLimitedAddresses = new HashSet<>();
private static final Set<String> blockedAddresses = new HashSet(); private static final Set<String> blockedAddresses = new HashSet<>();
private static WebSocketClient clientSocket = null; private static WebSocketClient clientSocket = null;
private static final Object socketSync = new Object(); 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 { private static class EaglerSocketClient extends WebSocketClient {
@ -1465,7 +1464,7 @@ public class EaglerAdapterImpl2 {
public static final float getVoiceSpeakVolume() { public static final float getVoiceSpeakVolume() {
return volumeSpeak; return volumeSpeak;
} }
private static final Set<String> emptySet = new HashSet(); private static final Set<String> emptySet = new HashSet<>();
public static final Set<String> getVoiceListening() { public static final Set<String> getVoiceListening() {
return emptySet; return emptySet;
} }
@ -1478,7 +1477,7 @@ public class EaglerAdapterImpl2 {
public static final Set<String> getVoiceMuted() { public static final Set<String> getVoiceMuted() {
return emptySet; return emptySet;
} }
private static final List<String> emptyList = new ArrayList(); private static final List<String> emptyList = new ArrayList<>();
public static final List<String> getVoiceRecent() { public static final List<String> getVoiceRecent() {
return emptyList; return emptyList;
} }
@ -1546,8 +1545,8 @@ public class EaglerAdapterImpl2 {
private static class ServerQueryImpl extends WebSocketClient implements ServerQuery { private static class ServerQueryImpl extends WebSocketClient implements ServerQuery {
private final LinkedList<QueryResponse> queryResponses = new LinkedList(); private final LinkedList<QueryResponse> queryResponses = new LinkedList<>();
private final LinkedList<byte[]> queryResponsesBytes = new LinkedList(); private final LinkedList<byte[]> queryResponsesBytes = new LinkedList<>();
private final String type; private final String type;
private boolean open; private boolean open;
private boolean alive; private boolean alive;

View File

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

View File

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

View File

@ -101,13 +101,13 @@ public class GuiConnecting extends GuiScreen {
private void showDisconnectScreen(String e) { private void showDisconnectScreen(String e) {
RateLimit l = EaglerAdapter.getRateLimitStatus(); RateLimit l = EaglerAdapter.getRateLimitStatus();
if(l == RateLimit.NOW_LOCKED) { 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) { }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) { }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) { }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 { }else {
this.mc.displayGuiScreen(new GuiDisconnected(this.field_98098_c, "connect.failed", "disconnect.genericReason", "could not connect to "+uri, e)); 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.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); 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(); RateLimit r = EaglerAdapter.getRateLimitStatus();
if(r != null) { if(r != null) {
if(r == RateLimit.NOW_LOCKED) { 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) { }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) { }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) { }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 { }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 { }else {
if(!(this.mc.currentScreen instanceof GuiDisconnected) && !(this.mc.currentScreen instanceof GuiScreenSingleplayerException) && if(!(this.mc.currentScreen instanceof GuiDisconnected) && !(this.mc.currentScreen instanceof GuiScreenSingleplayerException) &&
!(this.mc.currentScreen instanceof GuiScreenSingleplayerLoading)) { !(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; this.disconnected = true;

View File

@ -32,6 +32,7 @@ import org.teavm.interop.AsyncCallback;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor; import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.ajax.ReadyStateChangeHandler; import org.teavm.jso.ajax.ReadyStateChangeHandler;
import org.teavm.jso.ajax.XMLHttpRequest; import org.teavm.jso.ajax.XMLHttpRequest;
import org.teavm.jso.browser.Storage; 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.browser.Window;
import org.teavm.jso.canvas.CanvasRenderingContext2D; import org.teavm.jso.canvas.CanvasRenderingContext2D;
import org.teavm.jso.canvas.ImageData; import org.teavm.jso.canvas.ImageData;
import org.teavm.jso.core.JSString;
import org.teavm.jso.dom.css.CSSStyleDeclaration; import org.teavm.jso.dom.css.CSSStyleDeclaration;
import org.teavm.jso.dom.events.ErrorEvent; import org.teavm.jso.dom.events.ErrorEvent;
import org.teavm.jso.dom.events.Event; 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.EaglercraftLANClient;
import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftLANServer; import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftLANServer;
import net.lax1dude.eaglercraft.adapter.teavm.EaglercraftVoiceClient; 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.SelfDefence;
import net.lax1dude.eaglercraft.adapter.teavm.WebGL2RenderingContext; import net.lax1dude.eaglercraft.adapter.teavm.WebGL2RenderingContext;
import net.lax1dude.eaglercraft.adapter.teavm.WebGLQuery; import net.lax1dude.eaglercraft.adapter.teavm.WebGLQuery;
import net.lax1dude.eaglercraft.adapter.teavm.WebGLVertexArray; 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.IPacket;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket00Handshake; import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket00Handshake;
import net.lax1dude.eaglercraft.sp.relay.pkt.IPacket07LocalWorlds; 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[] identifier = new String[0];
private static String integratedServerScript = "worker_bootstrap.js"; private static String integratedServerScript = "worker_bootstrap.js";
private static boolean anisotropicFilteringSupported = false; 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() { public static final String[] getIdentifier() {
return identifier; return identifier;
@ -395,6 +406,18 @@ public class EaglerAdapterImpl2 {
}); });
onBeforeCloseRegister(); onBeforeCloseRegister();
checkImmediateContinueSupport();
vsyncTimeout = -1;
vsyncSupport = false;
try {
asyncRequestAnimationFrame();
vsyncSupport = true;
}catch(Throwable t) {
System.err.println("VSync is not supported on this browser!");
}
initFileChooser(); initFileChooser();
EarlyLoadScreen.paintScreen(); EarlyLoadScreen.paintScreen();
@ -456,33 +479,54 @@ public class EaglerAdapterImpl2 {
@JSBody(params = { }, script = "return window.startVoiceClient();") @JSBody(params = { }, script = "return window.startVoiceClient();")
private static native EaglercraftVoiceClient 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 = @JSBody(params = { }, script =
"window.eagsFileChooser = {\r\n" + "var ret = {\r\n" +
"inputElement: null,\r\n" + "inputElement: null,\r\n" +
"openFileChooser: function(ext, mime){\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.type = \"file\";\r\n" +
"el.multiple = false;\r\n" + "el.multiple = false;\r\n" +
"el.addEventListener(\"change\", function(evt){\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" + "if(f.length == 0){\r\n" +
"window.eagsFileChooser.getFileChooserResult = null;\r\n" + "ret.getFileChooserResult = null;\r\n" +
"}else{\r\n" + "}else{\r\n" +
"(async function(){\r\n" + "f[0].arrayBuffer().then(function(res) {\r\n" +
"window.eagsFileChooser.getFileChooserResult = await f[0].arrayBuffer();\r\n" + "ret.getFileChooserResult = res;\r\n" +
"window.eagsFileChooser.getFileChooserResultName = f[0].name;\r\n" + "ret.getFileChooserResultName = f[0].name;\r\n" +
"})();\r\n" + "});\r\n" +
"}\r\n" + "}\r\n" +
"});\r\n" + "});\r\n" +
"window.eagsFileChooser.getFileChooserResult = null;\r\n" + "ret.getFileChooserResult = null;\r\n" +
"window.eagsFileChooser.getFileChooserResultName = null;\r\n" + "ret.getFileChooserResultName = null;\r\n" +
"el.accept = \".\" + ext;\r\n" + "el.accept = \".\" + ext;\r\n" +
"el.click();\r\n" + "el.click();\r\n" +
"},\r\n" + "},\r\n" +
"getFileChooserResult: null,\r\n" + "getFileChooserResult: null,\r\n" +
"getFileChooserResultName: null\r\n" + "getFileChooserResultName: null\r\n" +
"};") "}; return ret;")
private static native void initFileChooser(); private static native EagsFileChooser initFileChooser();
public static final void destroyContext() { public static final void destroyContext() {
@ -1695,7 +1739,13 @@ public class EaglerAdapterImpl2 {
public static final boolean shouldShutdown() { public static final boolean shouldShutdown() {
return false; 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(); double r = win.getDevicePixelRatio();
int w = parent.getClientWidth(); int w = parent.getClientWidth();
int h = parent.getClientHeight(); 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.blitFramebuffer(0, 0, backBufferWidth, backBufferHeight, 0, 0, w2, h2, COLOR_BUFFER_BIT, NEAREST);
webgl.bindFramebuffer(FRAMEBUFFER, backBuffer.obj); webgl.bindFramebuffer(FRAMEBUFFER, backBuffer.obj);
resizeBackBuffer(w2, h2); 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() { public static final void setupBackBuffer() {
backBuffer = _wglCreateFramebuffer(); backBuffer = _wglCreateFramebuffer();
@ -1739,9 +1954,6 @@ public class EaglerAdapterImpl2 {
} }
public static final float getContentScaling() { public static final float getContentScaling() {
return (float)win.getDevicePixelRatio(); return (float)win.getDevicePixelRatio();
}
public static final void setVSyncEnabled(boolean p1) {
} }
public static final void enableRepeatEvents(boolean b) { public static final void enableRepeatEvents(boolean b) {
enableRepeatEvents = b; enableRepeatEvents = b;
@ -1776,9 +1988,6 @@ public class EaglerAdapterImpl2 {
} }
public static final void setDisplaySize(int x, int y) { 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"); 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;};") @JSBody(params = { }, script = "window.onbeforeunload = function(){javaMethods.get('net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2.onWindowUnload()V').invoke();return false;};")
private static native void onBeforeCloseRegister(); private static native void onBeforeCloseRegister();
@JSBody(params = { "ext", "mime" }, script = "window.eagsFileChooser.openFileChooser(ext, mime);") public static final void openFileChooser(String ext, String mime) {
public static native void openFileChooser(String ext, String mime); fileChooser.openFileChooser(ext, mime);
}
@JSBody(params = { }, script = "return window.eagsFileChooser.getFileChooserResult != null;") public static final boolean getFileChooserResultAvailable() {
public static final native boolean getFileChooserResultAvailable(); return fileChooser.getGetFileChooserResult() != null;
}
public static final byte[] getFileChooserResult() { public static final byte[] getFileChooserResult() {
ArrayBuffer b = getFileChooserResult0(); ArrayBuffer b = getFileChooserResult0();
@ -2022,11 +2233,15 @@ public class EaglerAdapterImpl2 {
getFileChooserResult0(); getFileChooserResult0();
} }
@JSBody(params = { }, script = "var ret = window.eagsFileChooser.getFileChooserResult; window.eagsFileChooser.getFileChooserResult = null; return ret;") private static final ArrayBuffer getFileChooserResult0() {
private static native ArrayBuffer getFileChooserResult0(); ArrayBuffer ret = fileChooser.getGetFileChooserResult();
fileChooser.setGetFileChooserResultName(null);
return ret;
}
@JSBody(params = { }, script = "var ret = window.eagsFileChooser.getFileChooserResultName; window.eagsFileChooser.getFileChooserResultName = null; return ret;") public static final String getFileChooserResultName() {
public static native String getFileChooserResultName(); return fileChooser.getGetFileChooserResultName();
}
public static final void setListenerPos(float x, float y, float z, float vx, float vy, float vz, float pitch, float yaw) { 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 var2 = MathHelper.cos(-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();
}