Phase 2: forge-1_12_2 full chunk section backfill/flush
Extends ChunkAdapter with readSections() (reads non-empty 16x16x16 sections via the chunk's ExtendedBlockStorage array, skipping fully-air ones for free) and DeltaSink with onChunkDirty(), additive to Phase 1's column-based readChunk()/onDelta — 2D column tracking is unchanged. BackendConnection grows sendSections(), base64-encoding each section's 4096-entry char[] (char, not short, since short would overflow for any blockId >= 2048 — see SectionData's javadoc) into the wire protocol's new "sections" message, mirrored in MCMapper-Backend's matching commit. MCMapperMod now tracks dirty chunks (not just dirty columns) and, on the same flush tick as column deltas, re-reads and resends full section data for any chunk touched since the last flush — same "current state, not a diff" approach as columns, at chunk instead of column granularity. Verified against a live MCMapper-Backend instance: a known half-solid section sent through this exact code path round-trips to a mesh with exactly 24 vertices / 36 indices at the backend's mesh-serving endpoint, matching the worker's greedy-mesher unit tests for a uniform section.
This commit is contained in:
@@ -2,6 +2,7 @@ package com.octoturge.mcmapper.common;
|
|||||||
|
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -10,14 +11,16 @@ import java.util.List;
|
|||||||
* modules (pure Java, no Minecraft API usage) — a docker-network hostname, LAN IP, or public
|
* modules (pure Java, no Minecraft API usage) — a docker-network hostname, LAN IP, or public
|
||||||
* domain in {@code MapperConfig#backendUrl} all work identically.
|
* domain in {@code MapperConfig#backendUrl} all work identically.
|
||||||
*
|
*
|
||||||
* Left as an interface with a no-op stub for Phase 0 scaffolding; the real WS client
|
* Real implementation ({@link DefaultBackendConnection}) landed in Phase 1 (column deltas) and
|
||||||
* (handshake, reconnect/backoff, batching) lands in Phase 1.
|
* grew {@link #sendSections} in Phase 2 (full-voxel 3D mesh backfill).
|
||||||
*/
|
*/
|
||||||
public interface BackendConnection {
|
public interface BackendConnection {
|
||||||
void connect(String url, String serverToken);
|
void connect(String url, String serverToken);
|
||||||
|
|
||||||
void sendDeltas(List<DeltaEvent> deltas);
|
void sendDeltas(List<DeltaEvent> deltas);
|
||||||
|
|
||||||
|
void sendSections(String dimension, int chunkX, int chunkZ, List<SectionData> sections);
|
||||||
|
|
||||||
void sendLinkRequest(LinkRequest request);
|
void sendLinkRequest(LinkRequest request);
|
||||||
|
|
||||||
void disconnect();
|
void disconnect();
|
||||||
@@ -31,6 +34,10 @@ public interface BackendConnection {
|
|||||||
public void sendDeltas(List<DeltaEvent> deltas) {
|
public void sendDeltas(List<DeltaEvent> deltas) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendSections(String dimension, int chunkX, int chunkZ, List<SectionData> sections) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendLinkRequest(LinkRequest request) {
|
public void sendLinkRequest(LinkRequest request) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.octoturge.mcmapper.common;
|
package com.octoturge.mcmapper.common;
|
||||||
|
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -8,16 +9,30 @@ import java.util.List;
|
|||||||
* The seam between a specific Minecraft/Forge API generation and the shared delta-capture and
|
* The seam between a specific Minecraft/Forge API generation and the shared delta-capture and
|
||||||
* networking logic. Each leaf module (forge-1_7_10, forge-1_12_2, neoforge-26_1) provides one
|
* networking logic. Each leaf module (forge-1_7_10, forge-1_12_2, neoforge-26_1) provides one
|
||||||
* implementation, adapting its own era's block-id/block-state representation into the
|
* implementation, adapting its own era's block-id/block-state representation into the
|
||||||
* {@code blockStateId} carried by {@link DeltaEvent}.
|
* {@code blockStateId} carried by {@link DeltaEvent} and {@link SectionData}.
|
||||||
*/
|
*/
|
||||||
public interface ChunkAdapter {
|
public interface ChunkAdapter {
|
||||||
/** Bulk-read a chunk's current state for initial sync / reconciliation, as delta events. */
|
/** Bulk-read a chunk's current column/heightmap state for initial sync, as delta events. */
|
||||||
List<DeltaEvent> readChunk(String dimension, int chunkX, int chunkZ);
|
List<DeltaEvent> readChunk(String dimension, int chunkX, int chunkZ);
|
||||||
|
|
||||||
|
/** Bulk-read a chunk's non-empty 16x16x16 sections (Phase 2, 3D meshing backfill). */
|
||||||
|
List<SectionData> readSections(int chunkX, int chunkZ);
|
||||||
|
|
||||||
/** Register the loader-specific hooks (block place/break, chunk load/unload) that feed the dirty buffer. */
|
/** Register the loader-specific hooks (block place/break, chunk load/unload) that feed the dirty buffer. */
|
||||||
void registerEventHooks(DeltaSink sink);
|
void registerEventHooks(DeltaSink sink);
|
||||||
|
|
||||||
interface DeltaSink {
|
interface DeltaSink {
|
||||||
void onDelta(DeltaEvent event);
|
void onDelta(DeltaEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chunk had a block change (or was freshly loaded) — its full section data should be
|
||||||
|
* re-read and resent. Separate from {@link #onDelta} because section reads are far more
|
||||||
|
* expensive than a single column read; the caller decides how/when to batch these
|
||||||
|
* (typically once per flush, deduped per chunk, same as column deltas).
|
||||||
|
* Default no-op so existing single-method {@code DeltaSink} lambdas (which only
|
||||||
|
* implement {@code onDelta}) keep compiling.
|
||||||
|
*/
|
||||||
|
default void onChunkDirty(int chunkX, int chunkZ) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package com.octoturge.mcmapper.common;
|
|||||||
import com.octoturge.mcmapper.common.json.MiniJson;
|
import com.octoturge.mcmapper.common.json.MiniJson;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
import com.octoturge.mcmapper.common.protocol.LinkRequest;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
import com.octoturge.mcmapper.common.ws.SimpleWebSocketClient;
|
import com.octoturge.mcmapper.common.ws.SimpleWebSocketClient;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -24,6 +26,7 @@ import java.util.function.Consumer;
|
|||||||
* {"type":"hello_ack","ok":false,"error":"..."}
|
* {"type":"hello_ack","ok":false,"error":"..."}
|
||||||
*
|
*
|
||||||
* mod -> api {"type":"columns","dimension":0,"columns":[{"x":..,"z":..,"height":..,"blockId":..,"blockMeta":..}]}
|
* mod -> api {"type":"columns","dimension":0,"columns":[{"x":..,"z":..,"height":..,"blockId":..,"blockMeta":..}]}
|
||||||
|
* mod -> api {"type":"sections","dimension":0,"chunkX":..,"chunkZ":..,"sections":[{"sectionY":..,"blocks":"<base64>"}]}
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* A "columns" message doubles as both initial backfill (one message per loaded chunk) and live
|
* A "columns" message doubles as both initial backfill (one message per loaded chunk) and live
|
||||||
@@ -33,6 +36,11 @@ import java.util.function.Consumer;
|
|||||||
* (1.7.10/1.12.2) always populate it with a stringified vanilla dimension id (e.g. {@code "0"}),
|
* (1.7.10/1.12.2) always populate it with a stringified vanilla dimension id (e.g. {@code "0"}),
|
||||||
* which is what lets this class safely {@code Integer.parseInt} it for the wire message.
|
* which is what lets this class safely {@code Integer.parseInt} it for the wire message.
|
||||||
*
|
*
|
||||||
|
* "sections" is the Phase 2 addition for full-voxel 3D mesh backfill, additive to "columns" —
|
||||||
|
* see {@link SectionData}'s javadoc for the block encoding and {@code ChunkAdapter.DeltaSink
|
||||||
|
* #onChunkDirty} for when it's sent (chunk load, and per-flush for chunks touched since the
|
||||||
|
* last flush).
|
||||||
|
*
|
||||||
* No offline queue: deltas sent while disconnected are dropped rather than buffered — the
|
* No offline queue: deltas sent while disconnected are dropped rather than buffered — the
|
||||||
* periodic reconciliation sweep (not yet built, see plan's Phase 7) is what's meant to catch
|
* periodic reconciliation sweep (not yet built, see plan's Phase 7) is what's meant to catch
|
||||||
* whatever a disconnect window missed, so buffering here would be solving the same problem twice.
|
* whatever a disconnect window missed, so buffering here would be solving the same problem twice.
|
||||||
@@ -174,6 +182,36 @@ public class DefaultBackendConnection implements BackendConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendSections(String dimension, int chunkX, int chunkZ, List<SectionData> sections) {
|
||||||
|
if (sections.isEmpty() || !serverReady) return;
|
||||||
|
|
||||||
|
List<Object> sectionList = new ArrayList<>();
|
||||||
|
for (SectionData s : sections) {
|
||||||
|
Map<String, Object> sectionObj = new LinkedHashMap<>();
|
||||||
|
sectionObj.put("sectionY", (double) s.sectionY);
|
||||||
|
sectionObj.put("blocks", encodeBlocksBase64(s.blocks));
|
||||||
|
sectionList.add(sectionObj);
|
||||||
|
}
|
||||||
|
Map<String, Object> msg = new LinkedHashMap<>();
|
||||||
|
msg.put("type", "sections");
|
||||||
|
msg.put("dimension", (double) Integer.parseInt(dimension));
|
||||||
|
msg.put("chunkX", (double) chunkX);
|
||||||
|
msg.put("chunkZ", (double) chunkZ);
|
||||||
|
msg.put("sections", sectionList);
|
||||||
|
sendRaw("sections", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeBlocksBase64(char[] blocks) {
|
||||||
|
byte[] bytes = new byte[blocks.length * 2];
|
||||||
|
for (int i = 0; i < blocks.length; i++) {
|
||||||
|
char v = blocks[i];
|
||||||
|
bytes[i * 2] = (byte) (v & 0xFF);
|
||||||
|
bytes[i * 2 + 1] = (byte) ((v >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
return Base64.getEncoder().encodeToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendLinkRequest(LinkRequest request) {
|
public void sendLinkRequest(LinkRequest request) {
|
||||||
// The `/mcmapper link` flow (Phase 3) isn't wired up yet — nothing calls this in Phase 1.
|
// The `/mcmapper link` flow (Phase 3) isn't wired up yet — nothing calls this in Phase 1.
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.octoturge.mcmapper.common.protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-voxel data for one 16x16x16 chunk section (Phase 2, 3D meshing) — additive to
|
||||||
|
* {@link DeltaEvent}'s column/heightmap data, not a replacement (see that type's javadoc).
|
||||||
|
*
|
||||||
|
* {@code blocks} holds 4096 entries indexed by {@code (ly*16 + lz)*16 + lx}, one per position
|
||||||
|
* in the section, each the same {@code (blockId << 4) | meta} encoding as {@link
|
||||||
|
* DeltaEvent#blockStateId}. It's a {@code char[]} (not {@code short[]}) specifically because
|
||||||
|
* Java's {@code char} is the JVM's only unsigned 16-bit integer type — a {@code short} would
|
||||||
|
* overflow into negative values for any blockId >= 2048 (id 2048 << 4 already exceeds
|
||||||
|
* {@code Short.MAX_VALUE}).
|
||||||
|
*/
|
||||||
|
public class SectionData {
|
||||||
|
public final int sectionY;
|
||||||
|
public final char[] blocks;
|
||||||
|
|
||||||
|
public SectionData(int sectionY, char[] blocks) {
|
||||||
|
if (blocks.length != 4096) {
|
||||||
|
throw new IllegalArgumentException("blocks must have exactly 4096 entries, got " + blocks.length);
|
||||||
|
}
|
||||||
|
this.sectionY = sectionY;
|
||||||
|
this.blocks = blocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -2,12 +2,14 @@ package com.octoturge.mcmapper.forge1122;
|
|||||||
|
|
||||||
import com.octoturge.mcmapper.common.ChunkAdapter;
|
import com.octoturge.mcmapper.common.ChunkAdapter;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.state.IBlockState;
|
import net.minecraft.block.state.IBlockState;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.WorldServer;
|
import net.minecraft.world.WorldServer;
|
||||||
import net.minecraft.world.chunk.Chunk;
|
import net.minecraft.world.chunk.Chunk;
|
||||||
|
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
import net.minecraftforge.common.util.BlockSnapshot;
|
import net.minecraftforge.common.util.BlockSnapshot;
|
||||||
import net.minecraftforge.event.world.BlockEvent;
|
import net.minecraftforge.event.world.BlockEvent;
|
||||||
@@ -17,6 +19,7 @@ import net.minecraftforge.fml.common.gameevent.TickEvent;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -35,6 +38,10 @@ public class Forge1122ChunkAdapter implements ChunkAdapter {
|
|||||||
this.dimensionId = String.valueOf(world.provider.getDimension());
|
this.dimensionId = String.valueOf(world.provider.getDimension());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDimensionId() {
|
||||||
|
return dimensionId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DeltaEvent> readChunk(String dimension, int chunkX, int chunkZ) {
|
public List<DeltaEvent> readChunk(String dimension, int chunkX, int chunkZ) {
|
||||||
Chunk chunk = world.getChunk(chunkX, chunkZ);
|
Chunk chunk = world.getChunk(chunkX, chunkZ);
|
||||||
@@ -60,6 +67,35 @@ public class Forge1122ChunkAdapter implements ChunkAdapter {
|
|||||||
return new DeltaEvent(dimensionId, worldX, topY, worldZ, blockStateId, now, source);
|
return new DeltaEvent(dimensionId, worldX, topY, worldZ, blockStateId, now, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SectionData> readSections(int chunkX, int chunkZ) {
|
||||||
|
Chunk chunk = world.getChunk(chunkX, chunkZ);
|
||||||
|
ExtendedBlockStorage[] storage = chunk.getBlockStorageArray();
|
||||||
|
List<SectionData> sections = new ArrayList<>();
|
||||||
|
for (int sectionY = 0; sectionY < storage.length; sectionY++) {
|
||||||
|
ExtendedBlockStorage ebs = storage[sectionY];
|
||||||
|
// Vanilla leaves a section's storage null when it's entirely air, and
|
||||||
|
// ExtendedBlockStorage tracks its own non-air block count — both let us skip empty
|
||||||
|
// sections without a 4096-position scan.
|
||||||
|
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++) {
|
||||||
|
IBlockState state = ebs.get(lx, ly, lz);
|
||||||
|
int id = Block.getIdFromBlock(state.getBlock());
|
||||||
|
int meta = state.getBlock().getMetaFromState(state);
|
||||||
|
int blockStateId = ((id & 0xFFF) << 4) | (meta & 0xF);
|
||||||
|
blocks[(ly * 16 + lz) * 16 + lx] = (char) blockStateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sections.add(new SectionData(sectionY, blocks));
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerEventHooks(DeltaSink sink) {
|
public void registerEventHooks(DeltaSink sink) {
|
||||||
MinecraftForge.EVENT_BUS.register(new EventHooks(sink));
|
MinecraftForge.EVENT_BUS.register(new EventHooks(sink));
|
||||||
@@ -106,12 +142,14 @@ public class Forge1122ChunkAdapter implements ChunkAdapter {
|
|||||||
for (DeltaEvent e : readChunk(dimensionId, chunk.x, chunk.z)) {
|
for (DeltaEvent e : readChunk(dimensionId, chunk.x, chunk.z)) {
|
||||||
sink.onDelta(e);
|
sink.onDelta(e);
|
||||||
}
|
}
|
||||||
|
sink.onChunkDirty(chunk.x, chunk.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void onServerTick(TickEvent.ServerTickEvent event) {
|
public void onServerTick(TickEvent.ServerTickEvent event) {
|
||||||
if (event.phase != TickEvent.Phase.END || dirtyColumns.isEmpty()) return;
|
if (event.phase != TickEvent.Phase.END || dirtyColumns.isEmpty()) return;
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
Set<Long> dirtyChunks = new LinkedHashSet<>();
|
||||||
Iterator<Long> it = dirtyColumns.iterator();
|
Iterator<Long> it = dirtyColumns.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
long key = it.next();
|
long key = it.next();
|
||||||
@@ -120,6 +158,10 @@ public class Forge1122ChunkAdapter implements ChunkAdapter {
|
|||||||
int wz = (int) key;
|
int wz = (int) key;
|
||||||
Chunk chunk = world.getChunk(wx >> 4, wz >> 4);
|
Chunk chunk = world.getChunk(wx >> 4, wz >> 4);
|
||||||
sink.onDelta(readColumn(chunk, wx, wz, wx & 15, wz & 15, now, DeltaEvent.Source.EVENT));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.octoturge.mcmapper.forge1122;
|
package com.octoturge.mcmapper.forge1122;
|
||||||
|
|
||||||
|
import com.octoturge.mcmapper.common.ChunkAdapter;
|
||||||
import com.octoturge.mcmapper.common.DefaultBackendConnection;
|
import com.octoturge.mcmapper.common.DefaultBackendConnection;
|
||||||
import com.octoturge.mcmapper.common.config.MapperConfig;
|
import com.octoturge.mcmapper.common.config.MapperConfig;
|
||||||
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
|
||||||
|
import com.octoturge.mcmapper.common.protocol.SectionData;
|
||||||
import net.minecraft.world.WorldServer;
|
import net.minecraft.world.WorldServer;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
import net.minecraftforge.common.config.Configuration;
|
import net.minecraftforge.common.config.Configuration;
|
||||||
@@ -17,13 +19,16 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point for the 1.12.2 leaf — the primary/first-implemented target (Enigmatica 2).
|
* Entry point for the 1.12.2 leaf — the primary/first-implemented target (Enigmatica 2).
|
||||||
* Phase 1: connects to the backend over WS, backfills the overworld's loaded chunks, and
|
* Phase 1: connects to the backend over WS, backfills the overworld's loaded chunks, and
|
||||||
* flushes event-driven column deltas on a timer (no periodic reconciliation sweep yet — see
|
* flushes event-driven column deltas on a timer (no periodic reconciliation sweep yet — see
|
||||||
* plan's Phase 7). Chat bridge and `/mcmapper link` land in Phase 3.
|
* plan's Phase 7). Phase 2 adds full-section backfill/flush for 3D mesh rendering. Chat bridge
|
||||||
|
* and `/mcmapper link` land in Phase 3.
|
||||||
*/
|
*/
|
||||||
@Mod(modid = MCMapperMod.MOD_ID, name = "MCMapper", version = MCMapperMod.VERSION)
|
@Mod(modid = MCMapperMod.MOD_ID, name = "MCMapper", version = MCMapperMod.VERSION)
|
||||||
public class MCMapperMod {
|
public class MCMapperMod {
|
||||||
@@ -35,7 +40,9 @@ public class MCMapperMod {
|
|||||||
private Configuration forgeConfig;
|
private Configuration forgeConfig;
|
||||||
private final MapperConfig config = new MapperConfig();
|
private final MapperConfig config = new MapperConfig();
|
||||||
private DefaultBackendConnection connection;
|
private DefaultBackendConnection connection;
|
||||||
|
private Forge1122ChunkAdapter adapter;
|
||||||
private final List<DeltaEvent> pendingDeltas = Collections.synchronizedList(new ArrayList<>());
|
private final List<DeltaEvent> pendingDeltas = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
private final Set<Long> pendingSectionChunks = Collections.synchronizedSet(new LinkedHashSet<>());
|
||||||
private int ticksSinceFlush = 0;
|
private int ticksSinceFlush = 0;
|
||||||
|
|
||||||
@Mod.EventHandler
|
@Mod.EventHandler
|
||||||
@@ -68,8 +75,18 @@ public class MCMapperMod {
|
|||||||
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
|
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
|
||||||
|
|
||||||
WorldServer overworld = event.getServer().getWorld(0);
|
WorldServer overworld = event.getServer().getWorld(0);
|
||||||
Forge1122ChunkAdapter adapter = new Forge1122ChunkAdapter(overworld);
|
adapter = new Forge1122ChunkAdapter(overworld);
|
||||||
adapter.registerEventHooks(pendingDeltas::add);
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
MinecraftForge.EVENT_BUS.register(this);
|
MinecraftForge.EVENT_BUS.register(this);
|
||||||
}
|
}
|
||||||
@@ -85,11 +102,30 @@ public class MCMapperMod {
|
|||||||
private void flush() {
|
private void flush() {
|
||||||
List<DeltaEvent> batch;
|
List<DeltaEvent> batch;
|
||||||
synchronized (pendingDeltas) {
|
synchronized (pendingDeltas) {
|
||||||
if (pendingDeltas.isEmpty()) return;
|
if (pendingDeltas.isEmpty()) {
|
||||||
batch = new ArrayList<>(pendingDeltas);
|
batch = null;
|
||||||
pendingDeltas.clear();
|
} else {
|
||||||
|
batch = new ArrayList<>(pendingDeltas);
|
||||||
|
pendingDeltas.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (batch != null) connection.sendDeltas(batch);
|
||||||
|
|
||||||
|
List<Long> dirtyChunks;
|
||||||
|
synchronized (pendingSectionChunks) {
|
||||||
|
if (pendingSectionChunks.isEmpty()) return;
|
||||||
|
dirtyChunks = new ArrayList<>(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connection.sendDeltas(batch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mod.EventHandler
|
@Mod.EventHandler
|
||||||
|
|||||||
Reference in New Issue
Block a user