package de.geolykt.starloader.impl.asm; import java.util.List; import java.util.Objects; import java.util.function.IntConsumer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.input.GestureDetector.GestureListener; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import de.geolykt.starloader.api.CoordinateGrid; import de.geolykt.starloader.api.Galimulator; import de.geolykt.starloader.api.gui.Drawing; import de.geolykt.starloader.api.gui.KeystrokeInputHandler; import de.geolykt.starloader.api.gui.canvas.CanvasManager; import de.geolykt.starloader.api.gui.canvas.CanvasSettings; import de.geolykt.starloader.api.gui.modconf.ConfigurationOption; import de.geolykt.starloader.api.gui.modconf.FloatOption; import de.geolykt.starloader.api.gui.modconf.IntegerOption; import de.geolykt.starloader.api.utils.TickLoopLock; import de.geolykt.starloader.api.utils.TickLoopLock.LockScope; import de.geolykt.starloader.impl.gui.AsyncPanListener; import de.geolykt.starloader.impl.gui.AsyncWidgetInput; import de.geolykt.starloader.impl.gui.GestureListenerAccess; import de.geolykt.starloader.impl.gui.WidgetMouseReleaseListener; import de.geolykt.starloader.impl.gui.keybinds.KeybindListMenu; import de.geolykt.starloader.impl.util.LongRingBuffer; import snoddasmannen.galimulator.AuxiliaryListener; import snoddasmannen.galimulator.GalColor; import snoddasmannen.galimulator.GalFX; import snoddasmannen.galimulator.GalimulatorGestureListener; import snoddasmannen.galimulator.MapData; import snoddasmannen.galimulator.Space; import snoddasmannen.galimulator.actors.Actor; import snoddasmannen.galimulator.rendersystem.class_4; import snoddasmannen.galimulator.ui.AboutWidget; import snoddasmannen.galimulator.ui.BufferedWidgetWrapper; import snoddasmannen.galimulator.ui.NinepatchButtonWidget; import snoddasmannen.galimulator.ui.Widget; /** * Class holding java-code that should be invoked by methods injected through ASM-Transformers. * This significantly reduces code upkeep costs and improves readability. Such hybrid development * also reduces development time. * * @since 2.0.0 */ public class TransformCallbacks { @ApiStatus.AvailableSince("2.0.0-a20241109") private static final boolean DEBUG_ASYNC_LOCK_REQUESTS = Boolean.getBoolean("org.stianloader.slapi.DEBUG_ASYNC_LOCK_REQUESTS"); /** * The default implementation of {@link IntegerOption#addValueChangeListener(java.util.function.IntConsumer)}, * {@link FloatOption#addValueChangeListener(de.geolykt.starloader.api.utils.FloatConsumer)} * and {@link ConfigurationOption#addValueChangeListener(java.util.function.Consumer)}. * * @param obj The instance of the option class * @since 2.0.0 */ public static void abi$raiseABIError(@NotNull Object obj) { throw new UnsupportedOperationException("This implementation (" + obj.getClass().getName() + ") does not implement the needed SLAPI 2.0 API."); } /** * Method that is called instead of the logic within the constructor of {@link AboutWidget} that adds * the shortcut list button. More specifically, this method adds a replacement for the shortcut list * button and adds it to the widget. * * @param widget The instance of the {@link AboutWidget} class that calls this method * @since 2.0.0 */ public static void about$shortcutListReplace(@NotNull AboutWidget widget) { widget.layout.newline(); widget.addChild(new NinepatchButtonWidget( GalFX.NINEPATCH.BUTTON3, (int)(widget.getWidth() * 0.8F), (int)(GalFX.P() * 2.0F), "Keyboard shortcuts", GalFX.FONT_TYPE.MONOTYPE_DEFAULT, GalColor.WHITE, GalColor.GREEN, 0) { @Override public void mouseDown(double x, double y) { Space.closeNonPersistentWidgets(); CanvasManager cm = CanvasManager.getInstance(); cm.openCanvas(cm.newCanvas(new KeybindListMenu(KeystrokeInputHandler.getInstance(), 800, 610), new CanvasSettings("Keyboard shortcuts"))); } }); } /** * This is the method that replaces {@link GalimulatorGestureListener#pan(float, float, float, float)}. * *
This method is mainly used to provide improved asynchronous capabilities by offering * finer-tuned access to locks. This method should not be altered by mods - if the need of doing so * should arise, please notify me so I can adjust this method to suit your usecases better. * *
Due to being an overwrite of the pan method within galimulator code,
* this method has the same properties as {@link GestureListener#pan(float, float, float, float)}.
*
* @param access Access to the caller gesture listener instance via {@link GestureListenerAccess}.
* @param x The current X-coordinate of the cursor.
* @param y The current Y-coordinate of the cursor.
* @param deltaX The difference in pixels to the last drag event within the x-axis.
* @param deltaY The difference in pixels to the last drag event within the y-axis.
* @return True if the input was processed, false otherwise.
* @since 2.0.0-a20241108
*/
@ApiStatus.AvailableSince("2.0.0-a20241108")
public static boolean gesturelistener$onPan(GestureListenerAccess access, float x, float y, float deltaX, float deltaY) {
TickLoopLock tickLock = Galimulator.getSimulationLoopLock();
// Test whether the graphical loop was locked (e.g. while generating a galaxy)
if (!tickLock.tryAcquireSoftControl()) {
return false;
}
tickLock.releaseSoft();
Actor selectedActor = access.slapi$getSelectedActor();
if (!access.slapi$isDraggingSelectedActor() && selectedActor != null && Space.a(selectedActor.getOwner())) {
access.slapi$setDraggingSelectedActor(true);
Space.addAuxiliaryListener(SLIntrinsics.createActorDragManager(selectedActor));
}
@SuppressWarnings("deprecation") // CoordinateGrid.WIDGET is used as intended
Vector3 widgetCoordinates = Drawing.convertCoordinates(CoordinateGrid.SCREEN, CoordinateGrid.WIDGET, x, y);
widgetCoordinates.y = GalFX.getScreenHeight() - widgetCoordinates.y;
Vector2 widgetCoords2 = new Vector2(widgetCoordinates.x, widgetCoordinates.y);
TickLoopLock.LockScope acquiredLock = null;
try {
// Iterate over widgets in backwards order (that is the higher the ordinal of a widget within the list, the higher it's priority)
for (int widgetIndex = Space.activeWidgets.size(); widgetIndex > 0;) {
Widget widget = Space.activeWidgets.get(--widgetIndex);
if (!widget.containsPoint(widgetCoords2)) {
continue;
}
float clickedWidgetX = (float) (widgetCoords2.x - widget.getX());
float clickedWidgetY = (float) (widgetCoords2.y - widget.getY());
if (!widget.l_() && Objects.isNull(acquiredLock) && !(widget instanceof AsyncWidgetInput && ((AsyncWidgetInput) widget).isAsyncPan())) {
if (TransformCallbacks.DEBUG_ASYNC_LOCK_REQUESTS) {
if (widget instanceof BufferedWidgetWrapper) {
LoggerFactory.getLogger(TransformCallbacks.class).info("Acquired strong control for BWW'D widget {}", ((BufferedWidgetWrapper) widget).getChildWidgets().get(0));
} else {
LoggerFactory.getLogger(TransformCallbacks.class).info("Acquired strong control for {}", widget);
}
}
acquiredLock = tickLock.acquireHardControlWithResources();
}
widget.a(deltaX, deltaY, clickedWidgetX, clickedWidgetY);
return true;
}
List This method is mainly used to provide improved asynchronous capabilities by offering
* finer-tuned access to locks. This method should not be altered by mods - if the need of doing so
* should arise, please notify me so I can adjust this method to suit your usecases better.
*
* Due to being an overwrite of the touchDown method within galimulator code,
* this method has the same properties as {@link InputProcessor#touchDown(int, int, int, int)}.
*
* @param access Access to the caller gesture listener instance via {@link GestureListenerAccess}.
* @param x The X-coordinate where the mouse button was pressed.
* @param y The Y-coordinate where the mouse button was pressed.
* @param pointer The pointer of the event, almost definitely The replacement logic mainly intends to replace slightly bugged code that would otherwise
* be hard to solve with simple mixins or ASM transformations. As such, this is among the more
* invasive mixins introduced by SLAPI.
*
* This method should not exit during normal operation. It might terminate during an application
* crash, though.
*
* This method is called via the respective Mixin overwrite. This method is, like all other
* methods in this class, not public API. Call, transform, or otherwise depend on this method
* at your own risk.
*
* @param tpsSetter Feedback supplier that is responsible for setting the current TPS (ticks
* per second) field.
* @since 2.0.0-a20250911
*/
@Blocking
@ApiStatus.AvailableSince("2.0.0-a20250911")
public static void tickloop$run(@NotNull IntConsumer tpsSetter) {
double frameaccummulator = 0;
boolean halfStep = false;
LongRingBuffer tpsBuffer = new LongRingBuffer(512);
while (true) {
try {
double targetTPS = Galimulator.getConfiguration().getTargetTPS();
if (targetTPS <= 0F) {
targetTPS = Double.POSITIVE_INFINITY;
}
long targetNSPT = (long) (1e+9 / targetTPS);
double tpf = Galimulator.getConfiguration().getTimelapseModifier();
if (tpf <= 0D) {
tpf = 1D;
}
double fpt = 1D / tpf;
int tickNumber = 0;
long startNanos = System.nanoTime();
TickLoopLock simLoopLock = Galimulator.getSimulationLoopLock();
frameaccummulator++;
while (frameaccummulator > fpt) {
frameaccummulator -= fpt;
tickNumber++;
try (LockScope lock = simLoopLock.acquireSoftControlWithResources()) {
if (!Space.get_ag() || (halfStep ^= true)) {
Space.tick();
}
}
}
try (LockScope lock = simLoopLock.acquireSoftControlWithResources()) {
class_4.a(Space.drawToCache());
}
Space.F.lock();
for (Widget var17 : Space.activeWidgets) {
var17.refreshLayout();
}
Space.F.unlock();
if (tickNumber > 0) {
long sleepTime = (startNanos - System.nanoTime()) + targetNSPT * tickNumber;
if (sleepTime > 0) {
Thread.sleep(sleepTime / 1_000_000L, (int) (sleepTime % 1_000_000));
}
// Update TPS counter
if (tickNumber <= 255) { // The tick timer makes no sense for large numbers anyways
tpsBuffer.appendValue(System.nanoTime(), tickNumber);
long nspt = (tpsBuffer.getHeadValue() - tpsBuffer.getTailValue()) / tpsBuffer.getLength();
tpsSetter.accept(nspt == 0 ? 0 : (int) (1_000_000_000 / nspt));
}
}
} catch (Throwable t) {
if (t instanceof ThreadDeath) {
Galimulator.panic("Simulation thread killed", false, t);
throw (ThreadDeath) t;
} else if (t instanceof InterruptedException) {
LoggerFactory.getLogger(TransformCallbacks.class).error("Simulation loop interrupted. Continuing anyways.", t);
continue;
}
Galimulator.panic("An error occured while running the ticking loop.", true, t);
break;
}
}
}
private TransformCallbacks() {
}
}
-1 since we are on desktop.
* @param button The button that was pressed.
* @return True if the input was processed, false otherwise.
* @see InputProcessor#touchDown(int, int, int, int)
* @since 2.0.0-a20241107
*/
@ApiStatus.AvailableSince("2.0.0-a20241107")
public static boolean gesturelistener$onTouchDown(GestureListenerAccess access, float x, float y, int pointer, int button) {
access.slapi$setLastClickedOnWidget(false);
TickLoopLock tickLock = Galimulator.getSimulationLoopLock();
// Test whether the graphical loop was locked (e.g. while generating a galaxy)
if (!tickLock.tryAcquireSoftControl()) {
return false;
}
tickLock.releaseSoft();
@SuppressWarnings("deprecation") // CoordinateGrid.WIDGET is used as intended
Vector3 widgetCoordinates = Drawing.convertCoordinates(CoordinateGrid.SCREEN, CoordinateGrid.WIDGET, x, y);
widgetCoordinates.y = GalFX.getScreenHeight() - widgetCoordinates.y;
Vector2 widgetCoords2 = new Vector2(widgetCoordinates.x, widgetCoordinates.y);
TickLoopLock.LockScope acquiredLock = null;
try {
// Iterate over widgets in backwards order (that is the higher the ordinal of a widget within the list, the higher it's priority)
for (int widgetIndex = Space.activeWidgets.size(); widgetIndex > 0;) {
Widget widget = Space.activeWidgets.get(--widgetIndex);
if (!widget.containsPoint(widgetCoords2)) {
continue;
}
float clickedWidgetX = (float) (widgetCoords2.x - widget.getX());
float clickedWidgetY = (float) (widgetCoords2.y - widget.getY());
if (widget instanceof AsyncWidgetInput && ((AsyncWidgetInput) widget).isAsyncClick()) {
if (widget.interceptMouseDown(clickedWidgetX, clickedWidgetY)) {
access.slapi$setLastClickedOnWidget(true);
return true;
} else {
continue;
}
}
if (!widget.l_() && Objects.isNull(acquiredLock)) {
acquiredLock = tickLock.acquireHardControlWithResources();
}
if (!widget.interceptMouseDown(clickedWidgetX, clickedWidgetY)) {
widget.mouseDown(clickedWidgetX, clickedWidgetY);
widget.considerRelayout();
}
// Since we clicked on a widget, we need to stop processing here
access.slapi$setLastClickedOnWidget(true);
return true;
}
List