From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Wed, 23 Nov 2022 20:35:40 +0100 Subject: [PATCH] Improve fluid direction caching Removed since 1.21.1, Paper 1.21.3, or Moonrise's imple is a bit faster License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) Gale - https://galemc.org This patch is based on the following patch: "Improve fluid direction caching" By: Paul Sauve As part of: Airplane (https://github.com/TECHNOVE/Airplane) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) * Airplane description * Implements a custom cache that better fits the needs of fluids calculating whether a direction can be moved in or something. There's a big javadoc on the FluidDirectionCache with some more information. * Airplane copyright * Airplane Copyright (C) 2020 Technove LLC This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/src/main/java/gg/airplane/structs/FluidDirectionCache.java b/src/main/java/gg/airplane/structs/FluidDirectionCache.java new file mode 100644 index 0000000000000000000000000000000000000000..bf6a36c933b686f0e34591dff3d43b3797c29b2b --- /dev/null +++ b/src/main/java/gg/airplane/structs/FluidDirectionCache.java @@ -0,0 +1,143 @@ +// Gale - Airplane - improve fluid direction caching + +package gg.airplane.structs; + +import it.unimi.dsi.fastutil.HashCommon; + +/** + * This is a replacement for the cache used in FluidTypeFlowing. + * The requirements for the previous cache were: + *
    + *
  • Store 200 entries
  • + *
  • Look for the flag in the cache
  • + *
  • If it exists, move to front of cache
  • + *
  • If it doesn't exist, remove last entry in cache and insert in front
  • + *
+ * This class accomplishes something similar, however has a few different + * requirements put into place to make this more optimize: + *
    + *
  • + * maxDistance is the most amount of entries to be checked, instead + * of having to check the entire list. + *
  • + *
  • + * In combination with that, entries are all tracked by age and how + * frequently they're used. This enables us to remove old entries, + * without constantly shifting any around. + *
  • + *
+ * Usage of the previous map would have to reset the head every single usage, + * shifting the entire map. Here, nothing happens except an increment when + * the cache is hit, and when it needs to replace an old element only a single + * element is modified. + */ +public class FluidDirectionCache { + + private static class FluidDirectionEntry { + private final T data; + private final boolean flag; + private int uses = 0; + private int age = 0; + + private FluidDirectionEntry(T data, boolean flag) { + this.data = data; + this.flag = flag; + } + + public int getValue() { + return this.uses - (this.age >> 1); // age isn't as important as uses + } + + public void incrementUses() { + this.uses = this.uses + 1 & Integer.MAX_VALUE; + } + + public void incrementAge() { + this.age = this.age + 1 & Integer.MAX_VALUE; + } + } + + private final FluidDirectionEntry[] entries; + private final int mask; + private final int maxDistance; // the most amount of entries to check for a value + + public FluidDirectionCache(int size) { + int arraySize = HashCommon.nextPowerOfTwo(size); + this.entries = new FluidDirectionEntry[arraySize]; + this.mask = arraySize - 1; + this.maxDistance = Math.min(arraySize, 4); + } + + public Boolean getValue(T data) { + FluidDirectionEntry curr; + int pos; + + if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) { + return null; + } else if (data.equals(curr.data)) { + curr.incrementUses(); + return curr.flag; + } + + int checked = 1; // start at 1 because we already checked the first spot above + + while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) { + if (data.equals(curr.data)) { + curr.incrementUses(); + return curr.flag; + } else if (++checked >= this.maxDistance) { + break; + } + } + + return null; + } + + public void putValue(T data, boolean flag) { + FluidDirectionEntry curr; + int pos; + + if ((curr = this.entries[pos = HashCommon.mix(data.hashCode()) & this.mask]) == null) { + this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add + return; + } else if (data.equals(curr.data)) { + curr.incrementUses(); + return; + } + + int checked = 1; // start at 1 because we already checked the first spot above + + while ((curr = this.entries[pos = (pos + 1) & this.mask]) != null) { + if (data.equals(curr.data)) { + curr.incrementUses(); + return; + } else if (++checked >= this.maxDistance) { + this.forceAdd(data, flag); + return; + } + } + + this.entries[pos] = new FluidDirectionEntry<>(data, flag); // add + } + + private void forceAdd(T data, boolean flag) { + int expectedPos = HashCommon.mix(data.hashCode()) & this.mask; + + int toRemovePos = expectedPos; + FluidDirectionEntry entryToRemove = this.entries[toRemovePos]; + + for (int i = expectedPos + 1; i < expectedPos + this.maxDistance; i++) { + int pos = i & this.mask; + FluidDirectionEntry entry = this.entries[pos]; + if (entry.getValue() < entryToRemove.getValue()) { + toRemovePos = pos; + entryToRemove = entry; + } + + entry.incrementAge(); // use this as a mechanism to age the other entries + } + + // remove the least used/oldest entry + this.entries[toRemovePos] = new FluidDirectionEntry(data, flag); + } +} diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index 1c0712295695727ee9c4d430d4157b8e17cbd71f..0eac7bc0c56ac17c900737271a965cfc1ccd03d2 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java @@ -45,6 +45,8 @@ public abstract class FlowingFluid extends Fluid { public static final BooleanProperty FALLING = BlockStateProperties.FALLING; public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_FLOWING; private static final int CACHE_SIZE = 200; + // Gale start - Airplane - improve fluid direction caching - use our own cache + /* private static final ThreadLocal> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> { Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap(200) { protected void rehash(int i) {} @@ -53,6 +55,14 @@ public abstract class FlowingFluid extends Fluid { object2bytelinkedopenhashmap.defaultReturnValue((byte) 127); return object2bytelinkedopenhashmap; }); + */ + + private static final ThreadLocal> localFluidDirectionCache = ThreadLocal.withInitial(() -> { + // Gale - Airplane - TODO - mess with this number for performance + // with 2048 it seems very infrequent on a small world that it has to remove old entries + return new gg.airplane.structs.FluidDirectionCache<>(2048); + }); + // Gale end - Airplane - improve fluid direction caching - use our own cache private final Map shapes = Maps.newIdentityHashMap(); public FlowingFluid() {} @@ -240,6 +250,8 @@ public abstract class FlowingFluid extends Fluid { } private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { + // Gale start - Airplane - improve fluid direction caching - modify to use our cache + /* Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap; if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { @@ -247,9 +259,16 @@ public abstract class FlowingFluid extends Fluid { } else { object2bytelinkedopenhashmap = null; } + */ + gg.airplane.structs.FluidDirectionCache cache = null; + + if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { + cache = localFluidDirectionCache.get(); + } Block.BlockStatePairKey block_a; + /* if (object2bytelinkedopenhashmap != null) { block_a = new Block.BlockStatePairKey(state, fromState, face); byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a); @@ -260,11 +279,22 @@ public abstract class FlowingFluid extends Fluid { } else { block_a = null; } + */ + if (cache != null) { + block_a = new Block.BlockStatePairKey(state, fromState, face); + Boolean flag = cache.getValue(block_a); + if (flag != null) { + return flag; + } + } else { + block_a = null; + } VoxelShape voxelshape = state.getCollisionShape(world, pos); VoxelShape voxelshape1 = fromState.getCollisionShape(world, fromPos); boolean flag = !Shapes.mergedFaceOccludes(voxelshape, voxelshape1, face); + /* if (object2bytelinkedopenhashmap != null) { if (object2bytelinkedopenhashmap.size() == 200) { object2bytelinkedopenhashmap.removeLastByte(); @@ -272,6 +302,11 @@ public abstract class FlowingFluid extends Fluid { object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0)); } + */ + if (cache != null) { + cache.putValue(block_a, flag); + } + // Gale end - Airplane - improve fluid direction caching - modify to use our cache return flag; }