Phase 9: implement forge-1_7_10 leaf (WS, deltas, chat, link, reconciliation, player tracking)
Ports the full feature set proven by forge-1_12_2 (Phases 1-2-3-7-7b) to MC 1.7.10's older, pre-block-state Forge/FML generation: Forge1710ChunkAdapter reads raw Block+meta via Chunk/ExtendedBlockStorage instead of IBlockState, hooks BlockEvent/ChunkEvent/TickEvent under cpw.mods.fml, and MCMapperMod/LinkCommand/Forge1710ChatBridge adapt to 1.7.10's CommandBase/ChatComponentText/ServerConfigurationManager API shapes. No MultiPlaceEvent hook at this Forge version (falls back to the reconciliation sweep for multi-block placements). Verified against real Forge 10.13.4.1614-1.7.10 via ./gradlew :forge-1_7_10:build.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package com.octoturge.mcmapper.forge1710;
|
||||
|
||||
import com.octoturge.mcmapper.common.ChatBridge;
|
||||
import com.octoturge.mcmapper.common.protocol.WaypointChatFormatter;
|
||||
import com.octoturge.mcmapper.common.protocol.WaypointShare;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class Forge1710ChatBridge implements ChatBridge {
|
||||
private final MinecraftServer server;
|
||||
private final Logger logger;
|
||||
|
||||
public Forge1710ChatBridge(MinecraftServer server, Logger logger) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectWebChatMessage(String displayName, String message) {
|
||||
server.getConfigurationManager().sendChatMsg(new ChatComponentText("[Web] " + displayName + ": " + message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectWaypointShare(WaypointShare waypoint) {
|
||||
// Same rationale as Forge1122ChatBridge: neither JourneyMap's nor Xaero's chat-waypoint
|
||||
// syntax needs a click-event component, so a plain broadcast message is enough.
|
||||
server.getConfigurationManager().sendChatMsg(new ChatComponentText(WaypointChatFormatter.format(waypoint)));
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package com.octoturge.mcmapper.forge1710;
|
||||
|
||||
import com.octoturge.mcmapper.common.ChunkAdapter;
|
||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
import net.minecraft.world.gen.ChunkProviderServer;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 1.7.10's {@link ChunkAdapter} — one Forge/MC generation older than {@code forge-1_12_2}'s (pre
|
||||
* block-state: raw {@code Block} + metadata int, not {@code IBlockState}), and pre-FML-repackage
|
||||
* (event/tick classes live under {@code cpw.mods.fml}, not {@code net.minecraftforge.fml}).
|
||||
* Column top-of-height lookups and section iteration otherwise mirror {@code
|
||||
* Forge1122ChunkAdapter} exactly — see its javadoc for the reconciliation-vs-event framing.
|
||||
*/
|
||||
public class Forge1710ChunkAdapter implements ChunkAdapter {
|
||||
private final WorldServer world;
|
||||
private final String dimensionId;
|
||||
|
||||
public Forge1710ChunkAdapter(WorldServer world) {
|
||||
this.world = world;
|
||||
this.dimensionId = String.valueOf(world.provider.dimensionId);
|
||||
}
|
||||
|
||||
public String getDimensionId() {
|
||||
return dimensionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeltaEvent> readChunk(String dimension, int chunkX, int chunkZ) {
|
||||
Chunk chunk = world.getChunkFromChunkCoords(chunkX, chunkZ);
|
||||
List<DeltaEvent> events = new ArrayList<DeltaEvent>(256);
|
||||
long now = System.currentTimeMillis();
|
||||
for (int lx = 0; lx < 16; lx++) {
|
||||
for (int lz = 0; lz < 16; lz++) {
|
||||
events.add(readColumn(chunk, chunkX * 16 + lx, chunkZ * 16 + lz, lx, lz, now,
|
||||
DeltaEvent.Source.RECONCILIATION));
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private DeltaEvent readColumn(Chunk chunk, int worldX, int worldZ, int localX, int localZ,
|
||||
long now, DeltaEvent.Source source) {
|
||||
int height = chunk.getHeightValue(localX, localZ);
|
||||
int topY = Math.max(0, height - 1);
|
||||
Block block = chunk.getBlock(localX, topY, localZ);
|
||||
int meta = chunk.getBlockMetadata(localX, topY, localZ);
|
||||
int id = Block.getIdFromBlock(block);
|
||||
int blockStateId = ((id & 0xFFF) << 4) | (meta & 0xF);
|
||||
return new DeltaEvent(dimensionId, worldX, topY, worldZ, blockStateId, now, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SectionData> readSections(int chunkX, int chunkZ) {
|
||||
Chunk chunk = world.getChunkFromChunkCoords(chunkX, chunkZ);
|
||||
ExtendedBlockStorage[] storage = chunk.getBlockStorageArray();
|
||||
List<SectionData> sections = new ArrayList<SectionData>();
|
||||
for (int sectionY = 0; sectionY < storage.length; sectionY++) {
|
||||
ExtendedBlockStorage ebs = storage[sectionY];
|
||||
// Same empty-section shortcut as forge-1_12_2 — vanilla leaves storage null for an
|
||||
// all-air section, and ExtendedBlockStorage tracks its own non-air count.
|
||||
if (ebs == null || ebs.isEmpty()) continue;
|
||||
|
||||
char[] blocks = new char[4096];
|
||||
for (int ly = 0; ly < 16; ly++) {
|
||||
for (int lz = 0; lz < 16; lz++) {
|
||||
for (int lx = 0; lx < 16; lx++) {
|
||||
Block block = ebs.getBlockByExtId(lx, ly, lz);
|
||||
int meta = ebs.getExtBlockMetadata(lx, ly, lz);
|
||||
int id = Block.getIdFromBlock(block);
|
||||
int blockStateId = ((id & 0xFFF) << 4) | (meta & 0xF);
|
||||
blocks[(ly * 16 + lz) * 16 + lx] = (char) blockStateId;
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.add(new SectionData(sectionY, blocks));
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEventHooks(DeltaSink sink) {
|
||||
MinecraftForge.EVENT_BUS.register(new EventHooks(sink));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> loadedChunkKeys() {
|
||||
List<Long> keys = new ArrayList<Long>();
|
||||
ChunkProviderServer provider = (ChunkProviderServer) world.getChunkProvider();
|
||||
for (Object obj : provider.loadedChunks) {
|
||||
Chunk chunk = (Chunk) obj;
|
||||
keys.add((((long) chunk.xPosition) << 32) | (chunk.zPosition & 0xFFFFFFFFL));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same "mark dirty, drain once per tick" strategy as {@code Forge1122ChunkAdapter} (see its
|
||||
* javadoc) — {@code BlockEvent.BreakEvent} still fires before the actual removal here. This
|
||||
* era's {@code BlockEvent} carries {@code world}/{@code x}/{@code y}/{@code z} as plain public
|
||||
* fields rather than a {@code World}/{@code BlockPos} getter pair, and there's no
|
||||
* {@code MultiPlaceEvent} hook (multi-block placements like doors/beds are caught by the
|
||||
* periodic reconciliation sweep instead — an acceptable gap given it's already a documented
|
||||
* fallback for anything event hooks miss).
|
||||
*/
|
||||
private class EventHooks {
|
||||
private final DeltaSink sink;
|
||||
private final Set<Long> dirtyColumns = ConcurrentHashMap.newKeySet();
|
||||
|
||||
EventHooks(DeltaSink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockBreak(BlockEvent.BreakEvent event) {
|
||||
markDirty(event.world, event.x, event.z);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockPlace(BlockEvent.PlaceEvent event) {
|
||||
markDirty(event.world, event.x, event.z);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onChunkLoad(ChunkEvent.Load event) {
|
||||
if (event.world != world) return;
|
||||
Chunk chunk = event.getChunk();
|
||||
for (DeltaEvent e : readChunk(dimensionId, chunk.xPosition, chunk.zPosition)) {
|
||||
sink.onDelta(e);
|
||||
}
|
||||
sink.onChunkDirty(chunk.xPosition, chunk.zPosition);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END || dirtyColumns.isEmpty()) return;
|
||||
long now = System.currentTimeMillis();
|
||||
Set<Long> dirtyChunks = new LinkedHashSet<Long>();
|
||||
Iterator<Long> it = dirtyColumns.iterator();
|
||||
while (it.hasNext()) {
|
||||
long key = it.next();
|
||||
it.remove();
|
||||
int wx = (int) (key >> 32);
|
||||
int wz = (int) key;
|
||||
Chunk chunk = world.getChunkFromChunkCoords(wx >> 4, wz >> 4);
|
||||
sink.onDelta(readColumn(chunk, wx, wz, wx & 15, wz & 15, now, DeltaEvent.Source.EVENT));
|
||||
dirtyChunks.add((((long) (wx >> 4)) << 32) | ((wz >> 4) & 0xFFFFFFFFL));
|
||||
}
|
||||
for (long chunkKey : dirtyChunks) {
|
||||
sink.onChunkDirty((int) (chunkKey >> 32), (int) chunkKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void markDirty(World eventWorld, int x, int z) {
|
||||
if (eventWorld != world) return;
|
||||
long key = ((long) x << 32) | (z & 0xFFFFFFFFL);
|
||||
dirtyColumns.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.octoturge.mcmapper.forge1710;
|
||||
|
||||
import com.octoturge.mcmapper.common.BackendConnection;
|
||||
import com.octoturge.mcmapper.common.LinkCodeGenerator;
|
||||
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
|
||||
/** {@code /mcmapper link} — see LinkRequest.java's javadoc and MCMapper-Backend's link.ts. */
|
||||
public class LinkCommand extends CommandBase {
|
||||
private final BackendConnection connection;
|
||||
|
||||
public LinkCommand(BackendConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "mcmapper";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/mcmapper link";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0; // any player may link their own account
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0 || !"link".equals(args[0])) {
|
||||
sender.addChatMessage(new ChatComponentText("Usage: /mcmapper link"));
|
||||
return;
|
||||
}
|
||||
if (!(sender instanceof EntityPlayerMP)) {
|
||||
sender.addChatMessage(new ChatComponentText("Only players can link an account."));
|
||||
return;
|
||||
}
|
||||
|
||||
EntityPlayerMP player = (EntityPlayerMP) sender;
|
||||
String code = LinkCodeGenerator.generate();
|
||||
LinkRequest.AuthMode authMode = MinecraftServer.getServer().isServerInOnlineMode()
|
||||
? LinkRequest.AuthMode.ONLINE : LinkRequest.AuthMode.OFFLINE;
|
||||
connection.sendLinkRequest(new LinkRequest(player.getUniqueID().toString(),
|
||||
player.getGameProfile().getName(), code, authMode));
|
||||
player.addChatMessage(new ChatComponentText(
|
||||
"Your MCMapper link code: " + code + " — enter it on the map site within 10 minutes."));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,216 @@
|
||||
package com.octoturge.mcmapper.forge1710;
|
||||
|
||||
import com.octoturge.mcmapper.common.ChunkAdapter;
|
||||
import com.octoturge.mcmapper.common.DefaultBackendConnection;
|
||||
import com.octoturge.mcmapper.common.ReconciliationScheduler;
|
||||
import com.octoturge.mcmapper.common.config.MapperConfig;
|
||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
|
||||
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLServerStartingEvent;
|
||||
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import net.minecraftforge.event.ServerChatEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Stub for the 1.7.10 leaf (Phase 9 — higher priority than 26.1.2, ships after 1.12.2 proves
|
||||
* the architecture). Not wired to the Forge {@code @Mod} annotation yet since this leaf's
|
||||
* ForgeGradle 2.1 toolchain isn't active in the root build — see build.gradle.
|
||||
* Entry point for the 1.7.10 leaf (Phase 9) — one Forge/MC generation older than {@code
|
||||
* forge-1_12_2} (raw block id + metadata, {@code cpw.mods.fml} packages, no {@code IBlockState}).
|
||||
* Implements the same feature set the 1.12.2 leaf proved (Phases 1-2-3-7-7b: WS connect, column +
|
||||
* section backfill/flush, chat bridge, {@code /mcmapper link}, reconciliation sweep, throttled
|
||||
* player positions) against the {@code common} interfaces — see {@link MCMapperMod}'s 1.12.2
|
||||
* counterpart for the shared design rationale, and {@link Forge1710ChunkAdapter} for what
|
||||
* actually differs API-wise.
|
||||
*/
|
||||
@Mod(modid = MCMapperMod.MOD_ID, name = "MCMapper", version = MCMapperMod.VERSION)
|
||||
public class MCMapperMod {
|
||||
public static final String MOD_ID = "mcmapper";
|
||||
public static final String VERSION = "0.1.0-SNAPSHOT";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(MOD_ID);
|
||||
|
||||
private Configuration forgeConfig;
|
||||
private final MapperConfig config = new MapperConfig();
|
||||
private DefaultBackendConnection connection;
|
||||
private Forge1710ChunkAdapter adapter;
|
||||
private final List<DeltaEvent> pendingDeltas = Collections.synchronizedList(new ArrayList<DeltaEvent>());
|
||||
private final Set<Long> pendingSectionChunks = Collections.synchronizedSet(new LinkedHashSet<Long>());
|
||||
private final ReconciliationScheduler reconciliationScheduler = new ReconciliationScheduler();
|
||||
private MinecraftServer mcServer;
|
||||
private int ticksSinceFlush = 0;
|
||||
private int ticksSinceReconciliation = 0;
|
||||
private int ticksSincePlayerPositions = 0;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
forgeConfig = new Configuration(event.getSuggestedConfigurationFile());
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
forgeConfig.load();
|
||||
config.backendUrl = forgeConfig.getString("backendUrl", "network", config.backendUrl,
|
||||
"WS URL of the MCMapper backend api service");
|
||||
config.serverToken = forgeConfig.getString("serverToken", "network", config.serverToken,
|
||||
"Per-server token issued when registering with the backend (see MCMapper-Backend's `bun run seed`)");
|
||||
config.deltaFlushIntervalTicks = forgeConfig.getInt("deltaFlushIntervalTicks", "network",
|
||||
config.deltaFlushIntervalTicks, 1, 20 * 60,
|
||||
"How often (in ticks) to batch and flush block-change deltas to the backend");
|
||||
config.reconciliationIntervalTicks = forgeConfig.getInt("reconciliationIntervalTicks", "network",
|
||||
config.reconciliationIntervalTicks, 20, 20 * 60 * 60,
|
||||
"How often (in ticks) to run a reconciliation sweep, re-reading and resending a " +
|
||||
"rotating slice of loaded chunks to catch changes event hooks miss " +
|
||||
"(world-gen, other mods, /fill, etc.)");
|
||||
config.reconciliationChunksPerSweep = forgeConfig.getInt("reconciliationChunksPerSweep", "network",
|
||||
config.reconciliationChunksPerSweep, 1, 5000,
|
||||
"Max chunks to re-read and resend per reconciliation sweep");
|
||||
config.playerTrackingEnabled = forgeConfig.getBoolean("playerTrackingEnabled", "network",
|
||||
config.playerTrackingEnabled,
|
||||
"Whether to send throttled online-player positions to the backend at all. The " +
|
||||
"backend also has its own per-server admin toggle deciding whether it " +
|
||||
"relays this on to web viewers — this setting only controls the mod side.");
|
||||
config.playerPositionIntervalTicks = forgeConfig.getInt("playerPositionIntervalTicks", "network",
|
||||
config.playerPositionIntervalTicks, 5, 20 * 60,
|
||||
"How often (in ticks) to send the online-player roster to the backend, when playerTrackingEnabled");
|
||||
if (forgeConfig.hasChanged()) forgeConfig.save();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void serverStarting(FMLServerStartingEvent event) {
|
||||
if (config.serverToken == null || config.serverToken.isEmpty()) {
|
||||
LOGGER.warn("MCMapper serverToken is not configured (see config/mcmapper.cfg) — not connecting to backend");
|
||||
return;
|
||||
}
|
||||
|
||||
mcServer = event.getServer();
|
||||
connection = new DefaultBackendConnection(LOGGER::info, LOGGER::warn);
|
||||
connection.connect(config.backendUrl, config.serverToken);
|
||||
LOGGER.info("MCMapper (1.7.10 leaf) connecting to " + config.backendUrl);
|
||||
|
||||
WorldServer overworld = DimensionManager.getWorld(0);
|
||||
adapter = new Forge1710ChunkAdapter(overworld);
|
||||
adapter.registerEventHooks(new ChunkAdapter.DeltaSink() {
|
||||
@Override
|
||||
public void onDelta(DeltaEvent delta) {
|
||||
pendingDeltas.add(delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkDirty(int chunkX, int chunkZ) {
|
||||
pendingSectionChunks.add((((long) chunkX) << 32) | (chunkZ & 0xFFFFFFFFL));
|
||||
}
|
||||
});
|
||||
|
||||
Forge1710ChatBridge chatBridge = new Forge1710ChatBridge(event.getServer(), LOGGER);
|
||||
connection.setChatListener(chatBridge::injectWebChatMessage);
|
||||
connection.setWaypointShareListener(chatBridge::injectWaypointShare);
|
||||
event.registerServerCommand(new LinkCommand(connection));
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerChat(ServerChatEvent event) {
|
||||
EntityPlayerMP player = (EntityPlayerMP) event.player;
|
||||
connection.sendChatMessage(player.getUniqueID().toString(), player.getGameProfile().getName(), event.message);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
|
||||
if (++ticksSinceFlush >= config.deltaFlushIntervalTicks) {
|
||||
ticksSinceFlush = 0;
|
||||
flush();
|
||||
}
|
||||
|
||||
if (++ticksSinceReconciliation >= config.reconciliationIntervalTicks) {
|
||||
ticksSinceReconciliation = 0;
|
||||
reconcile();
|
||||
}
|
||||
|
||||
if (config.playerTrackingEnabled && ++ticksSincePlayerPositions >= config.playerPositionIntervalTicks) {
|
||||
ticksSincePlayerPositions = 0;
|
||||
sendPlayerPositions();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPlayerPositions() {
|
||||
List<PlayerPosition> players = new ArrayList<PlayerPosition>();
|
||||
for (Object obj : mcServer.getConfigurationManager().playerEntityList) {
|
||||
EntityPlayerMP player = (EntityPlayerMP) obj;
|
||||
if (player.dimension != 0) continue;
|
||||
players.add(new PlayerPosition(player.getUniqueID().toString(), player.getGameProfile().getName(),
|
||||
MathHelper.floor_double(player.posX), MathHelper.floor_double(player.posY),
|
||||
MathHelper.floor_double(player.posZ)));
|
||||
}
|
||||
connection.sendPlayerPositions(0, players);
|
||||
}
|
||||
|
||||
private void reconcile() {
|
||||
List<Long> chunkKeys = reconciliationScheduler.next(adapter.loadedChunkKeys(),
|
||||
config.reconciliationChunksPerSweep);
|
||||
if (chunkKeys.isEmpty()) return;
|
||||
|
||||
String dimensionId = adapter.getDimensionId();
|
||||
for (long key : chunkKeys) {
|
||||
int chunkX = (int) (key >> 32);
|
||||
int chunkZ = (int) key;
|
||||
List<DeltaEvent> columns = adapter.readChunk(dimensionId, chunkX, chunkZ);
|
||||
if (!columns.isEmpty()) connection.sendDeltas(columns);
|
||||
List<SectionData> sections = adapter.readSections(chunkX, chunkZ);
|
||||
if (!sections.isEmpty()) connection.sendSections(dimensionId, chunkX, chunkZ, sections);
|
||||
}
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
List<DeltaEvent> batch;
|
||||
synchronized (pendingDeltas) {
|
||||
if (pendingDeltas.isEmpty()) {
|
||||
batch = null;
|
||||
} else {
|
||||
batch = new ArrayList<DeltaEvent>(pendingDeltas);
|
||||
pendingDeltas.clear();
|
||||
}
|
||||
}
|
||||
if (batch != null) connection.sendDeltas(batch);
|
||||
|
||||
List<Long> dirtyChunks;
|
||||
synchronized (pendingSectionChunks) {
|
||||
if (pendingSectionChunks.isEmpty()) return;
|
||||
dirtyChunks = new ArrayList<Long>(pendingSectionChunks);
|
||||
pendingSectionChunks.clear();
|
||||
}
|
||||
String dimensionId = adapter.getDimensionId();
|
||||
for (long key : dirtyChunks) {
|
||||
int chunkX = (int) (key >> 32);
|
||||
int chunkZ = (int) key;
|
||||
List<SectionData> sections = adapter.readSections(chunkX, chunkZ);
|
||||
if (!sections.isEmpty()) {
|
||||
connection.sendSections(dimensionId, chunkX, chunkZ, sections);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void serverStopping(FMLServerStoppingEvent event) {
|
||||
if (connection != null) connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user