Fix crash on resource reload

- Properly delete MaterialManagers and ShaderContexts
 - Reload renderers on resource reload
 - More utility methods in WorldAttached
This commit is contained in:
JozsefA 2021-06-24 01:20:03 -07:00
parent a21377b9c6
commit 5c1f186264
13 changed files with 127 additions and 56 deletions

View file

@ -24,6 +24,8 @@ import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class Backend {
public static final Logger log = LogManager.getLogger(Backend.class);
@ -175,8 +177,14 @@ public class Backend {
/**
* Used to avoid calling Flywheel functions on (fake) worlds that don't specifically support it.
*/
public static boolean isFlywheelWorld(IWorld world) {
return (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel()) || world == Minecraft.getInstance().world;
public static boolean isFlywheelWorld(@Nullable IWorld world) {
if (world == null)
return false;
if (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel())
return true;
return world == Minecraft.getInstance().world;
}
public static boolean isGameActive() {

View file

@ -58,6 +58,7 @@ public abstract class ShaderContext<P extends GlProgram> implements IShaderConte
@Override
public void delete() {
programs.values().forEach(IMultiProgram::delete);
programs.clear();
}
/**

View file

@ -19,6 +19,15 @@ import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.event.ForgeEvents;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
import net.minecraftforge.event.world.WorldEvent;
import org.lwjgl.system.MemoryUtil;
import com.google.common.collect.Lists;
@ -89,9 +98,14 @@ public class ShaderSources implements ISelectiveResourceReloadListener {
}
Backend.log.info("Loaded all shader programs.");
// no need to hog all that memory
shaderSource.clear();
ClientWorld world = Minecraft.getInstance().world;
if (Backend.isFlywheelWorld(world)) {
InstancedRenderDispatcher.loadAllInWorld(world);
}
}
}
}

View file

@ -18,7 +18,9 @@ public abstract class GlObject {
protected final void checkHandle() {
if (!this.isHandleValid()) {
throw new IllegalStateException("Handle is not valid");
String descriptor = getDescriptor();
String message = (descriptor == null ? "" : (descriptor + " ")) + "handle is not valid.";
throw new IllegalStateException(message);
}
}
@ -32,7 +34,9 @@ public abstract class GlObject {
public void delete() {
if (!isHandleValid()) {
throw new IllegalStateException("Handle already deleted.");
String descriptor = getDescriptor();
String message = (descriptor == null ? "" : (descriptor + " ")) + "handle already deleted.";
throw new IllegalStateException(message);
}
deleteInternal(handle);
@ -40,4 +44,8 @@ public abstract class GlObject {
}
protected abstract void deleteInternal(int handle);
protected String getDescriptor() {
return "";
}
}

View file

@ -74,4 +74,8 @@ public abstract class GlProgram extends GlObject {
glDeleteProgram(handle);
}
@Override
public String toString() {
return "program " + name;
}
}

View file

@ -37,7 +37,7 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
materialManager.onOriginShift(this);
materialManager.addListener(this);
}
public void tick(double cameraX, double cameraY, double cameraZ) {

View file

@ -40,7 +40,6 @@ import net.minecraft.util.LazyValue;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.event.TickEvent;
@ -124,15 +123,7 @@ public class InstancedRenderDispatcher {
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientWorld world = event.getWorld();
if (Backend.getInstance().canUseInstancing() && world != null) {
Contexts.WORLD.getMaterialManager(world).delete();
TileInstanceManager tiles = getTiles(world);
tiles.invalidate();
world.loadedTileEntityList.forEach(tiles::add);
EntityInstanceManager entities = getEntities(world);
entities.invalidate();
world.getAllEntities().forEach(entities::add);
loadAllInWorld(world);
}
}
@ -188,4 +179,14 @@ public class InstancedRenderDispatcher {
if (breaking != null)
glBindTexture(GL_TEXTURE_2D, breaking.getGlTextureId());
}
public static void loadAllInWorld(ClientWorld world) {
Contexts.WORLD.getMaterialManager(world).delete();
TileInstanceManager tiles = tileInstanceManager.replace(world);
world.loadedTileEntityList.forEach(tiles::add);
EntityInstanceManager entities = entityInstanceManager.replace(world);
world.getAllEntities().forEach(entities::add);
}
}

View file

@ -91,8 +91,11 @@ public class MaterialManager<P extends WorldProgram> {
public void delete() {
atlasMaterials.values().forEach(InstanceMaterial::delete);
materials.values().stream().flatMap(m -> m.values().stream()).forEach(InstanceMaterial::delete);
atlasMaterials.clear();
atlasRenderers.clear();
materials.clear();
}
@SuppressWarnings("unchecked")
@ -131,7 +134,7 @@ public class MaterialManager<P extends WorldProgram> {
return originCoordinate;
}
public void onOriginShift(OriginShiftListener listener) {
public void addListener(OriginShiftListener listener) {
listeners.add(listener);
}

View file

@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -21,7 +21,6 @@ import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.loading.ShaderTransformer;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.IMultiProgram;
import com.jozufozu.flywheel.core.shader.StateSensitiveMultiProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.util.WorldAttached;
@ -38,7 +37,7 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
protected Supplier<Stream<ResourceLocation>> specStream;
protected TemplateFactory templateFactory;
private final WorldAttached<MaterialManager<P>> materialManager = new WorldAttached<>($ -> new MaterialManager<>(this));
private final WorldAttached<MaterialManager<P>> worldAttachedMMs = new WorldAttached<>($ -> new MaterialManager<>(this));
private final Map<ShaderType, ResourceLocation> builtins = new EnumMap<>(ShaderType.class);
private final Map<ShaderType, String> builtinSources = new EnumMap<>(ShaderType.class);
@ -71,7 +70,7 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
}
public MaterialManager<P> getMaterialManager(IWorld world) {
return materialManager.get(world);
return worldAttachedMMs.get(world);
}
public WorldContext<P> withSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
@ -89,8 +88,6 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
@Override
public void load() {
programs.values().forEach(IMultiProgram::delete);
programs.clear();
Backend.log.info("Loading context '{}'", name);
@ -130,7 +127,7 @@ public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
public void delete() {
super.delete();
materialManager.forEach(MaterialManager::delete);
worldAttachedMMs.empty(MaterialManager::delete);
}
@Override

View file

@ -49,10 +49,19 @@ public class ExtensibleGlProgram extends GlProgram {
@Override
public String toString() {
return "ExtensibleGlProgram{" +
"name=" + name +
", extensions=" + extensions +
'}';
StringBuilder builder = new StringBuilder();
builder.append("program ")
.append(name)
.append('[');
for (IExtensionInstance extension : extensions) {
builder.append(extension)
.append('+');
}
builder.append(']');
return builder.toString();
}
/**

View file

@ -4,8 +4,6 @@ import java.util.ArrayList;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
@ -42,15 +40,8 @@ public class ForgeEvents {
IWorld world = event.getWorld();
if (Backend.isFlywheelWorld(world)) {
ClientWorld clientWorld = (ClientWorld) world;
TileInstanceManager tiles = InstancedRenderDispatcher.getTiles(world);
tiles.invalidate();
clientWorld.loadedTileEntityList.forEach(tiles::add);
EntityInstanceManager entities = InstancedRenderDispatcher.getEntities(world);
entities.invalidate();
clientWorld.getAllEntities().forEach(entities::add);
InstancedRenderDispatcher.loadAllInWorld((ClientWorld) world);
}
}
}

View file

@ -2,19 +2,21 @@ package com.jozufozu.flywheel.util;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.world.IWorld;
public class WorldAttached<T> {
Map<IWorld, T> attached;
private final WorldAttacher<T> factory;
private final Function<IWorld, T> factory;
public WorldAttached(WorldAttacher<T> factory) {
public WorldAttached(Function<IWorld, T> factory) {
this.factory = factory;
attached = new HashMap<>();
}
@ -24,7 +26,7 @@ public class WorldAttached<T> {
T t = attached.get(world);
if (t != null)
return t;
T entry = factory.attach(world);
T entry = factory.apply(world);
put(world, entry);
return entry;
}
@ -33,20 +35,46 @@ public class WorldAttached<T> {
attached.put(world, entry);
}
public void forEach(Consumer<T> consumer) {
/**
* Replaces the entry with a new one from the factory and returns the new entry.
*/
@Nonnull
public T replace(IWorld world) {
attached.remove(world);
return get(world);
}
/**
* Replaces the entry with a new one from the factory and returns the new entry.
*/
@Nonnull
public T replace(IWorld world, Consumer<T> finalizer) {
T remove = attached.remove(world);
finalizer.accept(remove);
return get(world);
}
/**
* Deletes all entries after calling a function on them.
*
* @param finalizer Do something with all of the world-value pairs
*/
public void empty(BiConsumer<IWorld, T> finalizer) {
attached.forEach(finalizer);
attached.clear();
}
/**
* Deletes all entries after calling a function on them.
*
* @param finalizer Do something with all of the values
*/
public void empty(Consumer<T> finalizer) {
attached.values()
.forEach(consumer);
.forEach(finalizer);
attached.clear();
}
@FunctionalInterface
public interface WorldAttacher<T> extends Function<IWorld, T> {
@Nonnull
T attach(IWorld world);
@Override
default T apply(IWorld world) {
return attach(world);
}
}
}