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

Fast chunk loading/generation #360

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dependencies {
transformedModCompileOnly(rfg.deobf("curse.maven:candycraft-251118:2330488"))

runtimeOnly(deobf("https://github.com/makamys/CoreTweaks/releases/download/0.3.3.2/CoreTweaks-1.7.10-0.3.3.2+nomixin.jar"))
runtimeOnlyNonPublishable(deobfCurse("bsprint-227409:2725690"))
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Angelica:1.0.0-alpha47")
}

// Replace when RFG support deobfuscation from notch mappings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public class SpeedupsConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean speedupChunkProviderClient;

@Config.Comment("Lightly threads chunk generation, loading, and discarding. Experimental, use at your own risk!")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean fastChunkHandling;

// Biomes O' Plenty

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/mitchej123/hodgepodge/mixins/Mixins.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@ public enum Mixins {
.addMixinClasses("minecraft.MixinWorldServer_LimitUpdateRecursion")
.setApplyIf(() -> FixesConfig.limitRecursiveBlockUpdateDepth >= 0)),

FAST_CHUNK_LOADING(new Builder("Lightly threads chunk generation and loading").setPhase(Phase.EARLY)
.setSide(Side.BOTH).addTargetedMod(TargetedMod.VANILLA)
.addMixinClasses(
"minecraft.fastload.MixinIntCache",
"minecraft.fastload.MixinWorldChunkManager",
"minecraft.fastload.MixinWorldServer",
"minecraft.fastload.MixinEntityPlayerMP",
"minecraft.fastload.MixinPlayerManager",
"minecraft.fastload.MixinPlayerInstance")
.setApplyIf(() -> SpeedupsConfig.fastChunkHandling)),

// Ic2 adjustments
IC2_UNPROTECTED_GET_BLOCK_FIX(new Builder("IC2 Kinetic Fix").setPhase(Phase.EARLY).setSide(Side.BOTH)
.addMixinClasses("ic2.MixinIc2WaterKinetic").setApplyIf(() -> FixesConfig.fixIc2UnprotectedGetBlock)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.mitchej123.hodgepodge.mixins.early.minecraft.fastload;

import static com.mitchej123.hodgepodge.Common.log;

import java.util.List;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.ICrafting;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.play.server.S26PacketMapChunkBulk;
import net.minecraft.tileentity.TileEntity;
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.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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import com.mitchej123.hodgepodge.mixins.interfaces.ExtEntityPlayerMP;
import com.mitchej123.hodgepodge.util.ChunkPosUtil;
import com.mojang.authlib.GameProfile;

import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;

@Mixin(EntityPlayerMP.class)
public abstract class MixinEntityPlayerMP extends EntityPlayer implements ICrafting, ExtEntityPlayerMP {

@Unique
private final List<ObjectImmutableList<Chunk>> hodgepodge$chunkSends = new ObjectArrayList<>();
@Unique
private final List<Chunk> hodgepodge$rollingChunks = new ObjectArrayList<>();
@Unique
private final List<TileEntity> hodgepodge$rollingTEs = new ObjectArrayList<>();
@Unique
private int hodgepodge$totalChunks = 0;
@Unique
private static final S26PacketMapChunkBulk hodgepodge$dummyPacket = new S26PacketMapChunkBulk();
@Unique
private boolean hodgepodge$isThrottled = false;
@Unique
private boolean hodgepodge$wasThrottled = false;
@Unique
private final LongOpenHashSet hodgepodge$loadedChunks = new LongOpenHashSet();
@Unique
private final LongOpenHashSet hodgepodge$chunksToLoad = new LongOpenHashSet();

@Override
public void setThrottled(boolean val) {
this.hodgepodge$isThrottled = val;
}

@Override
public boolean isThrottled() {
return this.hodgepodge$isThrottled;
}

@Override
public void setWasThrottled(boolean val) {
this.hodgepodge$wasThrottled = val;
}

@Override
public boolean wasThrottled() {
return this.hodgepodge$wasThrottled;
}

@Override
public LongOpenHashSet chunksToLoad() {
return this.hodgepodge$chunksToLoad;
}

@Override
public LongOpenHashSet loadedChunks() {
return this.hodgepodge$loadedChunks;
}

@Shadow
public abstract WorldServer getServerForPlayer();

@Shadow
public NetHandlerPlayServer playerNetServerHandler;

@Shadow
protected abstract void func_147097_b(TileEntity p_147097_1_);

public MixinEntityPlayerMP(World p_i45324_1_, GameProfile p_i45324_2_) {
super(p_i45324_1_, p_i45324_2_);
}

@Redirect(method = "onUpdate", at = @At(value = "INVOKE", target = "Ljava/util/List;isEmpty()Z", ordinal = 1))
private boolean hodgepodge$skipOGChunkList(List instance) {
return true;
}

@Inject(method = "onUpdate", at = @At(value = "TAIL"))
private void hodgepodge$replaceChunkList(CallbackInfo ci) {

if (this.hodgepodge$chunksToLoad.longStream().anyMatch(this.hodgepodge$loadedChunks::contains))
log.warn("sending duplicate!!!");

final int chunksPPacket = S26PacketMapChunkBulk.func_149258_c();
final LongIterator chunkKeys = this.hodgepodge$chunksToLoad.longIterator();
Chunk chunk;

// For every chunk...
while (chunkKeys.hasNext()) {

final long key = chunkKeys.nextLong();
final int cx = ChunkPosUtil.getPackedX(key);
final int cz = ChunkPosUtil.getPackedZ(key);

// Only send the chunk if it exists
if (this.worldObj.blockExists(cx << 4, 0, cz << 4)) {
chunk = this.worldObj.getChunkFromChunkCoords(cx, cz);

if (chunk.func_150802_k()) {

++this.hodgepodge$totalChunks;
this.hodgepodge$rollingChunks.add(chunk);
this.hodgepodge$loadedChunks.add(key);
this.hodgepodge$rollingTEs.addAll(
((WorldServer) this.worldObj)
.func_147486_a(cx << 4, 0, cz << 4, (cx << 4) + 15, 256, (cz << 16) + 15));
// BugFix: 16 makes it load an extra chunk, which isn't associated with a player, which makes it not
// unload unless a player walks near it.
chunkKeys.remove();
}
}

// Don't overflow the packet size
if (this.hodgepodge$rollingChunks.size() == chunksPPacket) {
this.hodgepodge$chunkSends.add(new ObjectImmutableList<>(this.hodgepodge$rollingChunks));
this.hodgepodge$rollingChunks.clear();
}
}

// Catch a half-full packet
if (this.hodgepodge$rollingChunks.size() < chunksPPacket)
this.hodgepodge$chunkSends.add(new ObjectImmutableList<>(this.hodgepodge$rollingChunks));

if (!this.hodgepodge$chunkSends.isEmpty()) {

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

for (int i = 0; i < this.hodgepodge$rollingTEs.size(); ++i) {
this.func_147097_b(this.hodgepodge$rollingTEs.get(i));
}

for (int i = 0; i < this.hodgepodge$totalChunks; ++i) {
chunk = this.hodgepodge$chunkSends.get(i / chunksPPacket).get(i % chunksPPacket);
this.getServerForPlayer().getEntityTracker().func_85172_a((EntityPlayerMP) (Object) this, chunk);
MinecraftForge.EVENT_BUS
.post(new ChunkWatchEvent.Watch(chunk.getChunkCoordIntPair(), (EntityPlayerMP) (Object) this));
}
}

if (this.hodgepodge$totalChunks > S26PacketMapChunkBulk.func_149258_c())
log.info("Sent {} chunks to the client", this.hodgepodge$totalChunks);

this.hodgepodge$totalChunks = 0;
this.hodgepodge$chunkSends.clear();
this.hodgepodge$rollingChunks.clear();
this.hodgepodge$rollingTEs.clear();
}
}
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.server.NewIntCache;

@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 NewIntCache.getCache(size);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.mitchej123.hodgepodge.mixins.early.minecraft.fastload;

import java.util.List;

import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.management.PlayerManager;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import com.llamalad7.mixinextras.sugar.Local;
import com.mitchej123.hodgepodge.mixins.interfaces.ExtEntityPlayerMP;
import com.mitchej123.hodgepodge.util.ChunkPosUtil;

@Mixin(PlayerManager.PlayerInstance.class)
public class MixinPlayerInstance {

@Redirect(
method = "addPlayer",
at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 1))
private boolean hodgepodge$replaceChunkSetAdd(List instance, Object o,
@Local(argsOnly = true) EntityPlayerMP player) {
return ((ExtEntityPlayerMP) player).chunksToLoad().add(ChunkPosUtil.toLong(o));
}

@Redirect(
method = "removePlayer",
at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z", ordinal = 3))
private boolean hodgepodge$replaceChunkSetRemove(List instance, Object o,
@Local(argsOnly = true) EntityPlayerMP player) {
return ((ExtEntityPlayerMP) player).chunksToLoad().remove(ChunkPosUtil.toLong(o));
}

@Redirect(
method = "sendToAllPlayersWatchingChunk",
at = @At(value = "INVOKE", target = "Ljava/util/List;contains(Ljava/lang/Object;)Z"))
private boolean hodgepodge$replaceChunkSetContains(List instance, Object o, @Local EntityPlayerMP player) {
return ((ExtEntityPlayerMP) player).chunksToLoad().remove(ChunkPosUtil.toLong(o));
}
}
Loading
Loading