Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge working parts of fastload #434

Merged
merged 7 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/config/SpeedupsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ public class SpeedupsConfig {
@Config.DefaultBoolean(true)
public static boolean speedupChunkProviderClient;

@Config.Comment("Rewrites internal cache methods to be safer and faster. Experimental, use at your own risk!")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean fastIntCache;

@Config.Comment("Removes hard caps on chunk handling speed. Experimental and probably incompatible with hybrid servers!")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean fastChunkHandling;

@Config.Comment("The maximum speed of chunkloading per player, in chunks/tick. High values may overload clients! Only active with fastChunkHandling.\n"
+ "For reference: Vanilla is 5, or 100 chunks/sec. At 32 render distance = 4225 chunks, loading the world would take 42.25 seconds.")
@Config.DefaultInt(50)
@Config.RangeInt(min = 5)
public static int maxSendSpeed;

@Config.Comment("The maximum speed of chunk unloading, in chunks/tick. High values may overload servers! Only active with fastChunkHandling.\n"
+ "For reference: Vanilla is 100, or 2000 chunks/sec. At 32 render distance = 4225 chunks, unloading the world would take 2.1125 seconds.")
@Config.DefaultInt(220)
@Config.RangeInt(min = 100)
public static int maxUnloadSpeed;

// Biomes O' Plenty

@Config.Comment("Speedup biome fog rendering in BiomesOPlenty")
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/hax/FastIntCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mitchej123.hodgepodge.hax;

import java.util.List;
import java.util.Map;

import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

public class FastIntCache {

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

private static final Map<Integer, List<int[]>> cachedObjects = new Int2ObjectOpenHashMap<>();

public static synchronized 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);

final List<int[]> caches = cachedObjects.computeIfAbsent(level, i -> new ObjectArrayList<>());

if (caches.isEmpty()) return new int[2 << (level - 1)];
return caches.remove(caches.size() - 1);
}

public static synchronized void releaseCache(int[] cache) {

final int level = (cache.length <= SMALLEST) ? MIN_LEVEL : 32 - Integer.numberOfLeadingZeros(cache.length - 1);
cachedObjects.computeIfAbsent(level, i -> new ObjectArrayList<>()).add(cache);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/mixins/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,16 @@ public enum Mixins {
new Builder("Memory fixes").setPhase(Phase.EARLY).setSide(Side.CLIENT).addTargetedMod(TargetedMod.VANILLA)
.addMixinClasses("memory.MixinFMLClientHandler").setApplyIf(() -> FixesConfig.enableMemoryFixes)),

FAST_INT_CACHE(new Builder("Rewrite internal caching methods to be safer and faster").setPhase(Phase.EARLY)
.setSide(Side.BOTH).addTargetedMod(TargetedMod.VANILLA)
.addMixinClasses("minecraft.fastload.MixinIntCache", "minecraft.fastload.MixinWorldChunkManager")
.setApplyIf(() -> SpeedupsConfig.fastIntCache)),

FAST_CHUNK_LOADING(new Builder("Invasively accelerates chunk handling").setPhase(Phase.EARLY).setSide(Side.BOTH)
.addTargetedMod(TargetedMod.VANILLA)
.addMixinClasses("minecraft.fastload.MixinEntityPlayerMP", "minecraft.fastload.MixinChunkProviderServer")
.setApplyIf(() -> SpeedupsConfig.fastChunkHandling)),

MEMORY_FIXES_IC2(new Builder("Removes allocation spam from the Direction.applyTo method").setPhase(Phase.LATE)
.setSide(Side.BOTH).addMixinClasses("ic2.MixinDirection_Memory")
.setApplyIf(() -> FixesConfig.enableMemoryFixes).addTargetedMod(TargetedMod.IC2)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.mitchej123.hodgepodge.mixins.early.minecraft.fastload;

import net.minecraft.world.gen.ChunkProviderServer;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;

import com.mitchej123.hodgepodge.config.SpeedupsConfig;

@Mixin(ChunkProviderServer.class)
public class MixinChunkProviderServer {

@ModifyConstant(method = "unloadQueuedChunks", constant = @Constant(intValue = 100, ordinal = 0))
private int hodgepodge$fastChunkDiscard(int original) {
return SpeedupsConfig.maxUnloadSpeed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package com.mitchej123.hodgepodge.mixins.early.minecraft.fastload;

import java.util.Iterator;
import java.util.List;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.play.server.S26PacketMapChunkBulk;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.ChunkWatchEvent;

import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import com.mitchej123.hodgepodge.config.SpeedupsConfig;
import com.mojang.authlib.GameProfile;

import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;

@Mixin(EntityPlayerMP.class)
public abstract class MixinEntityPlayerMP extends EntityPlayer {

@Shadow
@Final
public List<ChunkCoordIntPair> loadedChunks;

@Shadow
public NetHandlerPlayServer playerNetServerHandler;

@Shadow
protected abstract void func_147097_b(TileEntity te);

// Cache these lists so they don't have to be reallocated every onUpdate
@Unique
private final ObjectArrayList<ObjectImmutableList<Chunk>> hodgepodge$chunkSends = new ObjectArrayList<>();
@Unique
private final ObjectArrayList<Chunk> hodgepodge$rollingChunks = new ObjectArrayList<>();
@Unique
private final ObjectArrayList<TileEntity> hodgepodge$rollingTEs = new ObjectArrayList<>();

@Shadow
public abstract WorldServer getServerForPlayer();

public MixinEntityPlayerMP(World world, GameProfile profile) {
super(world, profile);
}

/**
* This injects just before where vanilla handles chunk sending and returns before then. This will mess up any
* mixins that target the tail of this function, but other solutions are similarly invasive.
*/
@Inject(
method = "onUpdate",
at = @At(
value = "FIELD",
target = "Lnet/minecraft/entity/player/EntityPlayerMP;loadedChunks:Ljava/util/List;",
shift = At.Shift.BEFORE,
ordinal = 0),
cancellable = true)
private void hodgepodge$replaceChunkSending(CallbackInfo ci) {
if (loadedChunks.isEmpty()) {
ci.cancel();
return;
}

int numChunks = 0;
final Iterator<ChunkCoordIntPair> allChunks = loadedChunks.iterator();
final int chunksPerPacket = S26PacketMapChunkBulk.func_149258_c();

// Gather chunks to send, unload chunks outside of range
while (allChunks.hasNext() && numChunks < SpeedupsConfig.maxSendSpeed) {
final ChunkCoordIntPair ccip = allChunks.next();
if (ccip == null) {
allChunks.remove();
continue;
}

if (!worldObj.blockExists(ccip.chunkXPos << 4, 0, ccip.chunkZPos << 4)) {
continue;
}

final Chunk chunk = worldObj.getChunkFromChunkCoords(ccip.chunkXPos, ccip.chunkZPos);
if (!chunk.func_150802_k()) { // chunk.isNotEmpty()
continue;
}

// If there's enough to fill a packet, overflow it
if (hodgepodge$rollingChunks.size() == chunksPerPacket) {
hodgepodge$chunkSends.add(new ObjectImmutableList<>(hodgepodge$rollingChunks));
hodgepodge$rollingChunks.clear();
}
hodgepodge$rollingChunks.add(chunk);
hodgepodge$rollingTEs.addAll(
((WorldServer) worldObj).func_147486_a(
ccip.chunkXPos * 16,
0,
ccip.chunkZPos * 16,
ccip.chunkXPos * 16 + 15,
256,
ccip.chunkZPos * 16 + 15));
allChunks.remove();
numChunks++;
}

// Since it only gets cleared when it's full, rollingChunks always holds the last ones
hodgepodge$chunkSends.add(new ObjectImmutableList<>(hodgepodge$rollingChunks));

if (numChunks > 0) {
for (int i = 0; i < hodgepodge$chunkSends.size(); ++i) {
playerNetServerHandler.sendPacket(new S26PacketMapChunkBulk(hodgepodge$chunkSends.get(i)));
}

for (TileEntity tileentity : hodgepodge$rollingTEs) {
func_147097_b(tileentity);
}

for (int i = 0; i < numChunks; ++i) {
final int div = i / chunksPerPacket;
final int rem = i % chunksPerPacket;
final Chunk chunk = hodgepodge$chunkSends.get(div).get(rem);
getServerForPlayer().getEntityTracker().func_85172_a((EntityPlayerMP) (Object) this, chunk);
MinecraftForge.EVENT_BUS
.post(new ChunkWatchEvent.Watch(chunk.getChunkCoordIntPair(), (EntityPlayerMP) (Object) this));
}
}
hodgepodge$chunkSends.clear();
hodgepodge$rollingChunks.clear();
hodgepodge$rollingTEs.clear();
ci.cancel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mitchej123.hodgepodge.mixins.early.minecraft.fastload;

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

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;

import com.mitchej123.hodgepodge.hax.FastIntCache;

@Mixin(IntCache.class)
public class MixinIntCache {

/**
* @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 FastIntCache.getCache(size);
}
}
Loading