Skip to content

Commit 746865c

Browse files
committed
add UnfocusedFPS Limiter
1 parent f20dafb commit 746865c

File tree

6 files changed

+243
-20
lines changed

6 files changed

+243
-20
lines changed

src/main/java/io/github/axolotlclient/AxolotlClient.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222

2323
package io.github.axolotlclient;
2424

25+
import java.util.ArrayList;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.UUID;
29+
2530
import com.mojang.blaze3d.platform.GlStateManager;
31+
2632
import io.github.axolotlclient.AxolotlclientConfig.AxolotlClientConfigManager;
2733
import io.github.axolotlclient.AxolotlclientConfig.ConfigManager;
2834
import io.github.axolotlclient.AxolotlclientConfig.DefaultConfigManager;
@@ -44,6 +50,7 @@
4450
import io.github.axolotlclient.modules.scrollableTooltips.ScrollableTooltips;
4551
import io.github.axolotlclient.modules.sky.SkyResourceManager;
4652
import io.github.axolotlclient.modules.tnttime.TntTime;
53+
import io.github.axolotlclient.modules.unfocusedFpsLimiter.UnfocusedFpsLimiter;
4754
import io.github.axolotlclient.modules.zoom.Zoom;
4855
import io.github.axolotlclient.util.FeatureDisabler;
4956
import io.github.axolotlclient.util.Logger;
@@ -57,11 +64,6 @@
5764
import net.minecraft.resource.Resource;
5865
import net.minecraft.util.Identifier;
5966

60-
import java.util.ArrayList;
61-
import java.util.HashMap;
62-
import java.util.List;
63-
import java.util.UUID;
64-
6567
public class AxolotlClient implements ClientModInitializer {
6668

6769
public static String modid = "AxolotlClient";
@@ -136,6 +138,7 @@ private static void getModules() {
136138
modules.add(Particles.getInstance());
137139
modules.add(ScreenshotUtils.getInstance());
138140
modules.add(BeaconBeam.getInstance());
141+
modules.add(UnfocusedFpsLimiter.getInstance());
139142
}
140143

141144
private static void addExternalModules() {

src/main/java/io/github/axolotlclient/mixin/GameRendererMixin.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import io.github.axolotlclient.modules.hypixel.skyblock.Skyblock;
4848
import io.github.axolotlclient.modules.motionblur.MotionBlur;
4949
import io.github.axolotlclient.modules.sky.SkyboxManager;
50+
import io.github.axolotlclient.modules.unfocusedFpsLimiter.UnfocusedFpsLimiter;
5051
import io.github.axolotlclient.modules.zoom.Zoom;
5152
import net.minecraft.block.Block;
5253
import net.minecraft.block.material.Material;
@@ -267,4 +268,11 @@ public void updateRotation(ClientPlayerEntity entity, float yaw, float pitch) {
267268
public float freelook$prevPitch(Entity entity) {
268269
return Freelook.getInstance().pitch(entity.prevPitch);
269270
}
271+
272+
@Inject(method = "render", at = @At("HEAD"), cancellable = true)
273+
private void limitFpsOnLostFocus(float tickDelta, long nanoTime, CallbackInfo ci){
274+
if(!UnfocusedFpsLimiter.getInstance().checkForRender()){
275+
ci.cancel();
276+
}
277+
}
270278
}

src/main/java/io/github/axolotlclient/mixin/MinecraftClientAccessor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222

2323
package io.github.axolotlclient.mixin;
2424

25-
import lombok.experimental.Accessors;
26-
import net.minecraft.client.MinecraftClient;
27-
import net.minecraft.client.render.ClientTickTracker;
2825
import org.spongepowered.asm.mixin.Mixin;
2926
import org.spongepowered.asm.mixin.gen.Accessor;
3027

28+
import net.minecraft.client.MinecraftClient;
29+
import net.minecraft.client.render.ClientTickTracker;
30+
3131
@Mixin(MinecraftClient.class)
3232
public interface MinecraftClientAccessor {
3333

src/main/java/io/github/axolotlclient/mixin/MinecraftClientMixin.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222

2323
package io.github.axolotlclient.mixin;
2424

25+
import java.util.concurrent.locks.LockSupport;
26+
27+
import org.apache.logging.log4j.Logger;
28+
import org.lwjgl.input.Keyboard;
29+
import org.lwjgl.input.Mouse;
30+
import org.lwjgl.opengl.Display;
31+
import org.spongepowered.asm.mixin.Final;
32+
import org.spongepowered.asm.mixin.Mixin;
33+
import org.spongepowered.asm.mixin.Shadow;
34+
import org.spongepowered.asm.mixin.injection.At;
35+
import org.spongepowered.asm.mixin.injection.Inject;
36+
import org.spongepowered.asm.mixin.injection.Redirect;
37+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
38+
2539
import io.github.axolotlclient.AxolotlClient;
2640
import io.github.axolotlclient.NetworkHelper;
2741
import io.github.axolotlclient.modules.hud.HudManager;
@@ -39,17 +53,6 @@
3953
import net.minecraft.client.world.ClientWorld;
4054
import net.minecraft.entity.player.ClientPlayerEntity;
4155
import net.minecraft.world.level.LevelInfo;
42-
import org.apache.logging.log4j.Logger;
43-
import org.lwjgl.input.Keyboard;
44-
import org.lwjgl.input.Mouse;
45-
import org.lwjgl.opengl.Display;
46-
import org.spongepowered.asm.mixin.Final;
47-
import org.spongepowered.asm.mixin.Mixin;
48-
import org.spongepowered.asm.mixin.Shadow;
49-
import org.spongepowered.asm.mixin.injection.At;
50-
import org.spongepowered.asm.mixin.injection.Inject;
51-
import org.spongepowered.asm.mixin.injection.Redirect;
52-
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
5356

5457
@Mixin(MinecraftClient.class)
5558
public abstract class MinecraftClientMixin {
@@ -168,4 +171,10 @@ public void onResize(CallbackInfo ci) {
168171
Util.window = new Window(MinecraftClient.getInstance());
169172
HudManager.getInstance().refreshAllBounds();
170173
}
174+
175+
@Redirect(method = "runGameLoop", at = @At(value = "INVOKE", target = "Ljava/lang/Thread;yield()V"))
176+
private void waitForTasks(){
177+
LockSupport.parkNanos("waiting for tasks", 500_000);
178+
}
179+
171180
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright © 2021-2022 moehreag <[email protected]> & Contributors
3+
*
4+
* This file is part of AxolotlClient.
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*
20+
* For more information, see the LICENSE file.
21+
*/
22+
23+
package io.github.axolotlclient.modules.unfocusedFpsLimiter;
24+
25+
import java.util.concurrent.locks.LockSupport;
26+
27+
import org.jetbrains.annotations.Nullable;
28+
import org.lwjgl.input.Mouse;
29+
import org.lwjgl.opengl.Display;
30+
31+
import io.github.axolotlclient.AxolotlClient;
32+
import io.github.axolotlclient.AxolotlclientConfig.options.BooleanOption;
33+
import io.github.axolotlclient.AxolotlclientConfig.options.FloatOption;
34+
import io.github.axolotlclient.AxolotlclientConfig.options.IntegerOption;
35+
import io.github.axolotlclient.AxolotlclientConfig.options.OptionCategory;
36+
import io.github.axolotlclient.modules.AbstractModule;
37+
import lombok.Getter;
38+
import net.minecraft.client.MinecraftClient;
39+
import net.minecraft.client.sound.SoundCategory;
40+
41+
/**
42+
* This module is based on the mod DynamicFps by juliand665.
43+
* <p>Original License: MIT</p>
44+
* <a href="https://github.com/juliand665/dynamic-fps">Original Source Github</a>
45+
*/
46+
47+
public class UnfocusedFpsLimiter extends AbstractModule {
48+
49+
@Getter
50+
private static final UnfocusedFpsLimiter Instance = new UnfocusedFpsLimiter();
51+
52+
private final BooleanOption enabled = new BooleanOption("axolotlclient.enabled", false);
53+
private final BooleanOption reduceFPSWhenUnfocused = new BooleanOption("axolotlclient.reduceFPS", false);
54+
private final IntegerOption unfocusedFPS = new IntegerOption("axolotlclient.unfocusedFPS", 10, 0, 60);
55+
private final BooleanOption restoreOnHover = new BooleanOption("axolotlclient.restoreOnHover", true);
56+
private final FloatOption unfocusedVolumeMultiplier = new FloatOption("axolotlclient.unfocusedVolumeMultiplier", 0.25f, 0f, 1f);
57+
private final FloatOption hiddenVolumeMultiplier = new FloatOption("axolotlclient.hiddenVolumeMultiplier", 0f, 0f, 1f);
58+
private final BooleanOption runGCOnUnfocus = new BooleanOption("axolotlclient.runGCOnUnfocus", false);
59+
60+
@Override
61+
public void init() {
62+
OptionCategory category = new OptionCategory("axolotlclient.fpsLimiter");
63+
category.add(enabled, reduceFPSWhenUnfocused, unfocusedFPS, restoreOnHover, unfocusedVolumeMultiplier, hiddenVolumeMultiplier, runGCOnUnfocus);
64+
AxolotlClient.CONFIG.rendering.add(category);
65+
}
66+
67+
private boolean isFocused, isVisible, isHovered;
68+
private long lastRender;
69+
70+
public boolean checkForRender(){
71+
72+
if(!enabled.get()){
73+
return true;
74+
}
75+
76+
isFocused = Display.isActive();
77+
isVisible = Display.isVisible();
78+
isHovered = Mouse.isInsideWindow();
79+
80+
checkForStateChanges();
81+
82+
long currentTime = MinecraftClient.getTime();
83+
long timeSinceLastRender = currentTime - lastRender;
84+
85+
if (!checkForRender(timeSinceLastRender)) return false;
86+
87+
lastRender = currentTime;
88+
return true;
89+
}
90+
91+
private boolean wasFocused = true;
92+
private boolean wasVisible = true;
93+
94+
private void checkForStateChanges() {
95+
if (isFocused != wasFocused) {
96+
wasFocused = isFocused;
97+
if (isFocused) {
98+
onFocus();
99+
} else {
100+
onUnfocus();
101+
}
102+
}
103+
104+
if (isVisible != wasVisible) {
105+
wasVisible = isVisible;
106+
if (isVisible) {
107+
onAppear();
108+
} else {
109+
onDisappear();
110+
}
111+
}
112+
}
113+
114+
private void onFocus() {
115+
setVolumeMultiplier(1);
116+
}
117+
118+
private void onUnfocus() {
119+
if (isVisible) {
120+
setVolumeMultiplier(unfocusedVolumeMultiplier.get());
121+
}
122+
123+
if (runGCOnUnfocus.get()) {
124+
System.gc();
125+
}
126+
}
127+
128+
private void onAppear() {
129+
if (!isFocused) {
130+
setVolumeMultiplier(unfocusedVolumeMultiplier.get());
131+
}
132+
}
133+
134+
private void onDisappear() {
135+
setVolumeMultiplier(hiddenVolumeMultiplier.get());
136+
}
137+
138+
private void setVolumeMultiplier(float multiplier) {
139+
// setting the volume to 0 stops all sounds (including music), which we want to avoid if possible.
140+
boolean clientWillPause = !isFocused && client.options.pauseOnLostFocus && client.currentScreen == null;
141+
// if the client would pause anyway, we don't need to do anything because that will already pause all sounds.
142+
if (multiplier == 0 && clientWillPause) return;
143+
144+
float baseVolume = MinecraftClient.getInstance().options.getSoundVolume(SoundCategory.MASTER);
145+
MinecraftClient.getInstance().getSoundManager().updateSoundVolume(
146+
SoundCategory.MASTER,
147+
baseVolume * multiplier
148+
);
149+
}
150+
151+
// we always render one last frame before actually reducing FPS, so the hud text shows up instantly when forcing low fps.
152+
// additionally, this would enable mods which render differently while mc is inactive.
153+
private boolean hasRenderedLastFrame = false;
154+
private boolean checkForRender(long timeSinceLastRender) {
155+
Integer fpsOverride = fpsOverride();
156+
if (fpsOverride == null) {
157+
hasRenderedLastFrame = false;
158+
return true;
159+
}
160+
161+
if (!hasRenderedLastFrame) {
162+
// render one last frame before reducing, to make sure differences in this state show up instantly.
163+
hasRenderedLastFrame = true;
164+
return true;
165+
}
166+
167+
if (fpsOverride == 0) {
168+
idle(1000);
169+
return false;
170+
}
171+
172+
long frameTime = 1000 / fpsOverride;
173+
boolean shouldSkipRender = timeSinceLastRender < frameTime;
174+
if (!shouldSkipRender) return true;
175+
176+
idle(frameTime);
177+
return false;
178+
}
179+
180+
/**
181+
force minecraft to idle because otherwise we'll be busy checking for render again and again
182+
*/
183+
private void idle(long waitMillis) {
184+
// cap at 30 ms before we check again so user doesn't have to wait long after tabbing back in
185+
waitMillis = Math.min(waitMillis, 30);
186+
LockSupport.parkNanos("waiting to render", waitMillis * 1_000_000);
187+
}
188+
189+
@Nullable
190+
private Integer fpsOverride() {
191+
if (!isVisible) return 0;
192+
if (restoreOnHover.get() && isHovered) return null;
193+
if (reduceFPSWhenUnfocused.get() && !Display.isActive()) return unfocusedFPS.get();
194+
return null;
195+
}
196+
}

src/main/resources/assets/axolotlclient/lang/en_us.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"count.tooltip": "How much to increase the amount of particles spawned.<br>Only works on EmitterParticles <br>(all that spread out and consist of usually 16 single ones, <br>e.g. Crit Particles).",
9797
"cpshud": "CPS HUD",
9898
"cpskeybind": "CPS from Keybinds",
99+
"crash": "This option has been disabled <br>because it would crash your game.",
99100
"credits": "Credits",
100101
"creditsBGM": "BGM",
101102
"crosshair_type": "Type",
@@ -114,7 +115,6 @@
114115
"degreescolor": "Degrees Color",
115116
"deleteAction": "[Delete]",
116117
"delete_image": "Delete this Screenshot",
117-
"crash": "This option has been disabled <br>because it would crash your game.",
118118
"dynamic": "Dynamic",
119119
"dynamicFov": "Dynamic FOV",
120120
"dynamicrotation": "Dynamic Rotation",
@@ -124,6 +124,7 @@
124124
"external_modules": "External Modules",
125125
"firsttextcolor": "First Color",
126126
"format": "Format",
127+
"fpsLimiter": "UnfocusedFPS Limiter",
127128
"fpshud": "FPS HUD",
128129
"freelook": "Freelook",
129130
"fullBright": "FullBright",
@@ -139,6 +140,7 @@
139140
"heldtextcolor": "Text Color (when held)",
140141
"hiddenNameOthers": "Hide others as:",
141142
"hiddenNameSelf": "Hide yourself as:",
143+
"hiddenVolumeMultiplier": "Hidden Volume Multiplier",
142144
"hideOtherNames": "Hide others' names",
143145
"hideOtherNames.tooltip": "Hides other people's names.",
144146
"hideOtherSkins": "Hide others' skins",
@@ -224,13 +226,16 @@
224226
"rawMouseInput": "Raw Mouse Input",
225227
"rawMouseInput.tooltip": "Tries to remove the calculations done to the mouse position when changed.",
226228
"reachhud": "Reach HUD",
229+
"reduceFPS": "Reduce FPS",
227230
"refreshTime": "Refresh Delay",
228231
"rendering": "Rendering Options",
232+
"restoreOnHover": "Restore On Hover",
229233
"rightcps": "Show Rightclick Cps",
230234
"rotation": "Rotation",
231235
"rotationLocked": "Rotation Lock",
232236
"rotationLocked.tooltip": "Whether the player rotation is locked in place.",
233237
"rpc": "Discord RPC",
238+
"runGCOnUnfocus": "Run GC on Unfocus",
234239
"scale": "Scale",
235240
"scale.tooltip": "The scale of this element.",
236241
"scoreboardhud": "Scoreboard HUD",
@@ -303,6 +308,8 @@
303308
"topbackgroundcolor": "Backgroundcolor (Top)",
304309
"toppadding": "Top Padding",
305310
"tpshud": "TPS HUD",
311+
"unfocusedFPS": "Unfocused FPS",
312+
"unfocusedVolumeMultiplier": "Unfocused Volume Multiplier",
306313
"useShadows": "Force Shadows",
307314
"width": "Width",
308315
"zoom": "Zoom",

0 commit comments

Comments
 (0)