diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 08fd9766c..13b8c46f9 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -28,6 +28,7 @@ import com.jozufozu.flywheel.event.ForgeEvents; import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor; import com.jozufozu.flywheel.vanilla.VanillaInstances; +import com.jozufozu.flywheel.vanilla.effect.ExampleEffect; import com.mojang.logging.LogUtils; import net.minecraft.commands.synchronization.ArgumentTypes; @@ -108,6 +109,8 @@ public class Flywheel { modEventBus.addListener(StitchedSprite::onTextureStitchPre); modEventBus.addListener(StitchedSprite::onTextureStitchPost); + // forgeEventBus.addListener(ExampleEffect::spawn); + LayoutShaders.init(); InstanceShaders.init(); Contexts.init(); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/Engine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/Engine.java index 37f782c11..13416bedb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/Engine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/Engine.java @@ -5,5 +5,7 @@ import java.util.List; import com.jozufozu.flywheel.api.InstancerManager; public interface Engine extends RenderDispatcher, InstancerManager { + void attachManagers(InstanceManager... listener); + void addDebugInfo(List info); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java index a2364ccbe..3a01acd34 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java @@ -1,15 +1,9 @@ package com.jozufozu.flywheel.backend.instancing; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; -import org.jetbrains.annotations.Nullable; - -import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.api.instance.DynamicInstance; import com.jozufozu.flywheel.api.instance.TickableInstance; import com.jozufozu.flywheel.backend.Backend; @@ -20,37 +14,38 @@ import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.light.LightUpdater; import com.mojang.math.Vector3f; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; public abstract class InstanceManager { - public final InstancerManager instancerManager; - private final Set queuedAdditions; private final Set queuedUpdates; - protected final Map instances; - protected final Object2ObjectOpenHashMap tickableInstances; - protected final Object2ObjectOpenHashMap dynamicInstances; - protected DistanceUpdateLimiter frame; protected DistanceUpdateLimiter tick; - public InstanceManager(InstancerManager instancerManager) { - this.instancerManager = instancerManager; + public InstanceManager() { this.queuedUpdates = new HashSet<>(64); this.queuedAdditions = new HashSet<>(64); - this.instances = new HashMap<>(); - - this.dynamicInstances = new Object2ObjectOpenHashMap<>(); - this.tickableInstances = new Object2ObjectOpenHashMap<>(); frame = createUpdateLimiter(); tick = createUpdateLimiter(); } + public abstract Storage getStorage(); + + /** + * Is the given object currently capable of being instanced? + * + *

+ * This won't be the case for TEs or entities that are outside of loaded chunks. + *

+ * + * @return true if the object is currently capable of being instanced. + */ + protected abstract boolean canCreateInstance(T obj); + protected DistanceUpdateLimiter createUpdateLimiter() { if (FlwConfig.get().limitUpdates()) { return new BandedPrimeLimiter(); @@ -65,30 +60,9 @@ public abstract class InstanceManager { * @return The object count. */ public int getObjectCount() { - return instances.size(); + return getStorage().getObjectCount(); } - /** - * Is the given object capable of being instanced at all? - * - * @return false if on object cannot be instanced. - */ - protected abstract boolean canInstance(T obj); - - /** - * Is the given object currently capable of being instanced? - * - *

- * This won't be the case for TEs or entities that are outside of loaded chunks. - *

- * - * @return true if the object is currently capable of being instanced. - */ - protected abstract boolean canCreateInstance(T obj); - - @Nullable - protected abstract AbstractInstance createRaw(T obj); - /** * Ticks the InstanceManager. * @@ -107,14 +81,14 @@ public abstract class InstanceManager { int cY = (int) cameraY; int cZ = (int) cameraZ; - ArrayList instances = new ArrayList<>(tickableInstances.values()); + var instances = getStorage().getInstancesForTicking(); int incr = 500; int size = instances.size(); int start = 0; while (start < size) { int end = Math.min(start + incr, size); - List sub = instances.subList(start, end); + var sub = instances.subList(start, end); taskEngine.submit(() -> { for (TickableInstance instance : sub) { tickInstance(cX, cY, cZ, instance); @@ -154,14 +128,14 @@ public abstract class InstanceManager { int cY = (int) camera.getPosition().y; int cZ = (int) camera.getPosition().z; - ArrayList instances = new ArrayList<>(dynamicInstances.values()); + var instances = getStorage().getInstancesForUpdate(); int incr = 500; int size = instances.size(); int start = 0; while (start < size) { int end = Math.min(start + incr, size); - List sub = instances.subList(start, end); + var sub = instances.subList(start, end); taskEngine.submit(() -> { for (DynamicInstance dyn : sub) { updateInstance(dyn, lookX, lookY, lookZ, cX, cY, cZ); @@ -197,13 +171,19 @@ public abstract class InstanceManager { public void add(T obj) { if (!Backend.isOn()) return; - if (canInstance(obj)) { - addInternal(obj); + if (canCreateInstance(obj)) { + getStorage().add(obj); } } public void queueAdd(T obj) { - if (!Backend.isOn()) return; + if (!Backend.isOn()) { + return; + } + + if (!canCreateInstance(obj)) { + return; + } synchronized (queuedAdditions) { queuedAdditions.add(obj); @@ -212,6 +192,11 @@ public abstract class InstanceManager { public void queueUpdate(T obj) { if (!Backend.isOn()) return; + + if (!canCreateInstance(obj)) { + return; + } + synchronized (queuedUpdates) { queuedUpdates.add(obj); } @@ -231,45 +216,21 @@ public abstract class InstanceManager { public void update(T obj) { if (!Backend.isOn()) return; - if (canInstance(obj)) { - AbstractInstance instance = getInstance(obj); - - if (instance != null) { - - // resetting instances is by default used to handle block state changes. - if (instance.shouldReset()) { - // delete and re-create the instance. - // resetting an instance supersedes updating it. - removeInternal(obj, instance); - createInternal(obj); - } else { - instance.update(); - } - } + if (canCreateInstance(obj)) { + getStorage().update(obj); } } public void remove(T obj) { if (!Backend.isOn()) return; - if (canInstance(obj)) { - AbstractInstance instance = getInstance(obj); - if (instance != null) removeInternal(obj, instance); + if (canCreateInstance(obj)) { + getStorage().remove(obj); } } public void invalidate() { - instances.values().forEach(AbstractInstance::remove); - instances.clear(); - dynamicInstances.clear(); - tickableInstances.clear(); - } - - @Nullable - protected AbstractInstance getInstance(I obj) { - if (!Backend.isOn()) return null; - - return instances.get(obj); + getStorage().invalidate(); } protected void processQueuedAdditions() { @@ -285,7 +246,7 @@ public abstract class InstanceManager { } if (!queued.isEmpty()) { - queued.forEach(this::addInternal); + queued.forEach(getStorage()::add); } } @@ -298,75 +259,16 @@ public abstract class InstanceManager { } if (queued.size() > 0) { - queued.forEach(this::update); - } - } - - protected void addInternal(T obj) { - if (!Backend.isOn()) return; - - AbstractInstance instance = instances.get(obj); - - if (instance == null && canCreateInstance(obj)) { - createInternal(obj); - } - } - - protected void removeInternal(T obj, AbstractInstance instance) { - instance.remove(); - instances.remove(obj); - dynamicInstances.remove(obj); - tickableInstances.remove(obj); - LightUpdater.get(instance.level) - .removeListener(instance); - } - - @Nullable - protected AbstractInstance createInternal(T obj) { - AbstractInstance renderer = createRaw(obj); - - if (renderer != null) { - setup(obj, renderer); - instances.put(obj, renderer); - } - - return renderer; - } - - private void setup(T obj, AbstractInstance renderer) { - renderer.init(); - renderer.updateLight(); - LightUpdater.get(renderer.level) - .addListener(renderer); - if (renderer instanceof TickableInstance r) { - tickableInstances.put(obj, r); - r.tick(); - } - - if (renderer instanceof DynamicInstance r) { - dynamicInstances.put(obj, r); - r.beginFrame(); + queued.forEach(getStorage()::update); } } public void onOriginShift() { - dynamicInstances.clear(); - tickableInstances.clear(); - instances.replaceAll((obj, instance) -> { - instance.remove(); - - AbstractInstance out = createRaw(obj); - - if (out != null) { - setup(obj, out); - } - - return out; - }); + getStorage().recreateAll(); } - public void detachLightListeners() { - for (AbstractInstance value : instances.values()) { + public void delete() { + for (AbstractInstance value : getStorage().allInstances()) { LightUpdater.get(value.level).removeListener(value); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java index 286a3726d..ca55fa1da 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java @@ -5,13 +5,15 @@ import com.jozufozu.flywheel.api.instance.TickableInstance; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager; +import com.jozufozu.flywheel.backend.instancing.effect.Effect; +import com.jozufozu.flywheel.backend.instancing.effect.EffectInstanceManager; import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager; import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; import com.jozufozu.flywheel.core.Contexts; import com.jozufozu.flywheel.core.RenderContext; -import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.util.ClientLevelExtension; +import com.jozufozu.flywheel.vanilla.effect.ExampleEffect; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; @@ -29,47 +31,47 @@ import net.minecraft.world.level.block.entity.BlockEntity; */ public class InstanceWorld { protected final Engine engine; - protected final InstanceManager entityInstanceManager; - protected final InstanceManager blockEntityInstanceManager; + protected final InstanceManager entities; + protected final InstanceManager blockEntities; public final ParallelTaskEngine taskEngine; + private final InstanceManager effects; public static InstanceWorld create(LevelAccessor level) { - return switch (Backend.getBackendType()) { - case INSTANCING -> { - InstancingEngine engine = new InstancingEngine<>(Contexts.WORLD); - - var entityInstanceManager = new EntityInstanceManager(engine); - var blockEntityInstanceManager = new BlockEntityInstanceManager(engine); - - engine.attachManager(entityInstanceManager); - engine.attachManager(blockEntityInstanceManager); - yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager); - } - case BATCHING -> { - var engine = new BatchingEngine(); - var entityInstanceManager = new EntityInstanceManager(engine); - var blockEntityInstanceManager = new BlockEntityInstanceManager(engine); - - yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager); - } - default -> throw new IllegalArgumentException("Unknown engine type"); + var engine = switch (Backend.getBackendType()) { + case INSTANCING -> new InstancingEngine<>(Contexts.WORLD); + case BATCHING -> new BatchingEngine(); + case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off."); }; + + var entities = new EntityInstanceManager(engine); + var blockEntities = new BlockEntityInstanceManager(engine); + var effects = new EffectInstanceManager(engine); + + engine.attachManagers(entities, blockEntities, effects); + + return new InstanceWorld(engine, entities, blockEntities, effects); } - public InstanceWorld(Engine engine, InstanceManager entityInstanceManager, InstanceManager blockEntityInstanceManager) { + public InstanceWorld(Engine engine, InstanceManager entities, InstanceManager blockEntities, + InstanceManager effects) { this.engine = engine; - this.entityInstanceManager = entityInstanceManager; - this.blockEntityInstanceManager = blockEntityInstanceManager; + this.entities = entities; + this.blockEntities = blockEntities; + this.effects = effects; this.taskEngine = Backend.getTaskEngine(); } - public InstanceManager getEntityInstanceManager() { - return entityInstanceManager; + public InstanceManager getEntities() { + return entities; } - public InstanceManager getBlockEntityInstanceManager() { - return blockEntityInstanceManager; + public InstanceManager getEffects() { + return effects; + } + + public InstanceManager getBlockEntities() { + return blockEntities; } /** @@ -77,8 +79,8 @@ public class InstanceWorld { */ public void delete() { engine.delete(); - entityInstanceManager.detachLightListeners(); - blockEntityInstanceManager.detachLightListeners(); + entities.delete(); + blockEntities.delete(); } /** @@ -96,8 +98,9 @@ public class InstanceWorld { taskEngine.syncPoint(); if (!shifted) { - blockEntityInstanceManager.beginFrame(taskEngine, camera); - entityInstanceManager.beginFrame(taskEngine, camera); + blockEntities.beginFrame(taskEngine, camera); + entities.beginFrame(taskEngine, camera); + effects.beginFrame(taskEngine, camera); } engine.beginFrame(taskEngine, camera); @@ -115,8 +118,13 @@ public class InstanceWorld { if (renderViewEntity == null) return; - blockEntityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ()); - entityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ()); + double x = renderViewEntity.getX(); + double y = renderViewEntity.getY(); + double z = renderViewEntity.getZ(); + + blockEntities.tick(taskEngine, x, y, z); + entities.tick(taskEngine, x, y, z); + effects.tick(taskEngine, x, y, z); } /** @@ -148,7 +156,7 @@ public class InstanceWorld { // Block entities are loaded while chunks are baked. // Entities are loaded with the world, so when chunks are reloaded they need to be re-added. ClientLevelExtension.getAllLoadedEntities(world) - .forEach(entityInstanceManager::add); + .forEach(entities::add); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java index 783cf2efe..ab05356a4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java @@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.instancing; import java.util.List; import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.backend.instancing.effect.Effect; import com.jozufozu.flywheel.config.FlwCommands; import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.core.RenderContext; @@ -10,6 +11,7 @@ import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.util.AnimationTickHolder; import com.jozufozu.flywheel.util.WorldAttached; +import com.jozufozu.flywheel.vanilla.effect.ExampleEffect; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; @@ -30,7 +32,7 @@ public class InstancedRenderDispatcher { public static void enqueueUpdate(BlockEntity blockEntity) { if (Backend.isOn() && blockEntity.hasLevel() && blockEntity.getLevel() instanceof ClientLevel) { instanceWorlds.get(blockEntity.getLevel()) - .getBlockEntityInstanceManager() + .getBlockEntities() .queueUpdate(blockEntity); } } @@ -42,17 +44,21 @@ public class InstancedRenderDispatcher { public static void enqueueUpdate(Entity entity) { if (Backend.isOn()) { instanceWorlds.get(entity.level) - .getEntityInstanceManager() + .getEntities() .queueUpdate(entity); } } public static InstanceManager getBlockEntities(LevelAccessor world) { - return getInstanceWorld(world).getBlockEntityInstanceManager(); + return getInstanceWorld(world).getBlockEntities(); } public static InstanceManager getEntities(LevelAccessor world) { - return getInstanceWorld(world).getEntityInstanceManager(); + return getInstanceWorld(world).getEntities(); + } + + public static InstanceManager getEffects(LevelAccessor world) { + return getInstanceWorld(world).getEffects(); } /** @@ -119,7 +125,7 @@ public class InstancedRenderDispatcher { InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level); debug.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString()); - debug.add("B: " + instanceWorld.blockEntityInstanceManager.getObjectCount() + ", E: " + instanceWorld.entityInstanceManager.getObjectCount()); + debug.add("B: " + instanceWorld.blockEntities.getObjectCount() + ", E: " + instanceWorld.entities.getObjectCount()); instanceWorld.engine.addDebugInfo(debug); } else { debug.add("Disabled"); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/One2OneStorage.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/One2OneStorage.java new file mode 100644 index 000000000..1c615176d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/One2OneStorage.java @@ -0,0 +1,148 @@ +package com.jozufozu.flywheel.backend.instancing; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.api.InstancerManager; +import com.jozufozu.flywheel.api.instance.DynamicInstance; +import com.jozufozu.flywheel.api.instance.TickableInstance; +import com.jozufozu.flywheel.light.LightUpdater; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +public abstract class One2OneStorage implements Storage { + private final Map instances; + private final Object2ObjectOpenHashMap tickableInstances; + private final Object2ObjectOpenHashMap dynamicInstances; + protected final InstancerManager instancerManager; + + public One2OneStorage(InstancerManager instancerManager) { + this.instancerManager = instancerManager; + this.instances = new HashMap<>(); + + this.dynamicInstances = new Object2ObjectOpenHashMap<>(); + this.tickableInstances = new Object2ObjectOpenHashMap<>(); + } + + @Override + public int getObjectCount() { + return instances.size(); + } + + @Override + public Iterable allInstances() { + return instances.values(); + } + + @Override + public List getInstancesForTicking() { + return new ArrayList<>(tickableInstances.values()); + } + + @Override + public List getInstancesForUpdate() { + return new ArrayList<>(dynamicInstances.values()); + } + + @Override + public void invalidate() { + instances.values().forEach(AbstractInstance::remove); + instances.clear(); + dynamicInstances.clear(); + tickableInstances.clear(); + } + + @Override + public void add(T obj) { + AbstractInstance instance = instances.get(obj); + + if (instance == null) { + create(obj); + } + } + + @Override + public void remove(T obj) { + var instance = instances.remove(obj); + + if (instance == null) { + return; + } + + instance.remove(); + dynamicInstances.remove(obj); + tickableInstances.remove(obj); + LightUpdater.get(instance.level) + .removeListener(instance); + } + + @Override + public void update(T obj) { + AbstractInstance instance = instances.get(obj); + + if (instance == null) { + return; + } + + // resetting instances is by default used to handle block state changes. + if (instance.shouldReset()) { + // delete and re-create the instance. + // resetting an instance supersedes updating it. + remove(obj); + create(obj); + } else { + instance.update(); + } + } + + @Override + public void recreateAll() { + dynamicInstances.clear(); + tickableInstances.clear(); + instances.replaceAll((obj, instance) -> { + instance.remove(); + + AbstractInstance out = createRaw(obj); + + if (out != null) { + setup(obj, out); + } + + return out; + }); + } + + private void create(T obj) { + AbstractInstance renderer = createRaw(obj); + + if (renderer != null) { + setup(obj, renderer); + instances.put(obj, renderer); + } + + } + + @Nullable + protected abstract AbstractInstance createRaw(T obj); + + private void setup(T obj, AbstractInstance renderer) { + renderer.init(); + renderer.updateLight(); + LightUpdater.get(renderer.level) + .addListener(renderer); + if (renderer instanceof TickableInstance r) { + tickableInstances.put(obj, r); + r.tick(); + } + + if (renderer instanceof DynamicInstance r) { + dynamicInstances.put(obj, r); + r.beginFrame(); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/Storage.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/Storage.java new file mode 100644 index 000000000..7c28f395c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/Storage.java @@ -0,0 +1,26 @@ +package com.jozufozu.flywheel.backend.instancing; + +import java.util.List; + +import com.jozufozu.flywheel.api.instance.DynamicInstance; +import com.jozufozu.flywheel.api.instance.TickableInstance; + +public interface Storage { + int getObjectCount(); + + Iterable allInstances(); + + List getInstancesForTicking(); + + List getInstancesForUpdate(); + + void invalidate(); + + void add(T obj); + + void remove(T obj); + + void update(T obj); + + void recreateAll(); +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingEngine.java index 5ac277a17..b0a932af6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingEngine.java @@ -8,9 +8,7 @@ import java.util.Map; import com.jozufozu.flywheel.api.InstancedPart; import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.backend.OptifineHandler; -import com.jozufozu.flywheel.backend.instancing.BatchDrawingTracker; -import com.jozufozu.flywheel.backend.instancing.Engine; -import com.jozufozu.flywheel.backend.instancing.TaskEngine; +import com.jozufozu.flywheel.backend.instancing.*; import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.util.FlwUtil; import com.mojang.blaze3d.platform.Lighting; @@ -112,6 +110,11 @@ public class BatchingEngine implements Engine { submitTasks(stack, taskEngine); } + @Override + public void attachManagers(InstanceManager... listener) { + // noop + } + @Override public void addDebugInfo(List info) { info.add("Batching"); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstanceManager.java index 1265669ce..a3cdff0c6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/blockentity/BlockEntityInstanceManager.java @@ -5,8 +5,10 @@ import java.util.List; import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.AbstractInstance; -import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; +import com.jozufozu.flywheel.backend.instancing.InstanceManager; +import com.jozufozu.flywheel.backend.instancing.One2OneStorage; +import com.jozufozu.flywheel.backend.instancing.Storage; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -17,51 +19,43 @@ import net.minecraft.world.level.block.entity.BlockEntity; public class BlockEntityInstanceManager extends InstanceManager { - private final Long2ObjectMap> posLookup = new Long2ObjectOpenHashMap<>(); + private final BlockEntityStorage storage; public BlockEntityInstanceManager(InstancerManager instancerManager) { - super(instancerManager); + storage = new BlockEntityStorage(instancerManager); + } + + @Override + public Storage getStorage() { + return storage; } public void getCrumblingInstances(long pos, List> data) { - BlockEntityInstance instance = posLookup.get(pos); + BlockEntityInstance instance = storage.posLookup.get(pos); if (instance != null) { data.add(instance); } } @Override - protected boolean canInstance(BlockEntity obj) { - return obj != null && InstancedRenderRegistry.canInstance(obj.getType()); - } - - @Override - protected AbstractInstance createRaw(BlockEntity obj) { - var instance = InstancedRenderRegistry.createInstance(instancerManager, obj); - - if (instance != null) { - BlockPos blockPos = obj.getBlockPos(); - posLookup.put(blockPos.asLong(), instance); + protected boolean canCreateInstance(BlockEntity blockEntity) { + if (blockEntity.isRemoved()) { + return false; } - return instance; - } - - @Override - protected void removeInternal(BlockEntity obj, AbstractInstance instance) { - super.removeInternal(obj, instance); - posLookup.remove(obj.getBlockPos().asLong()); - } - - @Override - protected boolean canCreateInstance(BlockEntity blockEntity) { - if (blockEntity.isRemoved()) return false; + if (!InstancedRenderRegistry.canInstance(blockEntity.getType())) { + return false; + } Level world = blockEntity.getLevel(); - if (world == null) return false; + if (world == null) { + return false; + } - if (world.isEmptyBlock(blockEntity.getBlockPos())) return false; + if (world.isEmptyBlock(blockEntity.getBlockPos())) { + return false; + } if (Backend.isFlywheelWorld(world)) { BlockPos pos = blockEntity.getBlockPos(); @@ -73,4 +67,32 @@ public class BlockEntityInstanceManager extends InstanceManager { return false; } + + public static class BlockEntityStorage extends One2OneStorage { + + final Long2ObjectMap> posLookup = new Long2ObjectOpenHashMap<>(); + + + public BlockEntityStorage(InstancerManager manager) { + super(manager); + } + + @Override + protected AbstractInstance createRaw(BlockEntity obj) { + var instance = InstancedRenderRegistry.createInstance(instancerManager, obj); + + if (instance != null) { + BlockPos blockPos = obj.getBlockPos(); + posLookup.put(blockPos.asLong(), instance); + } + + return instance; + } + + @Override + public void remove(BlockEntity obj) { + super.remove(obj); + posLookup.remove(obj.getBlockPos().asLong()); + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/Effect.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/Effect.java new file mode 100644 index 000000000..dc0291e2d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/Effect.java @@ -0,0 +1,11 @@ +package com.jozufozu.flywheel.backend.instancing.effect; + +import java.util.Collection; + +import com.jozufozu.flywheel.api.InstancerManager; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; + +public interface Effect { + + Collection createInstances(InstancerManager instancerManager); +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/EffectInstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/EffectInstanceManager.java new file mode 100644 index 000000000..035875a2a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/EffectInstanceManager.java @@ -0,0 +1,155 @@ +package com.jozufozu.flywheel.backend.instancing.effect; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.jozufozu.flywheel.api.InstancerManager; +import com.jozufozu.flywheel.api.instance.DynamicInstance; +import com.jozufozu.flywheel.api.instance.TickableInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; +import com.jozufozu.flywheel.backend.instancing.InstanceManager; +import com.jozufozu.flywheel.backend.instancing.Storage; +import com.jozufozu.flywheel.light.LightUpdater; + +public class EffectInstanceManager extends InstanceManager { + + private final EffectStorage storage; + + public EffectInstanceManager(InstancerManager instancerManager) { + storage = new EffectStorage<>(instancerManager); + } + + @Override + public Storage getStorage() { + return storage; + } + + @Override + protected boolean canCreateInstance(Effect obj) { + return true; + } + + public static class EffectStorage implements Storage { + + private final Multimap instances; + private final Set dynamicInstances; + private final Set tickableInstances; + private final InstancerManager manager; + + public EffectStorage(InstancerManager manager) { + this.instances = HashMultimap.create(); + this.dynamicInstances = new HashSet<>(); + this.tickableInstances = new HashSet<>(); + this.manager = manager; + } + + @Override + public int getObjectCount() { + return instances.size(); + } + + @Override + public Iterable allInstances() { + return instances.values(); + } + + @Override + public List getInstancesForTicking() { + return new ArrayList<>(tickableInstances); + } + + @Override + public List getInstancesForUpdate() { + return new ArrayList<>(dynamicInstances); + } + + @Override + public void invalidate() { + instances.values().forEach(AbstractInstance::remove); + instances.clear(); + tickableInstances.clear(); + dynamicInstances.clear(); + } + + @Override + public void add(T obj) { + var instances = this.instances.get(obj); + + if (instances.isEmpty()) { + create(obj); + } + } + + @Override + public void remove(T obj) { + var instances = this.instances.removeAll(obj); + + if (instances.isEmpty()) { + return; + } + + this.tickableInstances.removeAll(instances); + this.dynamicInstances.removeAll(instances); + for (AbstractInstance instance : instances) { + LightUpdater.get(instance.level) + .removeListener(instance); + } + } + + @Override + public void update(T obj) { + var instances = this.instances.get(obj); + + if (instances.isEmpty()) { + return; + } + + instances.forEach(AbstractInstance::update); + } + + @Override + public void recreateAll() { + this.dynamicInstances.clear(); + this.tickableInstances.clear(); + this.instances.asMap() + .forEach((obj, instances) -> { + instances.forEach(AbstractInstance::remove); + instances.clear(); + + var newInstances = obj.createInstances(manager); + + newInstances.forEach(this::setup); + + instances.addAll(newInstances); + }); + } + + private void create(T obj) { + var instances = obj.createInstances(manager); + + this.instances.putAll(obj, instances); + + instances.forEach(this::setup); + } + + private void setup(AbstractInstance renderer) { + renderer.init(); + renderer.updateLight(); + LightUpdater.get(renderer.level) + .addListener(renderer); + if (renderer instanceof TickableInstance r) { + tickableInstances.add(r); + r.tick(); + } + + if (renderer instanceof DynamicInstance r) { + dynamicInstances.add(r); + r.beginFrame(); + } + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/package-info.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/package-info.java new file mode 100644 index 000000000..8be4dc051 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/effect/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.jozufozu.flywheel.backend.instancing.effect; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java index 3ccbbdc0d..3643c30e3 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java @@ -1,10 +1,13 @@ package com.jozufozu.flywheel.backend.instancing.entity; +import org.jetbrains.annotations.Nullable; + import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.AbstractInstance; -import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; +import com.jozufozu.flywheel.backend.instancing.InstanceManager; +import com.jozufozu.flywheel.backend.instancing.One2OneStorage; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; @@ -13,23 +16,31 @@ import net.minecraft.world.level.Level; public class EntityInstanceManager extends InstanceManager { + private final One2OneStorage storage; + public EntityInstanceManager(InstancerManager instancerManager) { - super(instancerManager); + storage = new One2OneStorage<>(instancerManager) { + @Override + protected @Nullable AbstractInstance createRaw(Entity obj) { + return InstancedRenderRegistry.createInstance(this.instancerManager, obj); + } + }; } @Override - protected boolean canInstance(Entity obj) { - return obj != null && InstancedRenderRegistry.canInstance(obj.getType()); - } - - @Override - protected AbstractInstance createRaw(Entity obj) { - return InstancedRenderRegistry.createInstance(instancerManager, obj); + public One2OneStorage getStorage() { + return storage; } @Override protected boolean canCreateInstance(Entity entity) { - if (!entity.isAlive()) return false; + if (!entity.isAlive()) { + return false; + } + + if (!InstancedRenderRegistry.canInstance(entity.getType())) { + return false; + } Level world = entity.level; diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java index 706ffe3e8..9e1043636 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java @@ -19,8 +19,8 @@ import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.gl.GlVertexArray; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; -import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.InstanceManager; +import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.jozufozu.flywheel.backend.instancing.TaskEngine; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance; @@ -202,8 +202,9 @@ public class InstancingEngine

implements Engine { return originCoordinate; } - public void attachManager(InstanceManager listener) { - instanceManagers.add(listener); + @Override + public void attachManagers(InstanceManager... listener) { + instanceManagers.addAll(List.of(listener)); } @Override @@ -350,7 +351,7 @@ public class InstancingEngine

implements Engine { } if (!(InstancedRenderDispatcher.getInstanceWorld(level) - .getBlockEntityInstanceManager() instanceof BlockEntityInstanceManager beim)) { + .getBlockEntities() instanceof BlockEntityInstanceManager beim)) { return Int2ObjectMaps.emptyMap(); } diff --git a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java index 126082776..2624cf2f1 100644 --- a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java +++ b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java @@ -144,7 +144,7 @@ public class CrumblingRenderer { private State() { instancerManager = new CrumblingEngine(); instanceManager = new CrumblingInstanceManager(instancerManager); - instancerManager.attachManager(instanceManager); + instancerManager.attachManagers(instanceManager); } private void kill() { diff --git a/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java b/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java index 9c61684ac..d4fdf6ef2 100644 --- a/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java +++ b/src/main/java/com/jozufozu/flywheel/core/source/ShaderField.java @@ -8,7 +8,7 @@ import com.jozufozu.flywheel.core.source.parse.AbstractShaderElement; import com.jozufozu.flywheel.core.source.span.Span; public class ShaderField extends AbstractShaderElement { - public static final Pattern PATTERN = Pattern.compile("layout\\s+\\(location\\s+=\\s+(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); + public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); public final Span location; public final @Nullable Decoration decoration; diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererInstanceUpdateMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererInstanceUpdateMixin.java index bb781b29e..0b494bb26 100644 --- a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererInstanceUpdateMixin.java +++ b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererInstanceUpdateMixin.java @@ -12,6 +12,7 @@ import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @Mixin(LevelRenderer.class) @@ -24,9 +25,16 @@ public class LevelRendererInstanceUpdateMixin { */ @Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V") private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) { - if (Backend.isOn()) { - InstancedRenderDispatcher.getBlockEntities(level) - .update(level.getBlockEntity(pos)); + if (!Backend.isOn()) { + return; } + BlockEntity blockEntity = level.getBlockEntity(pos); + + if (blockEntity == null) { + return; + } + + InstancedRenderDispatcher.getBlockEntities(level) + .update(blockEntity); } } diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java b/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java new file mode 100644 index 000000000..967b5dee2 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/effect/ExampleEffect.java @@ -0,0 +1,117 @@ +package com.jozufozu.flywheel.vanilla.effect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.jozufozu.flywheel.api.InstancerManager; +import com.jozufozu.flywheel.api.instance.DynamicInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; +import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; +import com.jozufozu.flywheel.backend.instancing.effect.Effect; +import com.jozufozu.flywheel.core.Models; +import com.jozufozu.flywheel.core.structs.StructTypes; +import com.jozufozu.flywheel.core.structs.model.TransformedPart; +import com.jozufozu.flywheel.util.box.GridAlignedBB; +import com.jozufozu.flywheel.util.box.ImmutableBox; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.fml.LogicalSide; + +public class ExampleEffect implements Effect { + + private static final int INSTANCE_COUNT = 50; + + private final Level level; + private final Vec3 targetPoint; + private final BlockPos blockPos; + private final ImmutableBox volume; + + private final List effects; + + public ExampleEffect(Level level, Vec3 targetPoint) { + this.level = level; + this.targetPoint = targetPoint; + this.blockPos = new BlockPos(targetPoint); + this.effects = new ArrayList<>(); + this.volume = GridAlignedBB.from(this.blockPos); + } + + public static void spawn(TickEvent.PlayerTickEvent event) { + if (event.side == LogicalSide.SERVER || event.phase == TickEvent.Phase.START) { + return; + } + + Player player = event.player; + Level level = player.level; + + if (level.random.nextFloat() > 0.01) { + return; + } + + var effects = InstancedRenderDispatcher.getEffects(level); + + effects.add(new ExampleEffect(level, player.position())); + } + + @Override + public Collection createInstances(InstancerManager instancerManager) { + effects.clear(); + for (int i = 0; i < INSTANCE_COUNT; i++) { + effects.add(new Instance(instancerManager, level)); + } + return effects; + } + + public class Instance extends AbstractInstance implements DynamicInstance { + + TransformedPart firefly; + + public Instance(InstancerManager instancerManager, Level level) { + super(instancerManager, level); + } + + @Override + public void init() { + firefly = instancerManager.factory(StructTypes.TRANSFORMED) + .model(Models.block(Blocks.SHROOMLIGHT.defaultBlockState())) + .createInstance(); + + firefly.setBlockLight(15) + .setSkyLight(15); + } + + @Override + public BlockPos getWorldPosition() { + return blockPos; + } + + @Override + public void remove() { + firefly.delete(); + } + + @Override + public ImmutableBox getVolume() { + return volume; + } + + @Override + public void beginFrame() { + var x = level.random.nextFloat() * 3 - 1.5; + var y = level.random.nextFloat() * 3 - 1.5; + var z = level.random.nextFloat() * 3 - 1.5; + + firefly.loadIdentity() + .translate(instancerManager.getOriginCoordinate()) + .translate(targetPoint) + .translate(x, y, z) + .scale(1 / 16f); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/effect/package-info.java b/src/main/java/com/jozufozu/flywheel/vanilla/effect/package-info.java new file mode 100644 index 000000000..06269f440 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/effect/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.jozufozu.flywheel.vanilla.effect; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/package-info.java b/src/main/java/com/jozufozu/flywheel/vanilla/package-info.java new file mode 100644 index 000000000..420204158 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.jozufozu.flywheel.vanilla; + +import javax.annotation.ParametersAreNonnullByDefault; + +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert index dc49c75ca..fdff61295 100644 --- a/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert +++ b/src/main/resources/assets/flywheel/flywheel/instance/oriented.vert @@ -2,11 +2,11 @@ #use "flywheel:util/light.glsl" #use "flywheel:util/quaternion.glsl" -layout (location = 0) in vec2 oriented_light; -layout (location = 1) in vec4 oriented_color; -layout (location = 2) in vec3 oriented_pos; -layout (location = 3) in vec3 oriented_pivot; -layout (location = 4) in vec4 oriented_rotation; +layout(location = 0) in vec2 oriented_light; +layout(location = 1) in vec4 oriented_color; +layout(location = 2) in vec3 oriented_pos; +layout(location = 3) in vec3 oriented_pivot; +layout(location = 4) in vec4 oriented_rotation; void flw_instanceVertex() { flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0); diff --git a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert index d5fe6fc62..535136c71 100644 --- a/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert +++ b/src/main/resources/assets/flywheel/flywheel/instance/transformed.vert @@ -1,10 +1,10 @@ #use "flywheel:api/vertex.glsl" #use "flywheel:util/light.glsl" -layout (location = 0) in vec2 transformed_light; -layout (location = 1) in vec4 transformed_color; -layout (location = 2) in mat4 transformed_pose; -layout (location = 6) in mat3 transformed_normal; +layout(location = 0) in vec2 transformed_light; +layout(location = 1) in vec4 transformed_color; +layout(location = 2) in mat4 transformed_pose; +layout(location = 6) in mat3 transformed_normal; void flw_instanceVertex() { flw_vertexPos = transformed_pose * flw_vertexPos;