Skip to content

Commit

Permalink
Improved IntCache backend
Browse files Browse the repository at this point in the history
Co-authored-by: ah-OOG-ah
  • Loading branch information
FalsePattern committed Jul 14, 2024
1 parent fc13523 commit fdb8cb1
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/main/java/mega/blendtronic/config/MinecraftConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ public final class MinecraftConfig {
@Config.DefaultBoolean(true)
public static boolean cauldronTank;

@Config.Comment("[SERVER] Replaces the world generator integer cache with a more thread safe and optimized variant.")
@Config.DefaultBoolean(true)
public static boolean newIntCache;

static {
ConfigurationManager.selfInit();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Blendtronic
*
* Copyright (C) 2021-2024 SirFell, the MEGA team
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package mega.blendtronic.mixin.mixins.common.intcache;

import mega.blendtronic.modules.intcache.NewIntCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;

import net.minecraft.world.gen.layer.IntCache;

@Mixin(IntCache.class)
public abstract class IntCacheMixin {
/**
* @author ah-OOG-ah
* @reason The old methods are non-threadsafe and barely safe at all - they recycle all allocated instances instead
* of explicitly releasing them.
*/
@Overwrite
public static synchronized int[] getIntCache(int size) {
return NewIntCache.getCache(size);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Blendtronic
*
* Copyright (C) 2021-2024 SirFell, the MEGA team
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package mega.blendtronic.mixin.mixins.common.intcache;

import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import mega.blendtronic.modules.intcache.NewIntCache;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.world.ChunkPosition;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.WorldChunkManager;

import java.util.List;
import java.util.Random;

/**
* @author ah-OOG-ah
* @author falsepattern
*
* The hodgepodge version reimplements a lot of the logic with inject-cancels. This is more robust than their solution.
*/
@Mixin(WorldChunkManager.class)
public abstract class WorldChunkManagerMixin {
@Redirect(method = { "getRainfall", "getBiomesForGeneration", "areBiomesViable",
"getBiomeGenAt([Lnet/minecraft/world/biome/BiomeGenBase;IIIIZ)[Lnet/minecraft/world/biome/BiomeGenBase;",
"findBiomePosition" },
at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/gen/layer/IntCache;resetIntCache()V"))
private void nukeIntCacheReset() {}

@Inject(method = "getRainfall",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER))
private void getRainfall$cachePreReturn(float[] listToReuse, int x, int z, int width, int length,
CallbackInfoReturnable<float[]> cir, @Local(name = "aint") int[] ints, @Share("intsPass") LocalRef<int[]> intsPass) {
intsPass.set(ints);
}

@Inject(method = "getRainfall",
at = @At("RETURN"))
private void getRainfall$releaseCache(float[] listToReuse, int x, int z, int width, int length,
CallbackInfoReturnable<float[]> cir, @Share("intsPass") LocalRef<int[]> intsPass) {
NewIntCache.releaseCache(intsPass.get());
}

@Inject(method = "getBiomesForGeneration",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER))
private void getBiomesForGeneration$cachePreReturn(BiomeGenBase[] biomes, int x, int z, int width, int height,
CallbackInfoReturnable<BiomeGenBase[]> cir, @Local(name = "aint") int[] ints, @Share("intsPass") LocalRef<int[]> intsPass) {
intsPass.set(ints);
}

@Inject(method = "getBiomesForGeneration",
at = @At("RETURN"))
private void getBiomesForGeneration$releaseCache(BiomeGenBase[] biomes, int x, int z, int width, int height,
CallbackInfoReturnable<BiomeGenBase[]> cir, @Share("intsPass") LocalRef<int[]> intsPass) {
NewIntCache.releaseCache(intsPass.get());
}

@Inject(method = "getBiomeGenAt([Lnet/minecraft/world/biome/BiomeGenBase;IIIIZ)[Lnet/minecraft/world/biome/BiomeGenBase;",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER))
private void getBiomeGenAt$cachePreReturn(BiomeGenBase[] listToReuse, int x, int y, int width, int length, boolean cacheFlag,
CallbackInfoReturnable<BiomeGenBase[]> cir, @Local(name = "aint") int[] ints, @Share("intsPass") LocalRef<int[]> intsPass) {
intsPass.set(ints);
}

@Inject(method = "getBiomeGenAt([Lnet/minecraft/world/biome/BiomeGenBase;IIIIZ)[Lnet/minecraft/world/biome/BiomeGenBase;",
at = @At("RETURN"),
slice = @Slice(from = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER)))
private void getBiomeGenAt$releaseCache(BiomeGenBase[] listToReuse, int x, int y, int width, int length, boolean cacheFlag,
CallbackInfoReturnable<BiomeGenBase[]> cir, @Share("intsPass") LocalRef<int[]> intsPass) {
NewIntCache.releaseCache(intsPass.get());
}

@Inject(method = "areBiomesViable",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER))
private void areBiomesViable$cachePreReturn(int x, int z, int radius, List<BiomeGenBase> allowed,
CallbackInfoReturnable<Boolean> cir, @Local(name = "aint") int[] ints, @Share("intsPass") LocalRef<int[]> intsPass) {
intsPass.set(ints);
}

@Inject(method = "areBiomesViable",
at = @At("RETURN"))
private void areBiomesViable$releaseCache(int x, int z, int radius, List<BiomeGenBase> allowed,
CallbackInfoReturnable<Boolean> cir, @Share("intsPass") LocalRef<int[]> intsPass) {
NewIntCache.releaseCache(intsPass.get());
}

@Inject(method = "findBiomePosition",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/gen/layer/GenLayer;getInts(IIII)[I",
shift = At.Shift.AFTER))
private void findBiomePosition$cachePreReturn(int p_150795_1_, int p_150795_2_, int p_150795_3_, List<BiomeGenBase> p_150795_4_, Random p_150795_5_,
CallbackInfoReturnable<ChunkPosition> cir, @Local(name = "aint") int[] ints, @Share("intsPass") LocalRef<int[]> intsPass) {
intsPass.set(ints);
}

@Inject(method = "findBiomePosition",
at = @At("RETURN"))
private void findBiomePosition$releaseCache(int p_150795_1_, int p_150795_2_, int p_150795_3_, List<BiomeGenBase> p_150795_4_, Random p_150795_5_,
CallbackInfoReturnable<ChunkPosition> cir, @Share("intsPass") LocalRef<int[]> intsPass) {
NewIntCache.releaseCache(intsPass.get());
}

}
7 changes: 6 additions & 1 deletion src/main/java/mega/blendtronic/mixin/plugin/Mixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ public enum Mixin implements IMixin {
// region BuildCraft
common_buildcraft_TileHopperMixin(COMMON, require(BUILDCRAFT).and(condition(() -> buildCraftChuteImprovements)), "buildcraft.TileHopperMixin"),

common_thaumcraft_TileCruicble(COMMON,require(THAUMCRAFT),"thaumcraft.TileCrucibleMixin")
common_thaumcraft_TileCruicble(COMMON,require(THAUMCRAFT),"thaumcraft.TileCrucibleMixin"),
// endregion

// region IntCache
common_intcache_IntCacheMixin(COMMON, condition(() -> newIntCache), "intcache.IntCacheMixin"),
common_intcache_WorldChunkManagerMixin(COMMON, condition(() -> newIntCache), "intcache.WorldChunkManagerMixin"),
// endregion
;

Expand Down
88 changes: 88 additions & 0 deletions src/main/java/mega/blendtronic/modules/intcache/NewIntCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Blendtronic
*
* Copyright (C) 2021-2024 SirFell, the MEGA team
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package mega.blendtronic.modules.intcache;

import java.util.List;
import java.util.concurrent.Semaphore;

import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;

/**
* Ported over from Hodgepodge, with some extra optimizations on our side
*
* @author ah-OOG-ah
* @author falsepattern
*/
public class NewIntCache {

private static final int SMALLEST = 256;
private static final int MIN_LEVEL = 32 - Integer.numberOfLeadingZeros(SMALLEST - 1);

private static final Int2ObjectMap<ObjectList<int[]>> cachedObjects = new Int2ObjectOpenHashMap<>();

private static final Semaphore SEMAPHORE = new Semaphore(1);

@SuppressWarnings("Convert2Lambda") //No lambdas to minimize allocations
private static final Int2ObjectFunction<ObjectList<int[]>> LIST_CONSTRUCTOR = new Int2ObjectFunction<>() {
@Override
public ObjectList<int[]> get(int key) {
return new ObjectArrayList<>(16);
}
};

public static int[] getCache(int size) {
// Get the smallest power of two larger than or equal to the number
final int level = (size <= SMALLEST) ? MIN_LEVEL : 32 - Integer.numberOfLeadingZeros(size - 1);

while (!SEMAPHORE.tryAcquire()) {
Thread.onSpinWait();
}
try {
final List<int[]> caches = cachedObjects.computeIfAbsent(level, LIST_CONSTRUCTOR);

if (caches.isEmpty()) {
return new int[2 << (level - 1)];
}
return caches.removeLast();
} finally {
SEMAPHORE.release();
}
}

public static void releaseCache(int[] cache) {
final int level = (cache.length <= SMALLEST) ? MIN_LEVEL : 32 - Integer.numberOfLeadingZeros(cache.length - 1);
while (!SEMAPHORE.tryAcquire()) {
Thread.onSpinWait();
}
try {
cachedObjects.computeIfAbsent(level, LIST_CONSTRUCTOR).add(cache);
} finally {
SEMAPHORE.release();
}
}
}

0 comments on commit fdb8cb1

Please sign in to comment.