Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/main/java/top/fpsmaster/event/EventDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public static void registerListener(Object listener) {
if (Event.class.isAssignableFrom(parameterType)) {
Class<? extends Event> eventType = (Class<? extends Event>) parameterType;
List<Handler> listeners = eventListeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>());
// Identity + method: Module.onEnable / always-on ctor registration used to stack
// duplicate handlers for the same listener, retaining extra MethodHandleHandler
// instances and double-firing every event.
if (alreadyRegistered(listeners, listener, method)) {
continue;
}
listeners.add(new MethodHandleHandler(listener, method));
}
}
Expand All @@ -29,7 +35,9 @@ public static void registerListener(Object listener) {

public static void unregisterListener(Object listener) {
for (List<Handler> listeners : eventListeners.values()) {
listeners.removeIf(eventListener -> eventListener.getListener().getClass().equals(listener.getClass()));
// Identity, not class equality: class-based removal dropped every instance of a type
// (or left orphans when the same class had multiple live listeners).
listeners.removeIf(handler -> handler.getListener() == listener);
}
}

Expand All @@ -50,7 +58,13 @@ public static void dispatchEvent(Event event) {
}
}
}
}



private static boolean alreadyRegistered(List<Handler> listeners, Object listener, Method method) {
for (Handler handler : listeners) {
if (handler.getListener() == listener && handler.getMethod().equals(method)) {
return true;
}
}
return false;
}
}
12 changes: 11 additions & 1 deletion src/main/java/top/fpsmaster/features/GlobalListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import top.fpsmaster.event.events.*;
import top.fpsmaster.features.impl.interfaces.BetterChat;
import top.fpsmaster.features.impl.interfaces.ClientSettings;
import top.fpsmaster.features.impl.interfaces.TargetDisplay;
import top.fpsmaster.features.impl.optimizes.Performance;
import top.fpsmaster.features.impl.render.DamageIndicator;
import top.fpsmaster.modules.config.ConfigProfileUtils;
import top.fpsmaster.ui.PendingScreen;
import top.fpsmaster.ui.notification.NotificationManager;
Expand All @@ -39,6 +41,7 @@

public class GlobalListener {
private long lastFlushAt;
private boolean hadWorld;

public void init() {
EventDispatcher.registerListener(this);
Expand Down Expand Up @@ -72,7 +75,14 @@ public void onTick(EventTick e) {
// Entity rendering stops entirely after a disconnect, so culling cannot rely on its render
// hook to notice a null world and release the old WorldClient/pending entity references.
Performance.ENTITY_CULLING.updateWorld(minecraft.theWorld);
if (minecraft.theWorld != null) {
boolean hasWorld = minecraft.theWorld != null;
if (hadWorld && !hasWorld) {
// Static module fields hold strong Entity refs across disconnect otherwise.
TargetDisplay.clearTarget();
DamageIndicator.clearState();
}
hadWorld = hasWorld;
if (hasWorld) {
ReplayRecorder.instance().startIfRequested();
}
ReplayRecorder.instance().onClientTick();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public static boolean isZoomBindDown() {
public ClientSettings() {
super("ClientSettings", Category.Utility);
addSettings(language, keyBind, followGameScale, fixedScale, blur, theme, zoomBind, clientCommand, prefix);
// Always-on: language / blur guards must fire whether or not the module "enabled" flag is
// true in a profile. onEnable/onDisable are no-ops so ConfigManager.set(true) cannot stack
// a second registration on top of this one.
EventDispatcher.registerListener(this);
// get system language
Locale locale = Locale.getDefault();
Expand All @@ -104,6 +107,16 @@ public ClientSettings() {
}
}

@Override
public void onEnable() {
// Registered once in the constructor; Module.set(true) must not register again.
}

@Override
public void onDisable() {
// Always-on listener — do not unregister when a profile writes enabled=false.
}

@Subscribe
public void onValueChange(EventValueChange e) throws FileException {
if (e.setting == language){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,29 @@ public TargetDisplay() {
addSettings(targetESP, targetHUD, espColor, omit);
}

/** Drops the strong EntityPlayer ref so a disconnect cannot pin the old world graph. */
public static void clearTarget() {
target = null;
lastHit = 0L;
}

@Override
public void onDisable() {
super.onDisable();
clearTarget();
}

@Subscribe
public void onRender(EventRender3D e) {
if (target != null && target.getHealth() > 0 && target.isEntityAlive() && System.currentTimeMillis() - lastHit < 3000) {
if (targetESP.getMode() == 0) {
drawCircle(target, 0.55, true);
}
if (target == null) {
return;
}
if (target.getHealth() <= 0 || !target.isEntityAlive() || System.currentTimeMillis() - lastHit >= 3000) {
clearTarget();
return;
}
if (targetESP.getMode() == 0) {
drawCircle(target, 0.55, true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import top.fpsmaster.features.settings.impl.BooleanSetting;
import top.fpsmaster.features.settings.impl.ModeSetting;
import top.fpsmaster.features.settings.impl.NumberSetting;
import top.fpsmaster.utils.render.ItemModelLists;
import top.fpsmaster.utils.render.TextureResolution;
import top.fpsmaster.utils.render.culling.EntityCulling;

Expand Down Expand Up @@ -529,6 +530,14 @@ public Performance() {
hideDoubleTallFlowers.addChangeListener(rebuildChunks);
hideFences.addChangeListener(rebuildChunks);
hideFenceGates.addChangeListener(rebuildChunks);

// Display lists are session-lived until a resource reload; turning the cache off (or the
// module) must free them immediately or they sit in driver memory for the rest of the run.
cacheItemModels.addChangeListener((setting, oldValue, newValue) -> {
if (!Boolean.TRUE.equals(newValue)) {
ItemModelLists.invalidate();
}
});
}


Expand All @@ -555,6 +564,10 @@ public void onDisable() {
super.onDisable();
using = false;
pendingWorldRefresh = true;
// Drop in-flight occlusion probes so pending Entity refs cannot outlive the feature, and
// free any CacheItemModels display lists that would otherwise wait for a resource reload.
ENTITY_CULLING.reset();
ItemModelLists.invalidate();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import java.util.ArrayList;

public class DamageIndicator extends Module {
private EntityLivingBase lastAttack;
private static final int MAX_INDICATORS = 64;
private static final DecimalFormat DAMAGE_FORMAT = new DecimalFormat("0.00");

private static EntityLivingBase lastAttack;

public DamageIndicator() {
super("DamageIndicator", Category.RENDER);
Expand All @@ -27,12 +30,28 @@ public DamageIndicator() {
static ArrayList<Damage> indicators = new ArrayList<>();

public static void addIndicator(float x, float y, float z, float damage) {
// Cap so a disable mid-fight (listeners stop, eviction stops) cannot leave an unbounded list.
while (indicators.size() >= MAX_INDICATORS) {
indicators.remove(0);
}
indicators.add(new Damage(damage, x, y, z, 0f));
}

/** Drops retained entities and pending floats — world leave / module disable. */
public static void clearState() {
lastAttack = null;
indicators.clear();
}

MathTimer timer = new MathTimer();
float health = 0;

@Override
public void onDisable() {
super.onDisable();
clearState();
}

@Subscribe
public void onAttack(EventAttack e) {
if (e.target instanceof EntityLivingBase) {
Expand Down Expand Up @@ -72,8 +91,7 @@ public void onRender(EventRender3D event) {

public void doRender(Damage indicator) {
Minecraft mc = Minecraft.getMinecraft();
DecimalFormat df = new DecimalFormat("0.00");
String damage = df.format(-indicator.damage);
String damage = DAMAGE_FORMAT.format(-indicator.damage);
GL11.glPushAttrib(GL11.GL_ALPHA | GL11.GL_BLEND | GL11.GL_TEXTURE_2D | GL11.GL_LIGHTING | GL11.GL_DEPTH_TEST | GL11.GL_CULL_FACE);
GL11.glPushMatrix();
GL11.glEnable(3042);
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/top/fpsmaster/features/impl/render/MotionBlur.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ private boolean isUsingShader() {
public void onDisable() {
super.onDisable();
Minecraft.getMinecraft().entityRenderer.stopUseShader();
// Old-mode ping-pong FBOs are static and survive toggles; without an explicit delete the
// driver's framebuffer memory stays allocated until process exit.
deleteBlurBuffers();
}

private static void deleteBlurBuffers() {
if (blurBufferMain != null) {
blurBufferMain.deleteFramebuffer();
blurBufferMain = null;
}
if (blurBufferInto != null) {
blurBufferInto.deleteFramebuffer();
blurBufferInto = null;
}
}

public static void blur(float multiplier) {
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/top/fpsmaster/font/TextRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public final class TextRenderer {

private static final int GEOMETRY_CACHE_LIMIT = 512;

/** Soft ceiling for the scramble cache inside one obfuscation epoch. */
private static final int OBFUSCATED_CACHE_LIMIT = 256;

/** Upper bound for the {@link #widths} memo, kept in lockstep with the geometry cache. */
private static final int WIDTH_CACHE_LIMIT = 1024;

Expand Down Expand Up @@ -140,7 +143,13 @@ protected boolean removeEldestEntry(Map.Entry<String, Float> eldest) {
* are worthless the moment the epoch turns, and mixing them in would push live entries out of a
* bounded LRU to make room for ones that are already stale.
*/
private final Map<String, Recorded> obfuscatedCache = new HashMap<String, Recorded>();
private final Map<String, Recorded> obfuscatedCache =
new LinkedHashMap<String, Recorded>(64, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Recorded> eldest) {
return size() > OBFUSCATED_CACHE_LIMIT;
}
};

private long obfuscationEpochStarted;

Expand Down
25 changes: 23 additions & 2 deletions src/main/java/top/fpsmaster/minimap/Minimap.java
Original file line number Diff line number Diff line change
Expand Up @@ -781,9 +781,27 @@ private static void drawMyTexturedModalRect(final float x, final float y, final
tessellator.draw();
}

/**
* Last storage size uploaded into {@link #mapTexture}.
*
* <p>{@code glTexImage2D} reallocates the whole level. Calling it every HUD frame (512² RGB ≈
* 0.75MB) orphans driver pages faster than they are reclaimed — at 60 FPS that is multi-GB per
* minute and matches "十几 GB / 半小时" reports when the minimap is on. Allocate once (or when
* the buffer size changes), then update with {@code glTexSubImage2D}.
*/
private static int mapTextureStorageW = -1;
private static int mapTextureStorageH = -1;

public static void bindTextureBuffer(final ByteBuffer image, final int width, final int height, final int par0) {
GL11.glBindTexture(3553, par0);
GL11.glTexImage2D(3553, 0, 6407, width, height, 0, 6407, 5121, image);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, par0);
if (width != mapTextureStorageW || height != mapTextureStorageH) {
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, width, height, 0,
GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
mapTextureStorageW = width;
mapTextureStorageH = height;
}
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, width, height,
GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, image);
}

public static boolean shouldRenderEntity(final Entity e) {
Expand Down Expand Up @@ -821,6 +839,9 @@ public static boolean shouldRenderEntity(final Entity e) {
Minimap.triedFBO = false;
Minimap.loadedFBO = false;
Minimap.mapTexture = new DynamicTexture(InterfaceHandler.mapTextures);
// New GL texture id — force the next upload to allocate storage again.
mapTextureStorageW = -1;
mapTextureStorageH = -1;
}

public class MapLoader implements Runnable {
Expand Down
Loading
Loading