mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-27 21:37:56 +01:00
Add instances when chunks are built for rendering
- RIP ChunkIter, may you rule over the depths of hell in peace - Fix memory leak when instance worlds get reset - Server worlds are not flywheel worlds - Nothing to do on world load anymore
This commit is contained in:
parent
b4fe00a314
commit
aaaccc47ee
10 changed files with 31 additions and 126 deletions
|
@ -161,6 +161,8 @@ public class Backend {
|
||||||
public static boolean isFlywheelWorld(@Nullable LevelAccessor world) {
|
public static boolean isFlywheelWorld(@Nullable LevelAccessor world) {
|
||||||
if (world == null) return false;
|
if (world == null) return false;
|
||||||
|
|
||||||
|
if (!world.isClientSide()) return false;
|
||||||
|
|
||||||
if (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel()) return true;
|
if (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel()) return true;
|
||||||
|
|
||||||
return world == Minecraft.getInstance().level;
|
return world == Minecraft.getInstance().level;
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class Loader implements ResourceManagerReloadListener {
|
||||||
ClientLevel world = Minecraft.getInstance().level;
|
ClientLevel world = Minecraft.getInstance().level;
|
||||||
if (Backend.isFlywheelWorld(world)) {
|
if (Backend.isFlywheelWorld(world)) {
|
||||||
// TODO: looks like it might be good to have another event here
|
// TODO: looks like it might be good to have another event here
|
||||||
InstancedRenderDispatcher.loadAllInWorld(world);
|
InstancedRenderDispatcher.resetInstanceWorld(world);
|
||||||
CrumblingRenderer.reset();
|
CrumblingRenderer.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,6 +211,10 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processQueuedAdditions() {
|
protected void processQueuedAdditions() {
|
||||||
|
if (queuedAdditions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ArrayList<T> queued;
|
ArrayList<T> queued;
|
||||||
|
|
||||||
synchronized (queuedAdditions) {
|
synchronized (queuedAdditions) {
|
||||||
|
@ -218,7 +222,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
|
||||||
queuedAdditions.clear();
|
queuedAdditions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queued.size() > 0) {
|
if (!queued.isEmpty()) {
|
||||||
queued.forEach(this::addInternal);
|
queued.forEach(this::addInternal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,4 +308,10 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
|
||||||
invalidate();
|
invalidate();
|
||||||
instancedTiles.forEach(this::add);
|
instancedTiles.forEach(this::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void detachLightListeners() {
|
||||||
|
for (AbstractInstance value : instances.values()) {
|
||||||
|
LightUpdater.get(value.world).removeListener(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,8 @@ import com.jozufozu.flywheel.core.Contexts;
|
||||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||||
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
||||||
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
||||||
import com.jozufozu.flywheel.util.ChunkIter;
|
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientLevel;
|
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
@ -60,17 +58,8 @@ public class InstanceWorld {
|
||||||
*/
|
*/
|
||||||
public void delete() {
|
public void delete() {
|
||||||
engine.delete();
|
engine.delete();
|
||||||
}
|
entityInstanceManager.detachLightListeners();
|
||||||
|
tileEntityInstanceManager.detachLightListeners();
|
||||||
/**
|
|
||||||
* Instantiate all the necessary instances to render the given world.
|
|
||||||
*/
|
|
||||||
public void loadAll(ClientLevel world) {
|
|
||||||
ChunkIter.forEachChunk(world, chunk -> {
|
|
||||||
chunk.getBlockEntities().values().forEach(tileEntityInstanceManager::add);
|
|
||||||
});
|
|
||||||
world.entitiesForRendering()
|
|
||||||
.forEach(entityInstanceManager::add);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -88,13 +88,12 @@ public class InstancedRenderDispatcher {
|
||||||
ClientLevel world = event.getWorld();
|
ClientLevel world = event.getWorld();
|
||||||
if (Backend.getInstance()
|
if (Backend.getInstance()
|
||||||
.canUseInstancing() && world != null) {
|
.canUseInstancing() && world != null) {
|
||||||
loadAllInWorld(world);
|
resetInstanceWorld(world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadAllInWorld(ClientLevel world) {
|
public static void resetInstanceWorld(ClientLevel world) {
|
||||||
instanceWorlds.replace(world, InstanceWorld::delete)
|
instanceWorlds.replace(world, InstanceWorld::delete);
|
||||||
.loadAll(world);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,10 @@ package com.jozufozu.flywheel.event;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.Backend;
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
|
|
||||||
import com.jozufozu.flywheel.light.LightUpdater;
|
import com.jozufozu.flywheel.light.LightUpdater;
|
||||||
import com.jozufozu.flywheel.util.ChunkIter;
|
|
||||||
import com.jozufozu.flywheel.util.WorldAttached;
|
import com.jozufozu.flywheel.util.WorldAttached;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientLevel;
|
|
||||||
import net.minecraft.world.level.LevelAccessor;
|
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||||
import net.minecraftforge.event.TickEvent;
|
import net.minecraftforge.event.TickEvent;
|
||||||
|
@ -40,20 +36,9 @@ public class ForgeEvents {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
|
||||||
public static void onLoadWorld(WorldEvent.Load event) {
|
|
||||||
LevelAccessor world = event.getWorld();
|
|
||||||
|
|
||||||
if (Backend.isFlywheelWorld(world)) {
|
|
||||||
InstancedRenderDispatcher.loadAllInWorld((ClientLevel) world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void unloadWorld(WorldEvent.Unload event) {
|
public static void unloadWorld(WorldEvent.Unload event) {
|
||||||
LevelAccessor world = event.getWorld();
|
WorldAttached.invalidateWorld(event.getWorld());
|
||||||
ChunkIter._unload(world);
|
|
||||||
WorldAttached.invalidateWorld(world);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.Backend;
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
|
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
|
||||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
|
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
|
||||||
|
|
||||||
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
||||||
|
@ -20,11 +21,16 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
public class ChunkRebuildHooksMixin {
|
public class ChunkRebuildHooksMixin {
|
||||||
|
|
||||||
@Inject(method = "handleBlockEntity", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "handleBlockEntity", at = @At("HEAD"), cancellable = true)
|
||||||
private <E extends BlockEntity> void filterBlockEntity(ChunkRenderDispatcher.CompiledChunk compiledChunk, Set<BlockEntity> set, E be, CallbackInfo ci) {
|
private <E extends BlockEntity> void addAndFilterBEs(ChunkRenderDispatcher.CompiledChunk compiledChunk, Set<BlockEntity> set, E be, CallbackInfo ci) {
|
||||||
|
|
||||||
if (Backend.getInstance().canUseInstancing() && Backend.isFlywheelWorld(be.getLevel()) && InstancedRenderRegistry.getInstance()
|
if (Backend.getInstance().canUseInstancing() && Backend.isFlywheelWorld(be.getLevel())) {
|
||||||
.shouldSkipRender(be)) {
|
|
||||||
ci.cancel();
|
InstancedRenderRegistry registry = InstancedRenderRegistry.getInstance();
|
||||||
|
if (registry.canInstance(be.getType()))
|
||||||
|
InstancedRenderDispatcher.getTiles(be.getLevel()).queueAdd(be);
|
||||||
|
|
||||||
|
if (registry.shouldSkipRender(be))
|
||||||
|
ci.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
package com.jozufozu.flywheel.mixin;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
||||||
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import com.jozufozu.flywheel.util.ChunkIter;
|
|
||||||
|
|
||||||
import net.minecraft.client.multiplayer.ClientChunkCache;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In order to iterate over all loaded chunks, we do something absolutely foul.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* By stealing the reference to the backing array of the chunk storage when it is constructed, we gain 0 maintenance
|
|
||||||
* access to the full array of loaded chunks.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@OnlyIn(Dist.CLIENT)
|
|
||||||
@Mixin(targets = "net.minecraft.client.multiplayer.ClientChunkCache$Storage")
|
|
||||||
public class LeakChunkStorageArrayMixin {
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
AtomicReferenceArray<LevelChunk> chunks;
|
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("TAIL"))
|
|
||||||
private void leakBackingArray(ClientChunkCache outer, int chunkRadius, CallbackInfo ci) {
|
|
||||||
ChunkIter._putStorageReference(outer.getLevel(), chunks);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package com.jozufozu.flywheel.util;
|
|
||||||
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import net.minecraft.world.level.BlockGetter;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
|
||||||
import net.minecraftforge.api.distmarker.Dist;
|
|
||||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for iterating over all loaded chunks.
|
|
||||||
*/
|
|
||||||
@OnlyIn(Dist.CLIENT)
|
|
||||||
public class ChunkIter {
|
|
||||||
|
|
||||||
private static final WeakHashMap<BlockGetter, AtomicReferenceArray<LevelChunk>> storages = new WeakHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over all loaded chunks in a level.
|
|
||||||
*/
|
|
||||||
public static void forEachChunk(BlockGetter level, Consumer<LevelChunk> consumer) {
|
|
||||||
AtomicReferenceArray<LevelChunk> storage = storages.get(level);
|
|
||||||
|
|
||||||
if (storage == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < storage.length(); i++) {
|
|
||||||
LevelChunk chunk = storage.get(i);
|
|
||||||
if (chunk != null) {
|
|
||||||
consumer.accept(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INTERNAL MAINTENANCE METHODS BELOW
|
|
||||||
|
|
||||||
public static void _putStorageReference(BlockGetter level, AtomicReferenceArray<LevelChunk> storage) {
|
|
||||||
storages.put(level, storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void _unload(BlockGetter world) {
|
|
||||||
storages.remove(world);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
"required": true,
|
"required": true,
|
||||||
"minVersion": "0.8",
|
"minVersion": "0.8",
|
||||||
"package": "com.jozufozu.flywheel.mixin",
|
"package": "com.jozufozu.flywheel.mixin",
|
||||||
"compatibilityLevel": "JAVA_16",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"refmap": "flywheel.refmap.json",
|
"refmap": "flywheel.refmap.json",
|
||||||
"client": [
|
"client": [
|
||||||
"CancelEntityRenderMixin",
|
"CancelEntityRenderMixin",
|
||||||
|
@ -10,7 +10,6 @@
|
||||||
"FixFabulousDepthMixin",
|
"FixFabulousDepthMixin",
|
||||||
"InstanceAddMixin",
|
"InstanceAddMixin",
|
||||||
"InstanceRemoveMixin",
|
"InstanceRemoveMixin",
|
||||||
"LeakChunkStorageArrayMixin",
|
|
||||||
"LevelRendererAccessor",
|
"LevelRendererAccessor",
|
||||||
"PausedPartialTickAccessor",
|
"PausedPartialTickAccessor",
|
||||||
"RenderHooksMixin",
|
"RenderHooksMixin",
|
||||||
|
|
Loading…
Reference in a new issue