Skip to content

Commit

Permalink
fix: Update global render contexts on world change
Browse files Browse the repository at this point in the history
Fixes a memory leak that was caused by static render contexts holding
a reference to world objects. This approach now assigns a lifetime to each
render context and loads/unloads them on client world change.

If other mod authors desire to render entities in other worlds, they must
maintain their own render context.
  • Loading branch information
jellysquid3 committed Dec 17, 2020
1 parent eb664ec commit ef6c9bd
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public long get(int x, int y, int z) {
return word;
}

public void clear() {
public void clearCache() {
this.map.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import me.jellysquid.mods.sodium.client.render.chunk.data.ChunkRenderData;
import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPass;
import me.jellysquid.mods.sodium.client.render.chunk.passes.BlockRenderPassManager;
import me.jellysquid.mods.sodium.client.render.pipeline.context.GlobalRenderContext;
import me.jellysquid.mods.sodium.client.util.math.FrustumExtended;
import me.jellysquid.mods.sodium.client.world.ChunkStatusListener;
import me.jellysquid.mods.sodium.client.world.ChunkStatusListenerManager;
Expand Down Expand Up @@ -88,27 +89,47 @@ private SodiumWorldRenderer(MinecraftClient client) {
}

public void setWorld(ClientWorld world) {
// Check that the world is actually changing
if (this.world == world) {
return;
}

// If we have a world is already loaded, unload the renderer
if (this.world != null) {
this.unloadWorld();
}

// If we're loading a new world, load the renderer
if (world != null) {
this.loadWorld(world);
}
}

private void loadWorld(ClientWorld world) {
this.world = world;
this.loadedChunkPositions.clear();
this.globalBlockEntities.clear();

if (world == null) {
if (this.chunkRenderManager != null) {
this.chunkRenderManager.destroy();
this.chunkRenderManager = null;
}
GlobalRenderContext.createRenderContext(this.world);

if (this.chunkRenderBackend != null) {
this.chunkRenderBackend.delete();
this.chunkRenderBackend = null;
}
this.initRenderer();

this.loadedChunkPositions.clear();
} else {
this.initRenderer();
((ChunkStatusListenerManager) world.getChunkManager()).setListener(this);
}

private void unloadWorld() {
GlobalRenderContext.destroyRenderContext(this.world);

((ChunkStatusListenerManager) world.getChunkManager()).setListener(this);
if (this.chunkRenderManager != null) {
this.chunkRenderManager.destroy();
this.chunkRenderManager = null;
}

if (this.chunkRenderBackend != null) {
this.chunkRenderBackend.delete();
this.chunkRenderBackend = null;
}

this.loadedChunkPositions.clear();
this.globalBlockEntities.clear();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.jellysquid.mods.sodium.client.render.pipeline.context;

import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import me.jellysquid.mods.sodium.client.model.light.LightPipelineProvider;
import me.jellysquid.mods.sodium.client.model.light.cache.HashLightDataCache;
import me.jellysquid.mods.sodium.client.model.quad.blender.BiomeColorBlender;
Expand All @@ -8,10 +9,10 @@
import net.minecraft.client.MinecraftClient;
import net.minecraft.world.BlockRenderView;

import java.util.WeakHashMap;
import java.util.Map;

public class GlobalRenderContext {
private static final WeakHashMap<BlockRenderView, GlobalRenderContext> INSTANCES = new WeakHashMap<>();
private static final Map<BlockRenderView, GlobalRenderContext> INSTANCES = new Reference2ObjectOpenHashMap<>();

private final BlockRenderer blockRenderer;
private final HashLightDataCache lightCache;
Expand All @@ -31,17 +32,37 @@ public BlockRenderer getBlockRenderer() {
return this.blockRenderer;
}

private void resetCache() {
this.lightCache.clearCache();
}

public static GlobalRenderContext getInstance(BlockRenderView world) {
return INSTANCES.computeIfAbsent(world, GlobalRenderContext::createInstance);
GlobalRenderContext instance = INSTANCES.get(world);

if (instance == null) {
throw new IllegalStateException("No global renderer exists");
}

return instance;
}

private static GlobalRenderContext createInstance(BlockRenderView world) {
return new GlobalRenderContext(world);
public static void destroyRenderContext(BlockRenderView world) {
if (INSTANCES.remove(world) == null) {
throw new IllegalStateException("No render context exists for world: " + world);
}
}

public static void createRenderContext(BlockRenderView world) {
if (INSTANCES.containsKey(world)) {
throw new IllegalStateException("Render context already exists for world: " + world);
}

INSTANCES.put(world, new GlobalRenderContext(world));
}

public static void reset() {
public static void resetCaches() {
for (GlobalRenderContext context : INSTANCES.values()) {
context.lightCache.clear();
context.resetCache();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public class MixinWorldRenderer {
private void reset(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera,
GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f,
CallbackInfo ci) {
GlobalRenderContext.reset();
GlobalRenderContext.resetCaches();
}
}

0 comments on commit ef6c9bd

Please sign in to comment.