Skip to content

[Savestates] Fixed world being unloaded after loadstate #247

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

Merged
merged 2 commits into from
May 27, 2025
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.minecrafttas.tasmod.mixin.savestates;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

import com.google.common.collect.ComparisonChain;
import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler;
import com.minecrafttas.tasmod.util.Ducks.PlayerChunkMapDuck;

import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.management.PlayerChunkMap;
import net.minecraft.server.management.PlayerChunkMapEntry;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;

@Mixin(PlayerChunkMap.class)
public abstract class MixinPlayerChunkMap implements PlayerChunkMapDuck {

@Shadow
@Final
private List<EntityPlayerMP> players;
@Shadow
private boolean sortMissingChunks;
@Shadow
@Final
private List<PlayerChunkMapEntry> entriesWithoutChunks;
@Shadow
private boolean sortSendToPlayers;
@Shadow
@Final
private List<PlayerChunkMapEntry> pendingSendToPlayers;
@Shadow
@Final
private WorldServer world;

/**
* @return The players from the specified chunk map
* @see SavestateWorldHandler#addPlayerToChunkMap()
*/
@Override
public List<EntityPlayerMP> getPlayers() {
return players;
}

/**
* {@inheritDoc}
* @see SavestateWorldHandler#addPlayersToChunkMap()
*/
@Override
public void forceTick() {

/*
* Update the chunks to make them eligible to be sent to the client
*
* In the #sendToPlayers() method is a check where the chunk is not sent,
* when Chunk#isPopulated() is false. This would normally happen during the WorldServer#updateBlocks() method,
* but we want to send the chunks without updating the blocks, hence this is circumvented like this.
*/
for (Iterator<Chunk> iterator2 = this.getChunkIterator(); iterator2.hasNext();) {
Chunk chunk = (Chunk) iterator2.next();
chunk.enqueueRelightChecks();
chunk.onTick(false);
}

this.sortMissingChunks = false;
Collections.sort(this.entriesWithoutChunks, new Comparator<PlayerChunkMapEntry>() {
public int compare(PlayerChunkMapEntry playerChunkMapEntry, PlayerChunkMapEntry playerChunkMapEntry2) {
return ComparisonChain.start().compare(playerChunkMapEntry.getClosestPlayerDistance(), playerChunkMapEntry2.getClosestPlayerDistance()).result();
}
});

this.sortSendToPlayers = false;
Collections.sort(this.pendingSendToPlayers, new Comparator<PlayerChunkMapEntry>() {

public int compare(PlayerChunkMapEntry playerChunkMapEntry, PlayerChunkMapEntry playerChunkMapEntry2) {
return ComparisonChain.start().compare(playerChunkMapEntry.getClosestPlayerDistance(), playerChunkMapEntry2.getClosestPlayerDistance()).result();
}
});

if (!this.pendingSendToPlayers.isEmpty()) {

int i = 81;
Iterator<PlayerChunkMapEntry> iterator2 = this.pendingSendToPlayers.iterator();

while (iterator2.hasNext()) {
PlayerChunkMapEntry playerChunkMapEntry3 = (PlayerChunkMapEntry) iterator2.next();
if (playerChunkMapEntry3.sendToPlayers()) {
iterator2.remove();
if (--i < 0) {
break;
}
}
}
}

if (this.players.isEmpty()) {
WorldProvider worldProvider = this.world.provider;
if (!worldProvider.canRespawnHere()) {
this.world.getChunkProvider().queueUnloadAll();
}
}
}

@Shadow
protected abstract Iterator<Chunk> getChunkIterator();
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ public enum TASmodKeybinds {
TEST1("Various Testing", "TASmod", Keyboard.KEY_F12, () -> {
}, VirtualKeybindings::isKeyDown),
TEST2("Various Testing2", "TASmod", Keyboard.KEY_F7, () -> {
// try {
// TASmodClient.client = new Client("localhost", TASmod.networkingport - 1, TASmodPackets.values(), mc.getSession().getProfile().getName(), true);
// } catch (Exception e) {
// e.printStackTrace();
// }
// TASmodClient.controller.setTASState(TASstate.PLAYBACK);
}, VirtualKeybindings::isKeyDown);

private Keybind keybind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,7 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex
server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToLoad + " loaded"));

// Add players to the chunk
server.getPlayerList().getPlayers().forEach(player -> {
worldHandler.addPlayerToServerChunk(player);
});
worldHandler.addPlayersToServerChunks();

worldHandler.sendChunksToClient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

import java.util.List;

import com.minecrafttas.tasmod.mixin.savestates.AccessorPlayerChunkMap;
import com.minecrafttas.tasmod.mixin.savestates.MixinChunkProviderServer;
import com.minecrafttas.tasmod.savestates.SavestateHandlerClient;
import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck;
import com.minecrafttas.tasmod.util.Ducks.WorldServerDuck;
import com.minecrafttas.tasmod.util.Ducks.PlayerChunkMapDuck;
import com.minecrafttas.tasmod.util.LoggerMarkers;

import net.minecraft.client.Minecraft;
Expand Down Expand Up @@ -55,6 +54,15 @@ public void enableLevelSaving() {
}
}

/**
* Add players to their respective chunks
*/
public void addPlayersToServerChunks() {
server.getPlayerList().getPlayers().forEach(player -> {
addPlayerToServerChunk(player);
});
}

/**
* Just like {@link SavestateHandlerClient#addPlayerToClientChunk(EntityPlayer)}, adds the player to the chunk on the server.
* This prevents the player from being able to place block inside of him
Expand Down Expand Up @@ -150,15 +158,14 @@ private void addPlayerToChunkMap(WorldServer world, EntityPlayerMP player) {
int playerChunkPosY = (int) player.posZ >> 4;
PlayerChunkMap playerChunkMap = world.getPlayerChunkMap();

List<EntityPlayerMP> players = ((AccessorPlayerChunkMap) playerChunkMap).getPlayers();
List<EntityPlayerMP> players = ((PlayerChunkMapDuck) playerChunkMap).getPlayers();

if (players.contains(player)) {
LOGGER.debug(LoggerMarkers.Savestate, "Not adding player {} to chunkmap, player already exists", player.getName());
} else {
playerChunkMap.addPlayer(player);
}
Chunk chunk = world.getChunkProvider().provideChunk(playerChunkPosX, playerChunkPosY);
chunk.addEntity(player);
world.getChunkProvider().provideChunk(playerChunkPosX, playerChunkPosY);

world.spawnEntity(player);
}
Expand Down Expand Up @@ -205,8 +212,8 @@ public void sendChunksToClient() {
WorldServer[] worlds = server.worlds;

for (WorldServer world : worlds) {
WorldServerDuck worldTick = (WorldServerDuck) world;
worldTick.sendChunksToClient();
PlayerChunkMapDuck chunkMap = (PlayerChunkMapDuck) world.getPlayerChunkMap();
chunkMap.forceTick();
}
}

Expand Down
41 changes: 27 additions & 14 deletions src/main/java/com/minecrafttas/tasmod/util/Ducks.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.minecrafttas.tasmod.util;

import java.util.List;

import com.minecrafttas.tasmod.mixin.savestates.MixinPlayerChunkMap;

import net.minecraft.entity.player.EntityPlayerMP;

/**
* Oh boy, ducks! I can't help but quack up when they waddle their way into the code. :duck:
* But let me tell you a little secret: I have a love-hate relationship with ducks. Not the adorable feathered creatures, mind you, but those sneaky little programming devils that swim in the deep waters of out-of-scope variables.
Expand Down Expand Up @@ -32,20 +38,6 @@ public static interface ChunkProviderDuck {
public void unloadAllChunks();
}

/**
* Quacks the worldserver to implement custom chunk ticking behavior
*
* @author Scribble
*/
public static interface WorldServerDuck {

/**
* Sends the chunks to the client
* @see com.minecrafttas.tasmod.mixin.savestates.MixinWorldServer#sendChunksToClient() MixinWorldServer#sendChunksToClient()
*/
public void sendChunksToClient();
}

/**
* Quacks the gui screen to spit out mouse positions independent of the display size
*/
Expand Down Expand Up @@ -129,4 +121,25 @@ public static interface WorldClientDuck {
*/
public void clearEntityList();
}

/**
* Quacks the {@link MixinPlayerChunkMap}
*/
public static interface PlayerChunkMapDuck {
/**
* @return The list of players of this chunk map
*/
public List<EntityPlayerMP> getPlayers();

/**
* <p>Forces a tick in the chunk map without being dependent on the world time.
* <p>The chunk map is responsible for sending the necessary chunks to the client.<br>
* However, to properly do that, the chunks have to be sorted first, which happens every few world ticks.
* <p>Under normal circumstances, WorldServer#tick() would update the world time to make this happen,
* but our goal with savestates is to load the chunks without advancing the world time.
* Hence why this method sorts and ticks the chunk map, without waiting for the world time
* @see MixinPlayerChunkMap#forceTick()
*/
public void forceTick();
}
}
3 changes: 1 addition & 2 deletions src/main/resources/tasmod.mixin.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"savestates.AccessorAnvilChunkLoader",
"savestates.AccessorChunkLoader",
"savestates.AccessorEntityLivingBase",
"savestates.AccessorPlayerChunkMap",
"savestates.MixinPlayerChunkMap",
"savestates.MixinChunkProviderServer",
"savestates.MixinNetHandlerPlayServer",
"savestates.MixinScoreboard",
"savestates.MixinWorldServer",

// Events
"events.MixinEntityPlayerMP",
Expand Down
Loading