Skip to content

Commit 50e1564

Browse files
authored
[Savestates] Fixed world being unloaded after loadstate (#247)
The issue was that 1. The chunks, after being loaded, where marked as "ticked = false". So when it came to sending the chunks, a check would prevent that. 2. The chunks were not sorted before sending them. Chunks are sorted every 4 ticks and savestating will also prevent that Changes - Added new MixinPlayerChunkMap and PlayerChunkMapDuck to add new methods to the chunk map - Removed AccessorPlayerChunkmap, as it's handled via ducks - Removed MixinWorldServer and WorldServerDuck as it's merged with the chunkmap duck - Updated some documentation
2 parents 8f310d8 + 41d0d82 commit 50e1564

File tree

8 files changed

+156
-98
lines changed

8 files changed

+156
-98
lines changed

src/main/java/com/minecrafttas/tasmod/mixin/savestates/AccessorPlayerChunkMap.java

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.minecrafttas.tasmod.mixin.savestates;
2+
3+
import java.util.Collections;
4+
import java.util.Comparator;
5+
import java.util.Iterator;
6+
import java.util.List;
7+
8+
import org.spongepowered.asm.mixin.Final;
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.Shadow;
11+
12+
import com.google.common.collect.ComparisonChain;
13+
import com.minecrafttas.tasmod.savestates.handlers.SavestateWorldHandler;
14+
import com.minecrafttas.tasmod.util.Ducks.PlayerChunkMapDuck;
15+
16+
import net.minecraft.entity.player.EntityPlayerMP;
17+
import net.minecraft.server.management.PlayerChunkMap;
18+
import net.minecraft.server.management.PlayerChunkMapEntry;
19+
import net.minecraft.world.WorldProvider;
20+
import net.minecraft.world.WorldServer;
21+
import net.minecraft.world.chunk.Chunk;
22+
23+
@Mixin(PlayerChunkMap.class)
24+
public abstract class MixinPlayerChunkMap implements PlayerChunkMapDuck {
25+
26+
@Shadow
27+
@Final
28+
private List<EntityPlayerMP> players;
29+
@Shadow
30+
private boolean sortMissingChunks;
31+
@Shadow
32+
@Final
33+
private List<PlayerChunkMapEntry> entriesWithoutChunks;
34+
@Shadow
35+
private boolean sortSendToPlayers;
36+
@Shadow
37+
@Final
38+
private List<PlayerChunkMapEntry> pendingSendToPlayers;
39+
@Shadow
40+
@Final
41+
private WorldServer world;
42+
43+
/**
44+
* @return The players from the specified chunk map
45+
* @see SavestateWorldHandler#addPlayerToChunkMap()
46+
*/
47+
@Override
48+
public List<EntityPlayerMP> getPlayers() {
49+
return players;
50+
}
51+
52+
/**
53+
* {@inheritDoc}
54+
* @see SavestateWorldHandler#addPlayersToChunkMap()
55+
*/
56+
@Override
57+
public void forceTick() {
58+
59+
/*
60+
* Update the chunks to make them eligible to be sent to the client
61+
*
62+
* In the #sendToPlayers() method is a check where the chunk is not sent,
63+
* when Chunk#isPopulated() is false. This would normally happen during the WorldServer#updateBlocks() method,
64+
* but we want to send the chunks without updating the blocks, hence this is circumvented like this.
65+
*/
66+
for (Iterator<Chunk> iterator2 = this.getChunkIterator(); iterator2.hasNext();) {
67+
Chunk chunk = (Chunk) iterator2.next();
68+
chunk.enqueueRelightChecks();
69+
chunk.onTick(false);
70+
}
71+
72+
this.sortMissingChunks = false;
73+
Collections.sort(this.entriesWithoutChunks, new Comparator<PlayerChunkMapEntry>() {
74+
public int compare(PlayerChunkMapEntry playerChunkMapEntry, PlayerChunkMapEntry playerChunkMapEntry2) {
75+
return ComparisonChain.start().compare(playerChunkMapEntry.getClosestPlayerDistance(), playerChunkMapEntry2.getClosestPlayerDistance()).result();
76+
}
77+
});
78+
79+
this.sortSendToPlayers = false;
80+
Collections.sort(this.pendingSendToPlayers, new Comparator<PlayerChunkMapEntry>() {
81+
82+
public int compare(PlayerChunkMapEntry playerChunkMapEntry, PlayerChunkMapEntry playerChunkMapEntry2) {
83+
return ComparisonChain.start().compare(playerChunkMapEntry.getClosestPlayerDistance(), playerChunkMapEntry2.getClosestPlayerDistance()).result();
84+
}
85+
});
86+
87+
if (!this.pendingSendToPlayers.isEmpty()) {
88+
89+
int i = 81;
90+
Iterator<PlayerChunkMapEntry> iterator2 = this.pendingSendToPlayers.iterator();
91+
92+
while (iterator2.hasNext()) {
93+
PlayerChunkMapEntry playerChunkMapEntry3 = (PlayerChunkMapEntry) iterator2.next();
94+
if (playerChunkMapEntry3.sendToPlayers()) {
95+
iterator2.remove();
96+
if (--i < 0) {
97+
break;
98+
}
99+
}
100+
}
101+
}
102+
103+
if (this.players.isEmpty()) {
104+
WorldProvider worldProvider = this.world.provider;
105+
if (!worldProvider.canRespawnHere()) {
106+
this.world.getChunkProvider().queueUnloadAll();
107+
}
108+
}
109+
}
110+
111+
@Shadow
112+
protected abstract Iterator<Chunk> getChunkIterator();
113+
}

src/main/java/com/minecrafttas/tasmod/mixin/savestates/MixinWorldServer.java

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/main/java/com/minecrafttas/tasmod/registries/TASmodKeybinds.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,6 @@ public enum TASmodKeybinds {
3939
TEST1("Various Testing", "TASmod", Keyboard.KEY_F12, () -> {
4040
}, VirtualKeybindings::isKeyDown),
4141
TEST2("Various Testing2", "TASmod", Keyboard.KEY_F7, () -> {
42-
// try {
43-
// TASmodClient.client = new Client("localhost", TASmod.networkingport - 1, TASmodPackets.values(), mc.getSession().getProfile().getName(), true);
44-
// } catch (Exception e) {
45-
// e.printStackTrace();
46-
// }
47-
// TASmodClient.controller.setTASState(TASstate.PLAYBACK);
4842
}, VirtualKeybindings::isKeyDown);
4943

5044
private Keybind keybind;

src/main/java/com/minecrafttas/tasmod/savestates/SavestateHandlerServer.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,7 @@ public void loadState(int savestateIndex, boolean tickrate0, boolean changeIndex
394394
server.getPlayerList().sendMessage(new TextComponentString(TextFormatting.GREEN + "Savestate " + indexToLoad + " loaded"));
395395

396396
// Add players to the chunk
397-
server.getPlayerList().getPlayers().forEach(player -> {
398-
worldHandler.addPlayerToServerChunk(player);
399-
});
397+
worldHandler.addPlayersToServerChunks();
400398

401399
worldHandler.sendChunksToClient();
402400

src/main/java/com/minecrafttas/tasmod/savestates/handlers/SavestateWorldHandler.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
import java.util.List;
66

7-
import com.minecrafttas.tasmod.mixin.savestates.AccessorPlayerChunkMap;
87
import com.minecrafttas.tasmod.mixin.savestates.MixinChunkProviderServer;
98
import com.minecrafttas.tasmod.savestates.SavestateHandlerClient;
109
import com.minecrafttas.tasmod.util.Ducks.ChunkProviderDuck;
11-
import com.minecrafttas.tasmod.util.Ducks.WorldServerDuck;
10+
import com.minecrafttas.tasmod.util.Ducks.PlayerChunkMapDuck;
1211
import com.minecrafttas.tasmod.util.LoggerMarkers;
1312

1413
import net.minecraft.client.Minecraft;
@@ -55,6 +54,15 @@ public void enableLevelSaving() {
5554
}
5655
}
5756

57+
/**
58+
* Add players to their respective chunks
59+
*/
60+
public void addPlayersToServerChunks() {
61+
server.getPlayerList().getPlayers().forEach(player -> {
62+
addPlayerToServerChunk(player);
63+
});
64+
}
65+
5866
/**
5967
* Just like {@link SavestateHandlerClient#addPlayerToClientChunk(EntityPlayer)}, adds the player to the chunk on the server.
6068
* This prevents the player from being able to place block inside of him
@@ -150,15 +158,14 @@ private void addPlayerToChunkMap(WorldServer world, EntityPlayerMP player) {
150158
int playerChunkPosY = (int) player.posZ >> 4;
151159
PlayerChunkMap playerChunkMap = world.getPlayerChunkMap();
152160

153-
List<EntityPlayerMP> players = ((AccessorPlayerChunkMap) playerChunkMap).getPlayers();
161+
List<EntityPlayerMP> players = ((PlayerChunkMapDuck) playerChunkMap).getPlayers();
154162

155163
if (players.contains(player)) {
156164
LOGGER.debug(LoggerMarkers.Savestate, "Not adding player {} to chunkmap, player already exists", player.getName());
157165
} else {
158166
playerChunkMap.addPlayer(player);
159167
}
160-
Chunk chunk = world.getChunkProvider().provideChunk(playerChunkPosX, playerChunkPosY);
161-
chunk.addEntity(player);
168+
world.getChunkProvider().provideChunk(playerChunkPosX, playerChunkPosY);
162169

163170
world.spawnEntity(player);
164171
}
@@ -205,8 +212,8 @@ public void sendChunksToClient() {
205212
WorldServer[] worlds = server.worlds;
206213

207214
for (WorldServer world : worlds) {
208-
WorldServerDuck worldTick = (WorldServerDuck) world;
209-
worldTick.sendChunksToClient();
215+
PlayerChunkMapDuck chunkMap = (PlayerChunkMapDuck) world.getPlayerChunkMap();
216+
chunkMap.forceTick();
210217
}
211218
}
212219

src/main/java/com/minecrafttas/tasmod/util/Ducks.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package com.minecrafttas.tasmod.util;
22

3+
import java.util.List;
4+
5+
import com.minecrafttas.tasmod.mixin.savestates.MixinPlayerChunkMap;
6+
7+
import net.minecraft.entity.player.EntityPlayerMP;
8+
39
/**
410
* Oh boy, ducks! I can't help but quack up when they waddle their way into the code. :duck:
511
* 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.
@@ -32,20 +38,6 @@ public static interface ChunkProviderDuck {
3238
public void unloadAllChunks();
3339
}
3440

35-
/**
36-
* Quacks the worldserver to implement custom chunk ticking behavior
37-
*
38-
* @author Scribble
39-
*/
40-
public static interface WorldServerDuck {
41-
42-
/**
43-
* Sends the chunks to the client
44-
* @see com.minecrafttas.tasmod.mixin.savestates.MixinWorldServer#sendChunksToClient() MixinWorldServer#sendChunksToClient()
45-
*/
46-
public void sendChunksToClient();
47-
}
48-
4941
/**
5042
* Quacks the gui screen to spit out mouse positions independent of the display size
5143
*/
@@ -129,4 +121,25 @@ public static interface WorldClientDuck {
129121
*/
130122
public void clearEntityList();
131123
}
124+
125+
/**
126+
* Quacks the {@link MixinPlayerChunkMap}
127+
*/
128+
public static interface PlayerChunkMapDuck {
129+
/**
130+
* @return The list of players of this chunk map
131+
*/
132+
public List<EntityPlayerMP> getPlayers();
133+
134+
/**
135+
* <p>Forces a tick in the chunk map without being dependent on the world time.
136+
* <p>The chunk map is responsible for sending the necessary chunks to the client.<br>
137+
* However, to properly do that, the chunks have to be sorted first, which happens every few world ticks.
138+
* <p>Under normal circumstances, WorldServer#tick() would update the world time to make this happen,
139+
* but our goal with savestates is to load the chunks without advancing the world time.
140+
* Hence why this method sorts and ticks the chunk map, without waiting for the world time
141+
* @see MixinPlayerChunkMap#forceTick()
142+
*/
143+
public void forceTick();
144+
}
132145
}

src/main/resources/tasmod.mixin.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@
1212
"savestates.AccessorAnvilChunkLoader",
1313
"savestates.AccessorChunkLoader",
1414
"savestates.AccessorEntityLivingBase",
15-
"savestates.AccessorPlayerChunkMap",
15+
"savestates.MixinPlayerChunkMap",
1616
"savestates.MixinChunkProviderServer",
1717
"savestates.MixinNetHandlerPlayServer",
1818
"savestates.MixinScoreboard",
19-
"savestates.MixinWorldServer",
2019

2120
// Events
2221
"events.MixinEntityPlayerMP",

0 commit comments

Comments
 (0)