Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/controllers/SyncController.java
T

484 lines
22 KiB
Java
Raw Normal View History

2025-01-29 18:25:34 -05:00
package kamkeel.npcs.controllers;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
2025-10-19 22:06:11 -04:00
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
2026-03-31 22:55:42 +02:00
import kamkeel.npcdbc.network.DBCPacketHandler;
import kamkeel.npcdbc.network.packets.get.DBCInfoSyncPacket;
import kamkeel.npcs.addon.DBCAddon;
2026-03-31 21:44:23 +02:00
import kamkeel.npcs.controllers.sync.SyncHandler;
import kamkeel.npcs.controllers.sync.SyncRegistry;
import kamkeel.npcs.controllers.sync.handlers.*;
2025-10-19 22:06:11 -04:00
import kamkeel.npcs.network.LargeAbstractPacket;
2025-01-29 18:25:34 -05:00
import kamkeel.npcs.network.PacketHandler;
2025-02-20 21:40:39 -05:00
import kamkeel.npcs.network.enums.EnumSyncAction;
2026-03-31 19:59:53 +02:00
import kamkeel.npcs.network.enums.SyncType;
2025-02-18 22:22:40 -05:00
import kamkeel.npcs.network.packets.data.LoginPacket;
2026-03-11 21:42:27 -04:00
import kamkeel.npcs.network.packets.data.ProfileSharedQuestPacket;
import kamkeel.npcs.network.packets.data.large.SyncPacket;
2025-06-24 05:41:34 -04:00
import kamkeel.npcs.network.packets.request.party.PartyInfoPacket;
2025-10-19 22:06:11 -04:00
import kamkeel.npcs.util.ByteBufUtils;
2025-01-29 18:25:34 -05:00
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
2025-10-19 22:06:11 -04:00
import net.minecraft.server.MinecraftServer;
2026-03-31 22:55:42 +02:00
import noppes.npcs.LogWriter;
2025-01-29 18:25:34 -05:00
import noppes.npcs.client.ClientCacheHandler;
2025-07-23 14:22:52 -04:00
import noppes.npcs.controllers.data.PlayerData;
2025-01-29 18:25:34 -05:00
2025-10-19 22:06:11 -04:00
import java.io.IOException;
2025-01-29 18:25:34 -05:00
import java.util.HashMap;
2026-01-30 05:01:54 -05:00
import java.util.LinkedHashMap;
2025-10-19 22:06:11 -04:00
import java.util.Map;
2026-03-31 19:59:53 +02:00
import java.util.Set;
2025-10-19 22:06:11 -04:00
import java.util.UUID;
2025-02-10 21:41:54 -05:00
import java.util.concurrent.ConcurrentHashMap;
2025-10-19 22:06:11 -04:00
import java.util.function.Supplier;
2025-01-29 18:25:34 -05:00
2026-03-31 21:44:23 +02:00
/**
* Central sync backend for CNPC+.
*
* <p>This controller manages two distinct sync lanes:</p>
* <ul>
* <li><b>Global cached registry families</b> — registered via {@link SyncRegistry}
* with concrete {@link SyncHandler} implementations. These families use
* cached serialized payloads, revision-based delta sync on reconnect,
* and chunked delivery. All family-specific RELOAD/UPDATE/REMOVE logic
* lives in the handler implementations, not here.</li>
* <li><b>Direct per-player sync helpers</b> — for transient per-player state
* like effects, ability hotbars, and cooldowns. These bypass the cached
* registry system and are sent as direct packets. Owned by
* {@link PlayerEffectSyncHelper} and {@link PlayerAbilitySyncHelper}.</li>
* </ul>
*
* <p>This class retains ownership of:</p>
* <ul>
* <li>Cache entry storage and lazy rebuild ({@link SyncCacheEntry})</li>
* <li>Cached payload byte[] + precomputed chunks ({@link CachedSyncPayload})</li>
* <li>Per-player revision tracking ({@link PlayerSyncState})</li>
* <li>Login handshake and post-login packet sequencing</li>
* <li>Generic sync dispatch helpers (syncAll, syncUpdate, syncRemove)</li>
* </ul>
*/
2025-01-30 09:26:04 -05:00
public class SyncController {
2025-01-29 18:25:34 -05:00
2026-03-31 22:55:42 +02:00
/** Cached serialized reload payloads and revision state for each global sync family. */
2026-03-31 19:59:53 +02:00
private static final Map<SyncType, SyncCacheEntry> cacheEntries = new LinkedHashMap<>();
2025-11-05 17:23:21 -05:00
2026-03-31 22:55:42 +02:00
/** Last known synced revision map for each connected player. */
private static final ConcurrentHashMap<UUID, PlayerSyncState> playerSyncState = new ConcurrentHashMap<>();
2025-11-05 17:23:21 -05:00
2026-03-31 22:55:42 +02:00
/** Stable identity token for the current dedicated-server process, used by the revision handshake. */
private static final String SERVER_IDENTITY_KEY = UUID.randomUUID().toString();
2025-10-19 22:06:11 -04:00
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
// Lifecycle — handler registration and cache initialization
// ═══════════════════════════════════════════════════════════════════════
2025-10-19 22:06:11 -04:00
2026-03-31 21:44:23 +02:00
/**
* Registers all core CNPC+ sync handlers in deterministic order.
* Called on server start. The registration order determines login
* sync ordering via {@link SyncRegistry#getLoginTypes()}.
*
* <p>Handler registration order matches the original hard-coded
* LOGIN_SYNC_TYPES array for behavioral parity.</p>
*/
2025-11-05 17:23:21 -05:00
public static void load() {
2025-11-05 17:33:24 -05:00
cacheEntries.clear();
2025-11-05 17:23:21 -05:00
playerSyncState.clear();
2026-03-31 21:44:23 +02:00
SyncRegistry.clear();
// Register all core handlers in deterministic order
// Order matches original LOGIN_SYNC_TYPES for login sync ordering
SyncRegistry.register(SyncType.FACTION, new FactionSyncHandler());
SyncRegistry.register(SyncType.DIALOG, new DialogSyncHandler());
SyncRegistry.register(SyncType.DIALOG_CATEGORY, new DialogCategorySyncHandler());
SyncRegistry.register(SyncType.QUEST, new QuestSyncHandler());
SyncRegistry.register(SyncType.QUEST_CATEGORY, new QuestCategorySyncHandler());
SyncRegistry.register(SyncType.PLAYERDATA, new PlayerDataSyncHandler());
SyncRegistry.register(SyncType.WORKBENCH_RECIPES, new WorkbenchRecipeSyncHandler());
SyncRegistry.register(SyncType.CARPENTRY_RECIPES, new CarpentryRecipeSyncHandler());
SyncRegistry.register(SyncType.ANVIL_RECIPES, new AnvilRecipeSyncHandler());
SyncRegistry.register(SyncType.CUSTOM_EFFECTS, new CustomEffectSyncHandler());
SyncRegistry.register(SyncType.MAGIC, new MagicSyncHandler());
SyncRegistry.register(SyncType.MAGIC_CYCLE, new MagicCycleSyncHandler());
SyncRegistry.register(SyncType.CUSTOM_ABILITY, new CustomAbilitySyncHandler());
SyncRegistry.register(SyncType.CHAINED_ABILITY, new ChainedAbilitySyncHandler());
// Register caches for all cached (login-sync) types
// The cache supplier delegates to the handler's serializeAll()
for (SyncType type : SyncRegistry.getLoginTypes()) {
SyncHandler handler = SyncRegistry.getHandler(type);
if (handler != null)
registerCache(type, handler::serializeAll);
}
}
2026-03-31 22:55:42 +02:00
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2026-03-31 22:55:42 +02:00
// Other Login syncers
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2025-11-05 16:52:38 -05:00
2026-03-31 22:55:42 +02:00
public static void beginLogin(EntityPlayerMP player) {
playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
PacketHandler.Instance.sendToPlayer(new LoginPacket(getServerCacheKey(), getServerRevisionSnapshot()), player);
2025-10-19 22:06:11 -04:00
}
2025-02-18 22:22:40 -05:00
2026-03-31 22:55:42 +02:00
private static void sendPostLoginPackets(EntityPlayerMP player) {
DBCAddon.instance.syncPlayer(player);
PlayerDataSyncHandler.getInstance().syncPlayerData(player, false);
PartyInfoPacket.sendPartyData(player);
ProfileSharedQuestPacket.sendToPlayer(player);
// Sync skin overlays after full handshake to ensure the client is ready.
// Overlays are not part of getSyncNBTFull(), and packets sent during
// PlayerLoggedInEvent can arrive before the client entity exists.
PlayerData data = PlayerData.get(player);
if (data != null) data.skinOverlays.updateClient();
}
// ═══════════════════════════════════════════════════════════════════════
// MAIN PIPELINE — completion of login handshake and full catalog sync
// ═══════════════════════════════════════════════════════════════════════
/**
* Runs the cached-family sync pass for one player using the currently tracked
* revision state. Used after the login handshake and by server-side callers
* that need to refresh a player's global cached sync families.
*/
public static void syncPlayer(EntityPlayerMP player) {
2025-11-05 17:23:21 -05:00
PlayerSyncState state = playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
2025-10-19 22:06:11 -04:00
2026-03-31 21:44:23 +02:00
// Registry-driven login iteration: iterate only cached types
for (SyncType type : SyncRegistry.getLoginTypes()) {
2025-11-05 17:23:21 -05:00
SyncCacheEntry entry = cacheEntries.get(type);
2026-03-31 22:55:42 +02:00
if (entry == null) continue;
2025-03-04 18:19:53 -05:00
2025-10-19 22:06:11 -04:00
int currentRevision = entry.getRevisionValue();
int lastRevision = state.getRevision(type);
2026-03-31 22:55:42 +02:00
if (lastRevision == currentRevision) continue;
2026-03-31 21:44:23 +02:00
2025-10-19 22:06:11 -04:00
CachedSyncPayload payload = entry.getPayload(type);
2026-03-31 22:55:42 +02:00
if (payload == null) continue;
2025-10-19 22:06:11 -04:00
if (lastRevision != payload.getRevision()) {
PacketHandler.Instance.sendToPlayer(new SyncPacket(type, payload), player);
state.updateRevision(type, payload.getRevision());
}
}
2026-03-31 22:55:42 +02:00
sendPostLoginPackets(player);
2025-10-19 22:06:11 -04:00
}
2026-03-31 22:55:42 +02:00
/**
* Completes the server-side revision handshake that follows {@link LoginPacket}.
*
* The client reports which cached revisions it believes are still valid for the
* current server identity. If the reported server identity is stale or missing,
* we reset the stored state and force a full cached resync. Otherwise we seed the
* player's tracked revisions from the client report and only send outdated families.
*/
public static void completeLoginRevisionHandshake(EntityPlayerMP player, String serverKey, String previousServerKey,
Map<SyncType, Integer> clientRevisions) {
String currentServerKey = getServerCacheKey();
2025-11-05 17:33:24 -05:00
PlayerSyncState state = playerSyncState.computeIfAbsent(player.getUniqueID(), PlayerSyncState::new);
2026-03-31 22:55:42 +02:00
boolean serverIdentityMismatch = !currentServerKey.equals(serverKey);
boolean previousServerMismatch = !currentServerKey.isEmpty() && (previousServerKey == null || !currentServerKey.equals(
previousServerKey));
boolean canReuseClientRevisions = clientRevisions != null && !clientRevisions.isEmpty();
2025-10-19 22:06:11 -04:00
2026-03-31 22:55:42 +02:00
// Any server identity mismatch means the client's cached revision map is unsafe to reuse.
if (serverIdentityMismatch || previousServerMismatch || !canReuseClientRevisions)
state.reset();
2026-03-31 22:55:42 +02:00
else // The client and server agree on identity, so seed the stored state from the report.
state.applyHandshake(clientRevisions);
2025-10-19 22:06:11 -04:00
syncPlayer(player);
}
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2026-03-31 22:55:42 +02:00
// Generic dispatcher — called from outside SyncController
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2025-01-29 18:25:34 -05:00
2026-03-31 21:44:23 +02:00
/**
* Generic full-broadcast helper. Rebuilds the cache for the given type
* and sends to all connected players.
*/
public static void syncAll(SyncType type) {
2026-03-31 22:55:42 +02:00
CachedSyncPayload payload = rebuildCache(type);
if (payload == null) return;
2026-03-31 21:44:23 +02:00
PacketHandler.Instance.sendToAll(new SyncPacket(type, payload));
updateAllPlayerRevisions(type, payload.getRevision());
}
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2026-03-31 22:55:42 +02:00
// Server-side dispatchers — registry-driven
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2025-01-29 18:25:34 -05:00
2026-03-31 19:59:53 +02:00
public static void syncRemove(SyncType syncType, int id) {
Map<SyncType, Integer> revisions = invalidateCaches(syncType);
2026-03-31 22:55:42 +02:00
int revision = getRequestedInvalidationRevision(syncType, revisions);
2026-03-31 21:44:23 +02:00
PacketHandler.Instance.sendToAll(new SyncPacket(syncType, EnumSyncAction.REMOVE, id, revision, new NBTTagCompound()));
2025-10-19 22:06:11 -04:00
updateAllPlayerRevisions(revisions);
2025-01-29 18:25:34 -05:00
}
2026-03-31 21:44:23 +02:00
public static void syncUpdate(SyncType type, int cat, NBTTagCompound compound) {
Map<SyncType, Integer> revisions = invalidateCaches(type);
2026-03-31 22:55:42 +02:00
int revision = getRequestedInvalidationRevision(type, revisions);
2026-03-31 21:44:23 +02:00
PacketHandler.Instance.sendToAll(new SyncPacket(type, EnumSyncAction.UPDATE, cat, revision, compound));
updateAllPlayerRevisions(revisions);
2025-02-10 02:44:28 -05:00
}
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2026-03-31 22:55:42 +02:00
// Client-side handlers — registry-driven handler delegation
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
2025-02-10 16:38:50 -05:00
2025-01-29 18:25:34 -05:00
@SideOnly(Side.CLIENT)
2026-03-31 22:55:42 +02:00
public static void clientHandleAll(SyncType syncType, int revision, NBTTagCompound fullCompound) {
2026-03-31 21:44:23 +02:00
SyncHandler handler = SyncRegistry.getHandler(syncType);
2026-03-31 22:55:42 +02:00
if (handler == null) return;
try {
2026-03-31 21:44:23 +02:00
handler.clientHandleReload(fullCompound);
2026-03-31 22:55:42 +02:00
} catch (Exception e) {
LogWriter.error("[SyncController] Failed to handle RELOAD for sync type " + syncType, e);
}
2026-03-31 19:59:53 +02:00
ClientCacheHandler.updateClientRevision(syncType, revision);
2026-03-31 22:55:42 +02:00
2025-01-29 18:25:34 -05:00
}
@SideOnly(Side.CLIENT)
2026-03-31 22:55:42 +02:00
public static void clientHandleUpdate(SyncType syncType, int category_id, int revision, NBTTagCompound compound) {
2026-03-31 21:44:23 +02:00
SyncHandler handler = SyncRegistry.getHandler(syncType);
2026-03-31 22:55:42 +02:00
if (handler == null) return;
try {
2026-03-31 21:44:23 +02:00
handler.clientHandleUpdate(compound, category_id);
2026-03-31 22:55:42 +02:00
} catch (Exception e) {
LogWriter.error("[SyncController] Failed to handle UPDATE for sync type " + syncType, e);
}
2026-03-31 21:44:23 +02:00
2026-03-31 19:59:53 +02:00
ClientCacheHandler.updateClientRevision(syncType, revision);
2025-01-29 18:25:34 -05:00
}
@SideOnly(Side.CLIENT)
2026-03-31 22:55:42 +02:00
public static void clientHandleRemove(SyncType syncType, int id, int revision, NBTTagCompound compound) {
2026-03-31 21:44:23 +02:00
SyncHandler handler = SyncRegistry.getHandler(syncType);
2026-03-31 22:55:42 +02:00
if (handler == null) return;
try {
2026-03-31 21:44:23 +02:00
handler.clientHandleRemove(id);
2026-03-31 22:55:42 +02:00
handler.clientHandleRemove(compound);
} catch (Exception e) {
LogWriter.error("[SyncController] Failed to handle REMOVE for sync type " + syncType, e);
}
2026-03-31 21:44:23 +02:00
2026-03-31 19:59:53 +02:00
ClientCacheHandler.updateClientRevision(syncType, revision);
2025-01-29 18:25:34 -05:00
}
2025-02-10 21:41:54 -05:00
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
// Cache and revision internals
// ═══════════════════════════════════════════════════════════════════════
2025-10-19 22:06:11 -04:00
2026-03-31 19:59:53 +02:00
private static void registerCache(SyncType type, Supplier<NBTTagCompound> supplier) {
cacheEntries.put(type, new SyncCacheEntry(supplier));
2025-10-19 22:06:11 -04:00
}
2026-03-31 22:55:42 +02:00
private static CachedSyncPayload rebuildCache(SyncType type) {
2025-11-05 17:33:24 -05:00
SyncCacheEntry entry = cacheEntries.get(type);
2026-03-31 22:55:42 +02:00
if (entry == null) return null;
2026-03-31 21:44:23 +02:00
2025-10-19 22:06:11 -04:00
entry.invalidate();
return entry.getPayload(type);
}
2026-03-31 21:44:23 +02:00
/**
* Registry-driven invalidation: delegates to handler-owned
* invalidation targets via {@link SyncRegistry#getInvalidationTargets}.
*/
2026-03-31 22:55:42 +02:00
private static Map<SyncType, Integer> invalidateCaches(SyncType type) {
2026-03-31 21:44:23 +02:00
Set<SyncType> targets = SyncRegistry.getInvalidationTargets(type);
2026-03-31 19:59:53 +02:00
Map<SyncType, Integer> revisions = new LinkedHashMap<>();
for (SyncType target : targets) {
2025-11-05 17:33:24 -05:00
SyncCacheEntry entry = cacheEntries.get(target);
2025-10-19 22:06:11 -04:00
if (entry != null) {
int newRevision = entry.invalidate();
revisions.put(target, newRevision);
}
}
return revisions;
}
2026-03-31 22:55:42 +02:00
private static int getRequestedInvalidationRevision(SyncType type, Map<SyncType, Integer> revisions) {
Integer revision = revisions.get(type);
return revision != null ? revision : getCurrentServerRevision(type);
}
public static int getCurrentServerRevision(SyncType type) {
SyncCacheEntry entry = cacheEntries.get(type);
return entry == null ? -1 : entry.getRevisionValue();
}
private static Map<SyncType, Integer> getServerRevisionSnapshot() {
// Registry-driven: iterate login types from registry
Map<SyncType, Integer> snapshot = new LinkedHashMap<>();
for (SyncType type : SyncRegistry.getLoginTypes()) {
SyncCacheEntry entry = cacheEntries.get(type);
if (entry != null)
snapshot.put(type, entry.getRevisionValue());
2025-10-19 22:06:11 -04:00
}
2026-03-31 22:55:42 +02:00
return snapshot;
}
private static void updateAllPlayerRevisions(SyncType type, int revision) {
if (revision < 0) return;
for (PlayerSyncState state : playerSyncState.values())
2025-10-19 22:06:11 -04:00
state.updateRevision(type, revision);
}
2026-03-31 19:59:53 +02:00
private static void updateAllPlayerRevisions(Map<SyncType, Integer> revisions) {
2026-03-31 22:55:42 +02:00
if (revisions.isEmpty()) return;
2025-11-05 17:33:24 -05:00
for (PlayerSyncState state : playerSyncState.values()) {
2026-03-31 22:55:42 +02:00
for (Map.Entry<SyncType, Integer> entry : revisions.entrySet())
2025-10-19 22:06:11 -04:00
state.updateRevision(entry.getKey(), entry.getValue());
}
}
2026-03-31 22:55:42 +02:00
private static String getServerCacheKey() {
MinecraftServer server = MinecraftServer.getServer();
if (server == null || !server.isDedicatedServer()) return "";
return SERVER_IDENTITY_KEY;
}
2026-03-31 21:44:23 +02:00
// ═══════════════════════════════════════════════════════════════════════
// Inner classes — cache payload and player revision state
// ═══════════════════════════════════════════════════════════════════════
2025-10-19 22:06:11 -04:00
public static final class CachedSyncPayload {
private final int revision;
private final byte[] payload;
private final byte[][] chunks;
private CachedSyncPayload(int revision, byte[] payload, byte[][] chunks) {
this.revision = revision;
this.payload = payload;
this.chunks = chunks;
}
public int getRevision() {
return revision;
}
public byte[] getPayload() {
return payload;
}
public byte[][] getChunks() {
return chunks;
}
}
private static final class SyncCacheEntry {
private final Supplier<NBTTagCompound> supplier;
private volatile CachedSyncPayload payload;
private int revision = 0;
private boolean dirty = true;
2026-03-31 19:59:53 +02:00
private SyncCacheEntry(Supplier<NBTTagCompound> supplier) {
2025-10-19 22:06:11 -04:00
this.supplier = supplier;
}
2026-03-31 19:59:53 +02:00
private synchronized CachedSyncPayload getPayload(SyncType requestedType) {
2026-03-31 22:55:42 +02:00
if (!dirty && payload != null) return payload;
2025-10-19 22:06:11 -04:00
int payloadRevision = revision;
ByteBuf buffer = Unpooled.buffer();
byte[] bytes;
try {
2026-03-31 22:55:42 +02:00
NBTTagCompound data = supplier.get();
2025-10-19 22:06:11 -04:00
buffer.writeInt(requestedType.ordinal());
buffer.writeInt(EnumSyncAction.RELOAD.ordinal());
buffer.writeInt(-1);
buffer.writeInt(payloadRevision);
ByteBufUtils.writeBigNBT(buffer, data);
bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
2026-03-31 22:55:42 +02:00
} catch (Exception e) {
LogWriter.error("Failed to serialize sync payload for " + requestedType, e);
return null;
2025-10-19 22:06:11 -04:00
} finally {
buffer.release();
}
byte[][] chunks = splitIntoChunks(bytes);
payload = new CachedSyncPayload(payloadRevision, bytes, chunks);
dirty = false;
return payload;
}
2026-03-31 22:55:42 +02:00
private static byte[][] splitIntoChunks(byte[] payload) {
int totalSize = payload.length;
int chunkCount = (totalSize + LargeAbstractPacket.CHUNK_SIZE - 1) / LargeAbstractPacket.CHUNK_SIZE;
if (chunkCount <= 0)
chunkCount = 1;
byte[][] chunks = new byte[chunkCount][];
for (int i = 0; i < chunkCount; i++) {
int offset = i * LargeAbstractPacket.CHUNK_SIZE;
int length = Math.min(LargeAbstractPacket.CHUNK_SIZE, totalSize - offset);
byte[] chunk = new byte[length];
System.arraycopy(payload, offset, chunk, 0, length);
chunks[i] = chunk;
}
return chunks;
}
2025-10-19 22:06:11 -04:00
private synchronized int invalidate() {
dirty = true;
payload = null;
revision++;
return revision;
}
private synchronized int getRevisionValue() {
return revision;
}
2025-11-05 16:52:38 -05:00
private synchronized void reset() {
dirty = true;
payload = null;
revision = 0;
}
2025-10-19 22:06:11 -04:00
}
private static final class PlayerSyncState {
2026-03-31 19:59:53 +02:00
private final Map<SyncType, Integer> revisions = new HashMap<>();
2025-10-19 22:06:11 -04:00
private PlayerSyncState(UUID ignored) {
}
2026-03-31 19:59:53 +02:00
private synchronized int getRevision(SyncType type) {
2025-10-19 22:06:11 -04:00
Integer value = revisions.get(type);
return value == null ? -1 : value;
}
2026-03-31 19:59:53 +02:00
private synchronized void updateRevision(SyncType type, int revision) {
2025-10-19 22:06:11 -04:00
revisions.put(type, revision);
}
2026-03-31 19:59:53 +02:00
private synchronized void applyHandshake(Map<SyncType, Integer> incoming) {
2026-03-31 22:55:42 +02:00
if (incoming == null || incoming.isEmpty()) return;
2025-10-19 22:06:11 -04:00
revisions.putAll(incoming);
}
private synchronized void reset() {
revisions.clear();
}
2025-10-19 22:06:11 -04:00
}
2025-01-29 18:25:34 -05:00
}