Skip to content

Commit cb35fcb

Browse files
committed
Add a texture expiry timer defaulting to 10 seconds.
This stops slideshows with 100s of images from consuming many GB of RAM to store all images, even long after they've been shown.
1 parent ebf6de3 commit cb35fcb

4 files changed

Lines changed: 74 additions & 10 deletions

File tree

src/main/java/alexiil/mc/mod/load/CustomLoadingScreen.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class CustomLoadingScreen {
3939
private static final Property PROP_CONFIG_RANDOMS;
4040
private static final Property PROP_WAIT;
4141
private static final Property PROP_FPS_LIMIT;
42+
private static final Property PROP_TEXTURE_CLEAR_INTERVAL;
4243
private static final Property PROP_DEBUG_RESOURCE_LOADING;
4344

4445
public static final boolean shouldWait;
@@ -48,6 +49,7 @@ public class CustomLoadingScreen {
4849
public static final boolean debugResourceLoading;
4950
public static final String customConfigPath;
5051
public static final int fpsLimit;
52+
public static final int textureClearInterval;
5153

5254
private static FrameDisplayer displayer;
5355

@@ -88,6 +90,15 @@ public class CustomLoadingScreen {
8890
PROP_FPS_LIMIT.setMaxValue(300);
8991
fpsLimit = Math.max(2, Math.min(300, PROP_FPS_LIMIT.getInt()));
9092

93+
PROP_TEXTURE_CLEAR_INTERVAL = CONFIG.get("performance", "texture_clear_interval", 10);
94+
PROP_TEXTURE_CLEAR_INTERVAL.setMinValue(0);
95+
PROP_TEXTURE_CLEAR_INTERVAL.setComment(
96+
"The interval, in seconds, after which textures will be deleted to save memory, at the cost of additional disk reading if they are used later."
97+
+ "\nSet debug.resource_loading to true to log when this occurs."
98+
+ "\nSet to 0 to disable texture clearing."
99+
);
100+
textureClearInterval = Math.max(0, PROP_TEXTURE_CLEAR_INTERVAL.getInt(10));
101+
91102
useCustom = PROP_USE_CUSTOM.getBoolean();
92103

93104
String customName = PROP_CONFIG.getString();

src/main/java/alexiil/mc/mod/load/json/subtypes/JsonRenderSlideshow.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,14 @@ protected BakedRender actuallyBake(FunctionContext context) throws InvalidExpres
5252
String l = image.replace("#", Integer.toString(i));
5353

5454
ResourceLocation r = new ResourceLocation(l);
55-
try {
56-
InputStream stream = TextureLoader.openResourceStream(r);
55+
try (InputStream stream = TextureLoader.openResourceStream(r)) {
5756
if (stream == null) {
5857
if (i == 0) {
5958
continue;
6059
} else {
6160
break;
6261
}
6362
}
64-
stream.close();
6563
} catch (FileNotFoundException fnfe) {
6664
if (i == 0) {
6765
continue;

src/main/java/alexiil/mc/mod/load/render/MinecraftDisplayerRenderer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ public void render() {
124124
GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
125125
GL11.glColor4f(1, 1, 1, 1);
126126

127+
textureManager.onFrame();
128+
127129
if (GLContext.getCapabilities().GL_KHR_debug) {
128130
KHRDebug.glPopDebugGroup();
129131
}
Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,47 @@
11
package alexiil.mc.mod.load.render;
22

3-
import java.util.HashSet;
4-
import java.util.Set;
3+
import java.util.ArrayList;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
57

68
import net.minecraft.client.renderer.texture.ITextureObject;
79
import net.minecraft.client.renderer.texture.ITickableTextureObject;
810
import net.minecraft.client.renderer.texture.TextureManager;
911
import net.minecraft.client.resources.IResourceManager;
1012
import net.minecraft.util.ResourceLocation;
1113

14+
import alexiil.mc.mod.load.CLSLog;
15+
import alexiil.mc.mod.load.CustomLoadingScreen;
16+
1217
public class TextureManagerCLS extends TextureManager {
1318

14-
private final Set<ResourceLocation> textures = new HashSet<>();
19+
/** Map of texture to last access tick. */
20+
private final Map<ResourceLocation, Long> textures = new HashMap<>();
21+
// If we're forced to put a full object into the map value, we may as well intern them by only creating them once
22+
private Long currentTime = System.currentTimeMillis();
1523

1624
public TextureManagerCLS(IResourceManager resourceManager) {
1725
super(resourceManager);
1826
}
1927

28+
private void onTextureAccess(ResourceLocation resource) {
29+
textures.put(resource, currentTime);
30+
}
31+
2032
@Override
2133
public void bindTexture(ResourceLocation resource) {
2234
super.bindTexture(resource);
23-
textures.add(resource);
35+
onTextureAccess(resource);
36+
}
37+
38+
@Override
39+
public ITextureObject getTexture(ResourceLocation resource) {
40+
ITextureObject obj = super.getTexture(resource);
41+
if (obj != null) {
42+
onTextureAccess(resource);
43+
}
44+
return obj;
2445
}
2546

2647
@Override
@@ -31,19 +52,51 @@ public void deleteTexture(ResourceLocation textureLocation) {
3152

3253
@Override
3354
public boolean loadTexture(ResourceLocation textureLocation, ITextureObject textureObj) {
34-
textures.add(textureLocation);
55+
onTextureAccess(textureLocation);
3556
return super.loadTexture(textureLocation, textureObj);
3657
}
3758

3859
@Override
3960
public boolean loadTickableTexture(ResourceLocation textureLocation, ITickableTextureObject textureObj) {
40-
textures.add(textureLocation);
61+
onTextureAccess(textureLocation);
4162
return super.loadTickableTexture(textureLocation, textureObj);
4263
}
4364

4465
public void deleteAll() {
45-
for (ResourceLocation location : textures.toArray(new ResourceLocation[0])) {
66+
for (ResourceLocation location : textures.keySet().toArray(new ResourceLocation[0])) {
4667
deleteTexture(location);
4768
}
4869
}
70+
71+
public void onFrame() {
72+
if (CustomLoadingScreen.textureClearInterval == 0) {
73+
return;
74+
}
75+
76+
Long last = currentTime;
77+
long next = System.currentTimeMillis();
78+
79+
if (last + 1000 < next) {
80+
return;
81+
}
82+
83+
currentTime = next;
84+
85+
long minTime = currentTime - (CustomLoadingScreen.textureClearInterval * 1000);
86+
87+
List<ResourceLocation> toRemove = new ArrayList<>();
88+
89+
for (Map.Entry<ResourceLocation, Long> entry : textures.entrySet()) {
90+
if (entry.getValue() < minTime) {
91+
toRemove.add(entry.getKey());
92+
}
93+
}
94+
95+
for (ResourceLocation tex : toRemove) {
96+
if (CustomLoadingScreen.debugResourceLoading) {
97+
CLSLog.info("[debug] Automatically deleting texture " + tex);
98+
}
99+
deleteTexture(tex);
100+
}
101+
}
49102
}

0 commit comments

Comments
 (0)