From ec8b831917490b27fe03355b2b816efdb20cb86f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 6 Apr 2022 11:51:59 -0700 Subject: [PATCH 1/7] Update changelog, make bug report template loader agnostic --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- changelog.txt | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dd66c76f3..03c717f62 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -96,9 +96,9 @@ body: required: true - type: input attributes: - label: Forge Version - description: The version of Forge you were using when the bug occured - placeholder: 39.0.0 + label: Loader Version + description: The version of Forge or Fabric you were using when the bug occured + placeholder: Forge 39.0.0 validations: required: true - type: textarea diff --git a/changelog.txt b/changelog.txt index 3afd6f854..89197f6e0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +0.6.2: +Update to 1.18.2 + 0.6.1: Fixes - Fix crash when loading block entities for Flywheel to render, most common when exploring the end. From eb5d3c4b250b37b9bc4b73fbdbc56990f772c652 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 16 Mar 2022 21:26:43 -0700 Subject: [PATCH 2/7] Model allocators are passed on init instead of stored --- .../instancing/instancing/GPUInstancer.java | 6 +- .../instancing/InstancedMaterial.java | 7 +-- .../instancing/InstancedMaterialGroup.java | 58 ++++++++++++------- .../core/crumbling/CrumblingGroup.java | 2 +- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancer.java index 3f0ff7c87..4b469c02f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancer.java @@ -19,7 +19,6 @@ import com.jozufozu.flywheel.core.model.Model; public class GPUInstancer extends AbstractInstancer { - private final ModelAllocator modelAllocator; private final BufferLayout instanceFormat; private final Instanced instancedType; @@ -32,9 +31,8 @@ public class GPUInstancer extends AbstractInstancer { protected boolean anyToUpdate; - public GPUInstancer(Instanced type, Model model, ModelAllocator modelAllocator) { + public GPUInstancer(Instanced type, Model model) { super(type::create, model); - this.modelAllocator = modelAllocator; this.instanceFormat = type.getLayout(); instancedType = type; } @@ -63,7 +61,7 @@ public class GPUInstancer extends AbstractInstancer { return deleted || model == null; } - public void init() { + public void init(ModelAllocator modelAllocator) { if (isInitialized()) return; initialized = true; diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java index e22b57ce4..7a8373136 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java @@ -11,7 +11,6 @@ import com.jozufozu.flywheel.api.InstanceData; import com.jozufozu.flywheel.api.Instancer; import com.jozufozu.flywheel.api.Material; import com.jozufozu.flywheel.api.struct.Instanced; -import com.jozufozu.flywheel.backend.model.ModelAllocator; import com.jozufozu.flywheel.core.model.Model; /** @@ -20,14 +19,12 @@ import com.jozufozu.flywheel.core.model.Model; */ public class InstancedMaterial implements Material { - protected final ModelAllocator allocator; protected final Map> models = new HashMap<>(); protected final Instanced type; protected final List> uninitialized = new ArrayList<>(); - public InstancedMaterial(Instanced type, ModelAllocator allocator) { + public InstancedMaterial(Instanced type) { this.type = type; - this.allocator = allocator; } /** @@ -40,7 +37,7 @@ public class InstancedMaterial implements Material { @Override public Instancer model(Object key, Supplier modelSupplier) { return models.computeIfAbsent(key, $ -> { - GPUInstancer instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator); + GPUInstancer instancer = new GPUInstancer<>(type, modelSupplier.get()); uninitialized.add(instancer); return instancer; }); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java index bd85a26b8..2bc228ed9 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java @@ -32,27 +32,21 @@ public class InstancedMaterialGroup

implements MaterialG protected final RenderType type; private final Map, InstancedMaterial> materials = new HashMap<>(); - private final ModelAllocator allocator; + private ModelAllocator allocator; private int vertexCount; private int instanceCount; public InstancedMaterialGroup(InstancingEngine

owner, RenderType type) { this.owner = owner; this.type = type; - if (GlCompat.getInstance() - .onAMDWindows()) { - this.allocator = FallbackAllocator.INSTANCE; - } else { - this.allocator = new ModelPool(Formats.POS_TEX_NORMAL); - } } @SuppressWarnings("unchecked") @Override public InstancedMaterial material(StructType type) { if (type instanceof Instanced instanced) { - return (InstancedMaterial) materials.computeIfAbsent(instanced, t -> new InstancedMaterial<>(t, allocator)); + return (InstancedMaterial) materials.computeIfAbsent(instanced, InstancedMaterial::new); } else { throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing."); } @@ -82,18 +76,7 @@ public class InstancedMaterialGroup

implements MaterialG } protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) { - // initialize all uninitialized instancers... - for (InstancedMaterial material : materials.values()) { - for (GPUInstancer instancer : material.uninitialized) { - instancer.init(); - } - material.uninitialized.clear(); - } - - if (allocator instanceof ModelPool pool) { - // ...and then flush the model arena in case anything was marked for upload - pool.flush(); - } + initializeInstancers(); vertexCount = 0; instanceCount = 0; @@ -119,7 +102,24 @@ public class InstancedMaterialGroup

implements MaterialG } } - public void setup(P program) { + private void initializeInstancers() { + ModelAllocator allocator = getModelAllocator(); + + // initialize all uninitialized instancers... + for (InstancedMaterial material : materials.values()) { + for (GPUInstancer instancer : material.uninitialized) { + instancer.init(allocator); + } + material.uninitialized.clear(); + } + + if (allocator instanceof ModelPool pool) { + // ...and then flush the model arena in case anything was marked for upload + pool.flush(); + } + } + + protected void setup(P program) { } @@ -133,4 +133,20 @@ public class InstancedMaterialGroup

implements MaterialG materials.clear(); } + + private ModelAllocator getModelAllocator() { + if (allocator == null) { + allocator = createAllocator(); + } + return this.allocator; + } + + private static ModelAllocator createAllocator() { + if (GlCompat.getInstance() + .onAMDWindows()) { + return FallbackAllocator.INSTANCE; + } else { + return new ModelPool(Formats.POS_TEX_NORMAL); + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingGroup.java b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingGroup.java index ac27997f3..ee113de17 100644 --- a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingGroup.java @@ -54,7 +54,7 @@ public class CrumblingGroup

extends InstancedMateria } @Override - public void setup(P p) { + protected void setup(P p) { p.setAtlasSize(width, height); } } From e3ceacd6e4c7d90a057e00152145e4e44d26a126 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 16 Mar 2022 22:57:30 -0700 Subject: [PATCH 3/7] Contraptions were too lit - Fix bug causing potentially moving contraptions to re-upload their light every tick - Reduces tick stutter with large amounts of contraptions --- .../com/jozufozu/flywheel/util/box/ImmutableBox.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/jozufozu/flywheel/util/box/ImmutableBox.java b/src/main/java/com/jozufozu/flywheel/util/box/ImmutableBox.java index 10a5c7a91..f87903d96 100644 --- a/src/main/java/com/jozufozu/flywheel/util/box/ImmutableBox.java +++ b/src/main/java/com/jozufozu/flywheel/util/box/ImmutableBox.java @@ -42,6 +42,15 @@ public interface ImmutableBox { return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ(); } + default boolean sameAs(ImmutableBox other, int margin) { + return getMinX() == other.getMinX() - margin && + getMinY() == other.getMinY() - margin && + getMinZ() == other.getMinZ() - margin && + getMaxX() == other.getMaxX() + margin && + getMaxY() == other.getMaxY() + margin && + getMaxZ() == other.getMaxZ() + margin; + } + default boolean sameAs(AABB other) { return getMinX() == Math.floor(other.minX) && getMinY() == Math.floor(other.minY) From 19d757d7028b43569f15f4ad21431ec668296ec8 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 16 Mar 2022 23:43:01 -0700 Subject: [PATCH 4/7] Moving in parallel - LightUpdates now uses task engine to update listeners in parallel - Basic workgroup system to run something on the main thread after a group of tasks is complete --- .../backend/instancing/InstanceWorld.java | 2 +- .../instancing/InstancedRenderDispatcher.java | 22 ++-- .../instancing/ParallelTaskEngine.java | 103 +++++++++++++++++- .../jozufozu/flywheel/light/LightUpdater.java | 35 ++++-- 4 files changed, 139 insertions(+), 23 deletions(-) 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 23d248b8a..ac6b9037a 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java @@ -32,7 +32,7 @@ public class InstanceWorld { protected final InstanceManager entityInstanceManager; protected final InstanceManager blockEntityInstanceManager; - protected final ParallelTaskEngine taskEngine; + public final ParallelTaskEngine taskEngine; public InstanceWorld(LevelAccessor levelAccessor) { Level world = (Level) levelAccessor; 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 ca423380b..908f2381b 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java @@ -54,18 +54,24 @@ public class InstancedRenderDispatcher { } public static InstanceManager getBlockEntities(LevelAccessor world) { - if (Backend.isOn()) { - return instanceWorlds.get(world) - .getBlockEntityInstanceManager(); - } else { - throw new NullPointerException("Backend is off, cannot retrieve instance world."); - } + return getInstanceWorld(world).getBlockEntityInstanceManager(); } public static InstanceManager getEntities(LevelAccessor world) { + return getInstanceWorld(world).getEntityInstanceManager(); + } + + public static ParallelTaskEngine getTaskEngine(LevelAccessor world) { + return getInstanceWorld(world).taskEngine; + } + + /** + * Get or create the {@link InstanceWorld} for the given world. + * @throws NullPointerException if the backend is off + */ + public static InstanceWorld getInstanceWorld(LevelAccessor world) { if (Backend.isOn()) { - return instanceWorlds.get(world) - .getEntityInstanceManager(); + return instanceWorlds.get(world); } else { throw new NullPointerException("Backend is off, cannot retrieve instance world."); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/ParallelTaskEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/ParallelTaskEngine.java index 19f5b2f59..cea0cbadb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/ParallelTaskEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/ParallelTaskEngine.java @@ -5,6 +5,9 @@ import java.util.Deque; import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,9 +23,15 @@ import net.minecraft.util.Mth; public class ParallelTaskEngine implements TaskEngine { private static final Logger LOGGER = LoggerFactory.getLogger("BatchExecutor"); + private final String name; + + /** + * If set to false, the engine will shut down. + */ private final AtomicBoolean running = new AtomicBoolean(false); private final WaitGroup wg = new WaitGroup(); + private final Deque syncTasks = new ConcurrentLinkedDeque<>(); private final Deque jobQueue = new ConcurrentLinkedDeque<>(); private final List threads = new ArrayList<>(); @@ -30,13 +39,15 @@ public class ParallelTaskEngine implements TaskEngine { private final int threadCount; - private final String name; - public ParallelTaskEngine(String name) { this.name = name; threadCount = getOptimalThreadCount(); } + public WorkGroupBuilder group(String name) { + return new WorkGroupBuilder(name); + } + /** * Spawns a number of work-stealing threads to process results in the build queue. If the builder is already * running, this method does nothing and exits. @@ -117,6 +128,10 @@ public class ParallelTaskEngine implements TaskEngine { this.wg.await(); } catch (InterruptedException ignored) { } + + while ((job = this.syncTasks.pollLast()) != null) { + job.run(); + } } @Nullable @@ -157,6 +172,7 @@ public class ParallelTaskEngine implements TaskEngine { private static int getMaxThreadCount() { return Runtime.getRuntime().availableProcessors(); } + private class WorkerRunnable implements Runnable { private final AtomicBoolean running = ParallelTaskEngine.this.running; @@ -176,4 +192,87 @@ public class ParallelTaskEngine implements TaskEngine { } } + + public class WorkGroupBuilder { + final String name; + + @Nullable + Runnable finalizer; + + Stream tasks; + + public WorkGroupBuilder(String name) { + this.name = name; + } + + public WorkGroupBuilder addTasks(Stream iterable, Consumer consumer) { + return addTasks(iterable.map(it -> () -> consumer.accept(it))); + } + + public WorkGroupBuilder addTasks(Stream tasks) { + if (this.tasks == null) { + this.tasks = tasks; + } else { + this.tasks = Stream.concat(this.tasks, tasks); + } + return this; + } + + public WorkGroupBuilder onComplete(Runnable runnable) { + this.finalizer = runnable; + return this; + } + + public void submit() { + if (this.tasks == null) { + return; + } + + WorkGroup workGroup = new WorkGroup(name, finalizer); + + tasks.map(task -> new WorkGroupTask(workGroup, task)).forEach(ParallelTaskEngine.this::submit); + } + + } + + private static class WorkGroupTask implements Runnable { + + private final WorkGroup parent; + private final Runnable wrapped; + + public WorkGroupTask(WorkGroup parent, Runnable wrapped) { + this.parent = parent; + this.wrapped = wrapped; + + this.parent.running.incrementAndGet(); + } + + @Override + public void run() { + this.wrapped.run(); + + this.parent.oneDown(); + } + } + + private class WorkGroup { + final String name; + + final Runnable finalizer; + + final AtomicInteger running = new AtomicInteger(0); + + public WorkGroup(String name, @Nullable Runnable finalizer) { + this.name = name; + this.finalizer = finalizer; + } + + public void oneDown() { + if (running.decrementAndGet() == 0) { + if (finalizer != null) { + ParallelTaskEngine.this.syncTasks.add(finalizer); + } + } + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index cf6a8d491..7692126a9 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -1,18 +1,21 @@ package com.jozufozu.flywheel.light; -import java.util.HashMap; -import java.util.Map; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Stream; +import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; +import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine; import com.jozufozu.flywheel.util.WeakHashSet; +import com.jozufozu.flywheel.util.WorldAttached; import com.jozufozu.flywheel.util.box.GridAlignedBB; import com.jozufozu.flywheel.util.box.ImmutableBox; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; -import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LightLayer; /** @@ -20,9 +23,11 @@ import net.minecraft.world.level.LightLayer; */ public class LightUpdater { - private static final Map light = new HashMap<>(); - public static LightUpdater get(BlockAndTintGetter world) { - return light.computeIfAbsent(world, LightUpdater::new); + private static final WorldAttached light = new WorldAttached<>(LightUpdater::new); + private final ParallelTaskEngine taskEngine; + + public static LightUpdater get(LevelAccessor world) { + return light.get(world); } private final LightProvider provider; @@ -31,7 +36,8 @@ public class LightUpdater { private final WeakContainmentMultiMap sections = new WeakContainmentMultiMap<>(); private final WeakContainmentMultiMap chunks = new WeakContainmentMultiMap<>(); - public LightUpdater(BlockAndTintGetter world) { + public LightUpdater(LevelAccessor world) { + taskEngine = InstancedRenderDispatcher.getTaskEngine(world); provider = new BasicProvider(world); } @@ -40,11 +46,16 @@ public class LightUpdater { } public void tick() { - for (MovingListener listener : movingListeners) { - if (listener.update(provider)) { - addListener(listener); - } - } + Queue listeners = new ConcurrentLinkedQueue<>(); + + taskEngine.group("LightUpdater") + .addTasks(movingListeners.stream(), listener -> { + if (listener.update(provider)) { + listeners.add(listener); + } + }) + .onComplete(() -> listeners.forEach(this::addListener)) + .submit(); } /** From 9a69ed19062a43e11eeca9b18c6c1b68f3906deb Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 7 Apr 2022 14:19:36 -0700 Subject: [PATCH 5/7] Layoff some workers - Only one task engine for everything now - Fixes crash on LightUpdater init when Flywheel is off - Attempt to wrangle InstanceWorld init code - Misc. cleanup/renaming --- build.gradle | 2 +- .../java/com/jozufozu/flywheel/Flywheel.java | 4 +- .../jozufozu/flywheel/backend/Backend.java | 41 +++++++++++---- .../backend/instancing/InstanceWorld.java | 37 +++++++------- .../instancing/InstancedRenderDispatcher.java | 6 +-- .../jozufozu/flywheel/config/BackendType.java | 51 +++++++++++++++++++ ...Argument.java => BackendTypeArgument.java} | 14 ++--- .../jozufozu/flywheel/config/FlwCommands.java | 14 ++--- .../jozufozu/flywheel/config/FlwConfig.java | 12 ++--- .../jozufozu/flywheel/config/FlwEngine.java | 48 ----------------- .../jozufozu/flywheel/light/LightUpdater.java | 4 +- 11 files changed, 125 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/config/BackendType.java rename src/main/java/com/jozufozu/flywheel/config/{EngineArgument.java => BackendTypeArgument.java} (72%) delete mode 100644 src/main/java/com/jozufozu/flywheel/config/FlwEngine.java diff --git a/build.gradle b/build.gradle index 7c73f44cf..928cd432d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { maven { url = 'https://maven.minecraftforge.net' } - jcenter() mavenCentral() maven { url = 'https://repo.spongepowered.org/repository/maven-public' } maven { url = 'https://maven.parchmentmc.org' } @@ -37,6 +36,7 @@ println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getPro minecraft { mappings channel: 'parchment', version: "${parchment_version}-${minecraft_version}" + runs { client { workingDirectory project.file('run') diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 63ca7a78f..3ae7e9691 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -5,7 +5,7 @@ import org.slf4j.Logger; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.OptifineHandler; -import com.jozufozu.flywheel.config.EngineArgument; +import com.jozufozu.flywheel.config.BackendTypeArgument; import com.jozufozu.flywheel.config.FlwCommands; import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.core.Contexts; @@ -88,7 +88,7 @@ public class Flywheel { } private static void setup(final FMLCommonSetupEvent event) { - ArgumentTypes.register(rl("engine").toString(), EngineArgument.class, new EmptyArgumentSerializer<>(EngineArgument::getInstance)); + ArgumentTypes.register(rl("engine").toString(), BackendTypeArgument.class, new EmptyArgumentSerializer<>(BackendTypeArgument::getInstance)); } public static ArtifactVersion getVersion() { diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backend.java b/src/main/java/com/jozufozu/flywheel/backend/Backend.java index 90a853493..d3be6f353 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/Backend.java +++ b/src/main/java/com/jozufozu/flywheel/backend/Backend.java @@ -6,8 +6,9 @@ import org.slf4j.Logger; import com.jozufozu.flywheel.api.FlywheelWorld; import com.jozufozu.flywheel.backend.gl.versioned.GlCompat; +import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine; import com.jozufozu.flywheel.config.FlwConfig; -import com.jozufozu.flywheel.config.FlwEngine; +import com.jozufozu.flywheel.config.BackendType; import com.jozufozu.flywheel.core.shader.ProgramSpec; import com.mojang.logging.LogUtils; @@ -19,12 +20,30 @@ import net.minecraft.world.level.LevelAccessor; public class Backend { public static final Logger LOGGER = LogUtils.getLogger(); - private static FlwEngine engine; + private static BackendType backendType; + + private static ParallelTaskEngine taskEngine; private static final Loader loader = new Loader(); - public static FlwEngine getEngine() { - return engine; + /** + * Get the current Flywheel backend type. + */ + public static BackendType getBackendType() { + return backendType; + } + + /** + * Get a thread pool for running Flywheel related work in parallel. + * @return A global Flywheel thread pool. + */ + public static ParallelTaskEngine getTaskEngine() { + if (taskEngine == null) { + taskEngine = new ParallelTaskEngine("Flywheel"); + taskEngine.startWorkers(); + } + + return taskEngine; } /** @@ -32,7 +51,7 @@ public class Backend { * (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use. */ public static String getBackendDescriptor() { - return engine == null ? "Uninitialized" : engine.getProperName(); + return backendType == null ? "Uninitialized" : backendType.getProperName(); } @Nullable @@ -41,11 +60,11 @@ public class Backend { } public static void refresh() { - engine = chooseEngine(); + backendType = chooseEngine(); } public static boolean isOn() { - return engine != FlwEngine.OFF; + return backendType != BackendType.OFF; } public static boolean canUseInstancing(@Nullable Level world) { @@ -73,9 +92,9 @@ public class Backend { RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged); } - private static FlwEngine chooseEngine() { - FlwEngine preferredChoice = FlwConfig.get() - .getEngine(); + private static BackendType chooseEngine() { + BackendType preferredChoice = FlwConfig.get() + .getBackendType(); boolean usingShaders = OptifineHandler.isUsingShaders(); boolean canUseEngine = switch (preferredChoice) { @@ -84,7 +103,7 @@ public class Backend { case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported(); }; - return canUseEngine ? preferredChoice : FlwEngine.OFF; + return canUseEngine ? preferredChoice : BackendType.OFF; } public static void init() { 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 ac6b9037a..c3ee1a1f6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceWorld.java @@ -7,7 +7,6 @@ import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager; import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager; import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; -import com.jozufozu.flywheel.config.FlwEngine; import com.jozufozu.flywheel.core.Contexts; import com.jozufozu.flywheel.core.shader.WorldProgram; import com.jozufozu.flywheel.event.BeginFrameEvent; @@ -17,7 +16,6 @@ import com.jozufozu.flywheel.util.ClientLevelExtension; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.world.entity.Entity; -import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.entity.BlockEntity; @@ -34,33 +32,35 @@ public class InstanceWorld { public final ParallelTaskEngine taskEngine; - public InstanceWorld(LevelAccessor levelAccessor) { - Level world = (Level) levelAccessor; - - this.taskEngine = new ParallelTaskEngine("Flywheel " + world.dimension().location()); - this.taskEngine.startWorkers(); - - FlwEngine engine = Backend.getEngine(); - - switch (engine) { + public static InstanceWorld create(LevelAccessor level) { + return switch (Backend.getBackendType()) { case INSTANCING -> { InstancingEngine manager = InstancingEngine.builder(Contexts.WORLD) .build(); - entityInstanceManager = new EntityInstanceManager(manager); - blockEntityInstanceManager = new BlockEntityInstanceManager(manager); + var entityInstanceManager = new EntityInstanceManager(manager); + var blockEntityInstanceManager = new BlockEntityInstanceManager(manager); manager.addListener(entityInstanceManager); manager.addListener(blockEntityInstanceManager); - this.engine = manager; + yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager); } case BATCHING -> { - this.engine = new BatchingEngine(); - entityInstanceManager = new EntityInstanceManager(this.engine); - blockEntityInstanceManager = new BlockEntityInstanceManager(this.engine); + var manager = new BatchingEngine(); + var entityInstanceManager = new EntityInstanceManager(manager); + var blockEntityInstanceManager = new BlockEntityInstanceManager(manager); + + yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager); } default -> throw new IllegalArgumentException("Unknown engine type"); - } + }; + } + + public InstanceWorld(Engine engine, InstanceManager entityInstanceManager, InstanceManager blockEntityInstanceManager) { + this.engine = engine; + this.entityInstanceManager = entityInstanceManager; + this.blockEntityInstanceManager = blockEntityInstanceManager; + this.taskEngine = Backend.getTaskEngine(); } public InstanceManager getEntityInstanceManager() { @@ -75,7 +75,6 @@ public class InstanceWorld { * Free all acquired resources and invalidate this instance world. */ public void delete() { - taskEngine.stopWorkers(); engine.delete(); entityInstanceManager.detachLightListeners(); blockEntityInstanceManager.detachLightListeners(); 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 908f2381b..6f8f77e3d 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java @@ -27,7 +27,7 @@ import net.minecraftforge.fml.common.Mod; @Mod.EventBusSubscriber(Dist.CLIENT) public class InstancedRenderDispatcher { - private static final WorldAttached instanceWorlds = new WorldAttached<>(InstanceWorld::new); + private static final WorldAttached instanceWorlds = new WorldAttached<>(InstanceWorld::create); /** * Call this when you want to manually run {@link AbstractInstance#update()}. @@ -61,10 +61,6 @@ public class InstancedRenderDispatcher { return getInstanceWorld(world).getEntityInstanceManager(); } - public static ParallelTaskEngine getTaskEngine(LevelAccessor world) { - return getInstanceWorld(world).taskEngine; - } - /** * Get or create the {@link InstanceWorld} for the given world. * @throws NullPointerException if the backend is off diff --git a/src/main/java/com/jozufozu/flywheel/config/BackendType.java b/src/main/java/com/jozufozu/flywheel/config/BackendType.java new file mode 100644 index 000000000..b5244b500 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/config/BackendType.java @@ -0,0 +1,51 @@ +package com.jozufozu.flywheel.config; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.annotation.Nullable; + +public enum BackendType { + OFF("Off"), + + /** + * Use a thread pool to buffer instances in parallel on the CPU. + */ + BATCHING("Parallel Batching"), + + /** + * Use GPU instancing to render everything. + */ + INSTANCING("GL33 Instanced Arrays"), + ; + + private static final Map lookup; + + static { + lookup = new HashMap<>(); + for (BackendType value : values()) { + lookup.put(value.name().toLowerCase(Locale.ROOT), value); + } + } + + private final String properName; + + BackendType(String properName) { + this.properName = properName; + } + + public String getProperName() { + return properName; + } + + @Nullable + public static BackendType byName(String name) { + return lookup.get(name); + } + + public static Collection validNames() { + return lookup.keySet(); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/config/EngineArgument.java b/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java similarity index 72% rename from src/main/java/com/jozufozu/flywheel/config/EngineArgument.java rename to src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java index 497a60870..7f2545a3a 100644 --- a/src/main/java/com/jozufozu/flywheel/config/EngineArgument.java +++ b/src/main/java/com/jozufozu/flywheel/config/BackendTypeArgument.java @@ -14,7 +14,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.TranslatableComponent; -public enum EngineArgument implements ArgumentType { +public enum BackendTypeArgument implements ArgumentType { INSTANCE; private static final Dynamic2CommandExceptionType INVALID = new Dynamic2CommandExceptionType((found, constants) -> { @@ -23,28 +23,28 @@ public enum EngineArgument implements ArgumentType { }); @Override - public FlwEngine parse(StringReader reader) throws CommandSyntaxException { + public BackendType parse(StringReader reader) throws CommandSyntaxException { String string = reader.readUnquotedString(); - FlwEngine engine = FlwEngine.byName(string); + BackendType engine = BackendType.byName(string); if (engine == null) { - throw INVALID.createWithContext(reader, string, FlwEngine.validNames()); + throw INVALID.createWithContext(reader, string, BackendType.validNames()); } return engine; } public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggest(FlwEngine.validNames(), builder); + return SharedSuggestionProvider.suggest(BackendType.validNames(), builder); } @Override public Collection getExamples() { - return FlwEngine.validNames(); + return BackendType.validNames(); } - public static EngineArgument getInstance() { + public static BackendTypeArgument getInstance() { return INSTANCE; } } diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java index ccd9e262d..7575111e0 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java @@ -27,7 +27,7 @@ public class FlwCommands { ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel"); - commandBuilder.addValue(config.client.engine, "backend", (builder, value) -> + commandBuilder.addValue(config.client.backend, "backend", (builder, value) -> builder .executes(context -> { LocalPlayer player = Minecraft.getInstance().player; @@ -36,11 +36,11 @@ public class FlwCommands { } return Command.SINGLE_SUCCESS; }) - .then(Commands.argument("type", EngineArgument.INSTANCE) + .then(Commands.argument("type", BackendTypeArgument.INSTANCE) .executes(context -> { LocalPlayer player = Minecraft.getInstance().player; if (player != null) { - FlwEngine type = context.getArgument("type", FlwEngine.class); + BackendType type = context.getArgument("type", BackendType.class); value.set(type); Component message = getEngineMessage(type); @@ -51,7 +51,7 @@ public class FlwCommands { return Command.SINGLE_SUCCESS; }))); - commandBuilder.addValue(config.client.debugNormals, "debugNormals", (builder, value) -> booleanValueCommand(builder, config, value, + commandBuilder.addValue(config.client.debugNormals, "debugNormals", (builder, value) -> booleanValueCommand(builder, value, (source, bool) -> { LocalPlayer player = Minecraft.getInstance().player; if (player == null) return; @@ -68,7 +68,7 @@ public class FlwCommands { } )); - commandBuilder.addValue(config.client.limitUpdates, "limitUpdates", (builder, value) -> booleanValueCommand(builder, config, value, + commandBuilder.addValue(config.client.limitUpdates, "limitUpdates", (builder, value) -> booleanValueCommand(builder, value, (source, bool) -> { LocalPlayer player = Minecraft.getInstance().player; if (player == null) return; @@ -90,7 +90,7 @@ public class FlwCommands { commandBuilder.build(event.getDispatcher()); } - public static void booleanValueCommand(LiteralArgumentBuilder builder, FlwConfig config, ConfigValue value, BiConsumer displayAction, BiConsumer setAction) { + public static void booleanValueCommand(LiteralArgumentBuilder builder, ConfigValue value, BiConsumer displayAction, BiConsumer setAction) { builder .executes(context -> { displayAction.accept(context.getSource(), value.get()); @@ -114,7 +114,7 @@ public class FlwCommands { return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED); } - public static Component getEngineMessage(@NotNull FlwEngine type) { + public static Component getEngineMessage(@NotNull BackendType type) { return switch (type) { case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED); case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN); diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java index 95be3cc46..1872131f7 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java @@ -27,8 +27,8 @@ public class FlwConfig { return INSTANCE; } - public FlwEngine getEngine() { - return client.engine.get(); + public BackendType getBackendType() { + return client.backend.get(); } public boolean debugNormals() { @@ -43,15 +43,15 @@ public class FlwConfig { } public static class ClientConfig { - public final EnumValue engine; + public final EnumValue backend; public final BooleanValue debugNormals; public final BooleanValue limitUpdates; public ClientConfig(ForgeConfigSpec.Builder builder) { - engine = builder.comment("Enable or disable the entire engine") - .defineEnum("backend", FlwEngine.INSTANCING); + backend = builder.comment("Select the backend to use.") + .defineEnum("backend", BackendType.INSTANCING); - debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal") + debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal.") .define("debugNormals", false); limitUpdates = builder.comment("Enable or disable instance update limiting with distance.") diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwEngine.java b/src/main/java/com/jozufozu/flywheel/config/FlwEngine.java deleted file mode 100644 index f5665e1f4..000000000 --- a/src/main/java/com/jozufozu/flywheel/config/FlwEngine.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jozufozu.flywheel.config; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import javax.annotation.Nullable; - -public enum FlwEngine { - OFF("off", "Off"), - BATCHING("batching", "Parallel Batching"), - INSTANCING("instancing", "GL33 Instanced Arrays"), - ; - - private static final Map lookup; - - static { - lookup = new HashMap<>(); - for (FlwEngine value : values()) { - lookup.put(value.shortName, value); - } - } - - private final String shortName; - private final String properName; - - FlwEngine(String shortName, String properName) { - this.shortName = shortName; - this.properName = properName; - } - - public String getShortName() { - return shortName; - } - - public String getProperName() { - return properName; - } - - @Nullable - public static FlwEngine byName(String name) { - return lookup.get(name); - } - - public static Collection validNames() { - return lookup.keySet(); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index 7692126a9..6161fd803 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -5,7 +5,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Stream; -import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; +import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine; import com.jozufozu.flywheel.util.WeakHashSet; import com.jozufozu.flywheel.util.WorldAttached; @@ -37,7 +37,7 @@ public class LightUpdater { private final WeakContainmentMultiMap chunks = new WeakContainmentMultiMap<>(); public LightUpdater(LevelAccessor world) { - taskEngine = InstancedRenderDispatcher.getTaskEngine(world); + taskEngine = Backend.getTaskEngine(); provider = new BasicProvider(world); } From af11d1e78b0b5753bcdd2e87a9875a48d8f52138 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 8 Apr 2022 17:23:02 -0700 Subject: [PATCH 6/7] Fix spooky shaded contraptions - Logic error in GPULightVolume#move - Race condition from LightUpdater showing up in LightVolume#initialize --- .../com/jozufozu/flywheel/light/GPULightVolume.java | 11 ++--------- .../com/jozufozu/flywheel/light/LightUpdater.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java b/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java index 43d42d90a..fee086071 100644 --- a/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java +++ b/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java @@ -114,15 +114,8 @@ public class GPULightVolume extends LightVolume { if (lightData == null) return; if (box.contains(newSampleVolume)) { - if (newSampleVolume.intersects(sampleVolume)) { - GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume); - sampleVolume.assign(newSampleVolume); - - copyLight(world, newArea); - } else { - sampleVolume.assign(newSampleVolume); - initialize(world); - } + sampleVolume.assign(newSampleVolume); + initialize(world); } else { super.move(world, newSampleVolume); } diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index 6161fd803..c1b3b4f54 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -46,6 +46,19 @@ public class LightUpdater { } public void tick() { + tickSerial(); + //tickParallel(); + } + + private void tickSerial() { + for (MovingListener movingListener : movingListeners) { + if (movingListener.update(provider)) { + addListener(movingListener); + } + } + } + + private void tickParallel() { Queue listeners = new ConcurrentLinkedQueue<>(); taskEngine.group("LightUpdater") From e26195169fe63b9dde31eddeb927f9fb98acb20c Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 22 Jun 2022 13:29:28 -0700 Subject: [PATCH 7/7] Closing the blinds - Prevent LightUpdater from interacting with invalid levels. - Bump version - 0.6.3 --- gradle.properties | 2 +- .../flywheel/light/DummyLightUpdater.java | 50 +++++++++++++++++++ .../jozufozu/flywheel/light/LightUpdated.java | 32 ++++++++++++ .../jozufozu/flywheel/light/LightUpdater.java | 15 ++++-- .../jozufozu/flywheel/util/WorldAttached.java | 2 +- 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/light/DummyLightUpdater.java create mode 100644 src/main/java/com/jozufozu/flywheel/light/LightUpdated.java diff --git a/gradle.properties b/gradle.properties index 7989201c7..45c6f4660 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs = -Xmx3G org.gradle.daemon = false # mod version info -mod_version = 0.6.2 +mod_version = 0.6.3 mc_update_version = 1.18 minecraft_version = 1.18.2 forge_version = 40.0.15 diff --git a/src/main/java/com/jozufozu/flywheel/light/DummyLightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/DummyLightUpdater.java new file mode 100644 index 000000000..0cb04f944 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/DummyLightUpdater.java @@ -0,0 +1,50 @@ +package com.jozufozu.flywheel.light; + +import java.util.stream.Stream; + +import com.jozufozu.flywheel.util.box.ImmutableBox; + +import net.minecraft.world.level.LightLayer; + +public class DummyLightUpdater extends LightUpdater { + public static final DummyLightUpdater INSTANCE = new DummyLightUpdater(); + + private DummyLightUpdater() { + super(null); + } + + @Override + public void tick() { + // noop + } + + @Override + public void addListener(LightListener listener) { + // noop + } + + @Override + public void removeListener(LightListener listener) { + // noop + } + + @Override + public void onLightUpdate(LightLayer type, long sectionPos) { + // noop + } + + @Override + public void onLightPacket(int chunkX, int chunkZ) { + // noop + } + + @Override + public Stream getAllBoxes() { + return Stream.empty(); + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdated.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdated.java new file mode 100644 index 000000000..4173ef2a8 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdated.java @@ -0,0 +1,32 @@ +package com.jozufozu.flywheel.light; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.LevelAccessor; + +/** + * Marker interface for custom/fake levels to indicate that LightUpdater should bother interacting with it.

+ * + * Implement this if your custom level has light updates at all. If so, be sure to call + * {@link com.jozufozu.flywheel.util.WorldAttached#invalidateWorld} when your level in unloaded. + */ +public interface LightUpdated extends LevelAccessor { + + /** + * @return {@code true} if this level is passing light updates into LightUpdater. + */ + default boolean receivesLightUpdates() { + return true; + } + + static boolean receivesLightUpdates(LevelAccessor level) { + // The client level is guaranteed to receive updates. + if (Minecraft.getInstance().level == level) { + return true; + } + // Custom/fake levels need to indicate that LightUpdater has meaning. + if (level instanceof LightUpdated c) { + return c.receivesLightUpdates(); + } + return false; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index c1b3b4f54..4958fd682 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -20,14 +20,23 @@ import net.minecraft.world.level.LightLayer; /** * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. + * + * @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement + * {@link LightUpdated} for LightUpdater to work with them. */ public class LightUpdater { - private static final WorldAttached light = new WorldAttached<>(LightUpdater::new); + private static final WorldAttached LEVELS = new WorldAttached<>(LightUpdater::new); private final ParallelTaskEngine taskEngine; - public static LightUpdater get(LevelAccessor world) { - return light.get(world); + public static LightUpdater get(LevelAccessor level) { + if (LightUpdated.receivesLightUpdates(level)) { + // The level is valid, add it to the map. + return LEVELS.get(level); + } else { + // Fake light updater for a fake level. + return DummyLightUpdater.INSTANCE; + } } private final LightProvider provider; diff --git a/src/main/java/com/jozufozu/flywheel/util/WorldAttached.java b/src/main/java/com/jozufozu/flywheel/util/WorldAttached.java index 408b54e51..45b989f2f 100644 --- a/src/main/java/com/jozufozu/flywheel/util/WorldAttached.java +++ b/src/main/java/com/jozufozu/flywheel/util/WorldAttached.java @@ -28,7 +28,7 @@ public class WorldAttached { } public static void invalidateWorld(LevelAccessor world) { - Iterator>> i = allMaps.iterator(); + var i = allMaps.iterator(); while (i.hasNext()) { Map map = i.next() .get();