diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 5b3d9ce..a498c32 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -21,7 +21,7 @@ jobs: distribution: 'zulu' java-version: '21' - name: Build projects - run: chmod 777 gradlew && ./gradlew build + run: chmod 777 gradlew && ./gradlew generateTemplates && ./gradlew build - name: Copy built jar run: mkdir build_out && cp Freesia-Velocity/build/libs/*.jar ./build_out && cp Freesia-Worker/build/libs/*.jar ./build_out && cp Freesia-Backend/build/libs/*.jar ./build_out - name: Upload Artifact diff --git a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/FreesiaBackend.java b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/FreesiaBackend.java index 68d31aa..7da32fe 100644 --- a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/FreesiaBackend.java +++ b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/FreesiaBackend.java @@ -16,12 +16,12 @@ public void onEnable() { INSTANCE = this; // TODO- De-hard-coding? - Bukkit.getMessenger().registerIncomingPluginChannel(this, "freesia:tracker_sync", this.trackerProcessor); - Bukkit.getMessenger().registerOutgoingPluginChannel(this, "freesia:tracker_sync"); + Bukkit.getMessenger().registerIncomingPluginChannel(this, TrackerProcessor.CHANNEL_NAME, this.trackerProcessor); + Bukkit.getMessenger().registerOutgoingPluginChannel(this, TrackerProcessor.CHANNEL_NAME); // TODO- De-hard-coding? - Bukkit.getMessenger().registerIncomingPluginChannel(this, "freesia:virtual_player_management", this.virtualPlayerManager); - Bukkit.getMessenger().registerOutgoingPluginChannel(this, "freesia:virtual_player_management"); + Bukkit.getMessenger().registerIncomingPluginChannel(this, VirtualPlayerManager.CHANNEL_NAME, this.virtualPlayerManager); + Bukkit.getMessenger().registerOutgoingPluginChannel(this, VirtualPlayerManager.CHANNEL_NAME); Bukkit.getPluginManager().registerEvents(this.trackerProcessor, this); diff --git a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/misc/VirtualPlayerManager.java b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/misc/VirtualPlayerManager.java index 882b37d..7d04741 100644 --- a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/misc/VirtualPlayerManager.java +++ b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/misc/VirtualPlayerManager.java @@ -17,7 +17,7 @@ import java.util.function.Consumer; public class VirtualPlayerManager implements PluginMessageListener, Listener { - private static final String CHANNEL_NAME = "freesia:virtual_player_management"; + public static final String CHANNEL_NAME = "freesia:virtual_player_management"; private final AtomicInteger eventIdGenerator = new AtomicInteger(0); private final Map> pendingCallbacks = new ConcurrentHashMap<>(); diff --git a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/tracker/TrackerProcessor.java b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/tracker/TrackerProcessor.java index 22b4b90..220fca5 100644 --- a/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/tracker/TrackerProcessor.java +++ b/Freesia-Backend/src/main/java/meow/kikir/freesia/backend/tracker/TrackerProcessor.java @@ -19,7 +19,7 @@ import java.util.*; public class TrackerProcessor implements PluginMessageListener, Listener { - private static final String CHANNEL_NAME = "freesia:tracker_sync"; + public static final String CHANNEL_NAME = "freesia:tracker_sync"; // The default tracker event which is provided by Paper @EventHandler diff --git a/Freesia-Velocity/build.gradle.kts b/Freesia-Velocity/build.gradle.kts index 8914359..98a4fb0 100644 --- a/Freesia-Velocity/build.gradle.kts +++ b/Freesia-Velocity/build.gradle.kts @@ -3,11 +3,12 @@ dependencies { compileOnly("com.github.retrooper:packetevents-velocity:2.5.0") compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") compileOnly("net.kyori:adventure-api:4.17.0") + + implementation(project(":Freesia-Common")) implementation("com.electronwill.night-config:toml:3.6.6") implementation("org.geysermc.mcprotocollib:protocol:1.21-SNAPSHOT") - implementation(project(":Freesia-Common")) implementation("ca.spottedleaf:concurrentutil:0.0.3") - annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") + annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") } val targetJavaVersion = 21 diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/Freesia.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/Freesia.java index 9f5c6b2..42eb393 100644 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/Freesia.java +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/Freesia.java @@ -12,7 +12,6 @@ import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Dependency; @@ -29,7 +28,7 @@ import meow.kikir.freesia.velocity.network.backend.MasterServerMessageHandler; import meow.kikir.freesia.velocity.network.mc.FreesiaPlayerTracker; import meow.kikir.freesia.velocity.network.misc.VirtualPlayerManager; -import meow.kikir.freesia.velocity.network.ysm.DefaultYsmPacketProxyImpl; +import meow.kikir.freesia.velocity.network.ysm.RealPlayerYsmPacketProxyImpl; import meow.kikir.freesia.velocity.network.ysm.VirtualYsmPacketProxyImpl; import meow.kikir.freesia.velocity.network.ysm.YsmMapperPayloadManager; import meow.kikir.freesia.velocity.storage.DefaultRealPlayerDataStorageManagerImpl; @@ -44,7 +43,7 @@ import java.util.Map; import java.util.UUID; -@Plugin(id = "freesia", name = "Freesia", version = "2.3.2-universal", authors = {"Earthme", "HappyRespawnanchor", "xiaozhangup"}, dependencies = @Dependency(id = "packetevents")) +@Plugin(id = "freesia", name = "Freesia", version = BuildConstants.VERSION, authors = {"Earthme", "HappyRespawnanchor", "xiaozhangup"}, dependencies = @Dependency(id = "packetevents")) public class Freesia implements PacketListener { public static final FreesiaPlayerTracker tracker = new FreesiaPlayerTracker(); public static final IDataStorageManager realPlayerDataStorageManager = new DefaultRealPlayerDataStorageManagerImpl(); @@ -71,7 +70,7 @@ private static void printLogo() { PROXY_SERVER.sendMessage(Component.text(" / /_ / ___// _ \\ / _ \\ / ___// // __ `/")); PROXY_SERVER.sendMessage(Component.text(" / __/ / / / __// __/(__ )/ // /_/ / ")); PROXY_SERVER.sendMessage(Component.text("/_/ /_/ \\___/ \\___//____//_/ \\__,_/ ")); - PROXY_SERVER.sendMessage(Component.text("\n Powered by YesSteveModel and all contributors, Version: 2.3.2-universal")); + PROXY_SERVER.sendMessage(Component.text("\n Powered by YesSteveModel and all contributors, Version: " + BuildConstants.VERSION)); PROXY_SERVER.sendMessage(Component.text("----------------------------------------------------------------")); } @@ -96,7 +95,7 @@ public void onProxyStart(ProxyInitializeEvent event) { LOGGER.info("Registering events and packet listeners."); // Mapper (Core function) - mapperManager = new YsmMapperPayloadManager(DefaultYsmPacketProxyImpl::new, VirtualYsmPacketProxyImpl::new); + mapperManager = new YsmMapperPayloadManager(RealPlayerYsmPacketProxyImpl::new, VirtualYsmPacketProxyImpl::new); // Register mc packet listener PacketEvents.getAPI().getEventManager().registerListener(this, PacketListenerPriority.HIGHEST); // Attach to ysm channel @@ -139,28 +138,33 @@ public EventTask onPlayerConnected(@NotNull ServerConnectedEvent event) { final Player targetPlayer = event.getPlayer(); return EventTask.async(() -> { - // On first connect - if (!mapperManager.hasPlayer(targetPlayer)) { - this.logger.info("Initiating mapper session for player {}", targetPlayer.getUsername()); + this.logger.info("Initiating mapper session for player {}", targetPlayer.getUsername()); - // Create mapper session - mapperManager.firstCreateMapper(targetPlayer); + // Create mapper session + mapperManager.autoCreateMapper(targetPlayer); - // Add to client kicker - kickChecker.onPlayerJoin(targetPlayer); - return; - } - - // Player might switch its current server - logger.info("Player {} has changed backend server. Reconnecting mapper session", targetPlayer.getUsername()); - // So, reconnect mapper session - mapperManager.reconnectWorker(targetPlayer); + // Add to client kicker + kickChecker.onPlayerJoin(targetPlayer); }); } @Subscribe - public void onServerPreConnect(@NotNull ServerPreConnectEvent event) { - mapperManager.updateRealPlayerEntityId(event.getPlayer(), -1); // Reset player's entity id to -1 as non initialized to prevent incorrect tracker status update + public EventTask onServerPreConnect(@NotNull ServerPreConnectEvent event) { + //mapperManager.updateRealPlayerEntityId(event.getPlayer(), -1); // Reset player's entity id to -1 as non initialized to prevent incorrect tracker status update + final Player player = event.getPlayer(); + + // Create mapper processor here + return EventTask.async(() -> { + final boolean potentialDisconnected = mapperManager.disconnectAlreadyConnected(player); + + if (potentialDisconnected) { + // Player switched server, do log + logger.info("Player {} has changed backend server. Reconnecting mapper session", player.getUsername()); + } + + // Re init after removed or init on first connected + mapperManager.initMapperPacketProcessor(event.getPlayer()); + }); } @Subscribe @@ -184,17 +188,12 @@ public void onPacketSend(@NotNull PacketSendEvent event) { final Player target = event.getPlayer(); logger.info("Entity id update for player {} to {}", target.getUsername(), playerSpawnPacket.getEntityId()); + // Update id and try notifying update once mapperManager.updateRealPlayerEntityId(target, playerSpawnPacket.getEntityId()); - } - } - // We need to push off the packet process of worker because the player's login packet might still not reached to the client when we create the mapper session - @Subscribe - public void onServerPostConnect(@NotNull ServerPostConnectEvent postConnectEvent) { - final Player target = postConnectEvent.getPlayer(); - - // Retire callbacks of worker ysm packet processing - mapperManager.onBackendReady(target); + // Finalize callbacks + PROXY_SERVER.getScheduler().buildTask(this, () -> mapperManager.onBackendReady(target)).schedule(); + } } } diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/DefaultYsmPacketProxyImpl.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/DefaultYsmPacketProxyImpl.java deleted file mode 100644 index 771c325..0000000 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/DefaultYsmPacketProxyImpl.java +++ /dev/null @@ -1,245 +0,0 @@ -package meow.kikir.freesia.velocity.network.ysm; - -import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; -import com.github.retrooper.packetevents.protocol.player.ClientVersion; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.proxy.Player; -import meow.kikir.freesia.velocity.FreesiaConstants; -import meow.kikir.freesia.velocity.Freesia; -import meow.kikir.freesia.velocity.YsmProtocolMetaFile; -import meow.kikir.freesia.velocity.events.PlayerYsmHandshakeEvent; -import meow.kikir.freesia.velocity.events.PlayerEntityStateChangeEvent; -import meow.kikir.freesia.velocity.network.mc.NbtRemapper; -import meow.kikir.freesia.velocity.network.mc.impl.StandardNbtRemapperImpl; -import meow.kikir.freesia.velocity.utils.FriendlyByteBuf; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.locks.StampedLock; - -public class DefaultYsmPacketProxyImpl implements YsmPacketProxy{ - private final Player player; - private final NbtRemapper nbtRemapper = new StandardNbtRemapperImpl(); - - private volatile NBTCompound lastYsmEntityStatus = null; - private final StampedLock entityStatusWriteLock = new StampedLock(); // Use optimistic locks - - private volatile int playerEntityId = -1; - private volatile int workerPlayerEntityId = -1; - - public DefaultYsmPacketProxyImpl(@NotNull Player player) { - this.player = player; - } - - @Override - public void setPlayerWorkerEntityId(int id) { - this.workerPlayerEntityId = id; - } - - @Override - public void setPlayerEntityId(int id) { - final int oldEntityId = this.playerEntityId; - - this.playerEntityId = id; - - if (oldEntityId == -1 && id != -1) { - // Try sync tracker status once - // We could hardly say there is no out-of-ordering issue here - // So we had better to force fire the update - this.refreshToOthers(); - } - } - - @Override - public int getPlayerEntityId() { - return this.playerEntityId; - } - - @Override - public int getPlayerWorkerEntityId() { - return this.workerPlayerEntityId; - } - - @Override - public Player getOwner() { - return this.player; - } - - private boolean isEntityStateOfSelf(int entityId){ - final int currentWorkerEntityId = this.workerPlayerEntityId; - - if (currentWorkerEntityId == -1) { - return false; - } - - return currentWorkerEntityId == entityId; - } - - @Override - public void sendEntityStateTo(@NotNull Player target){ - this.sendEntityStateToInternal(target, this.lastYsmEntityStatus); - } - - private void sendEntityStateToInternal(Player target, NBTCompound entityStatus) { - final int currentEntityId = this.playerEntityId; // Get current entity id on the server of the player - - if (entityStatus == null || currentEntityId == -1) { // If no data got or player is not in the backend server currently - return; - } - - try { - final Object targetChannel = PacketEvents.getAPI().getProtocolManager().getChannel(target.getUniqueId()); // Get the netty channel of the player - - if (targetChannel == null) { - return; - } - - final ClientVersion clientVersion = PacketEvents.getAPI().getProtocolManager().getClientVersion(targetChannel); // Get the client version of the player - - final int targetProtocolVer = clientVersion.getProtocolVersion(); // Protocol version(Used for nbt remappers) - final FriendlyByteBuf wrappedPacketData = new FriendlyByteBuf(Unpooled.buffer()); - - wrappedPacketData.writeByte(4); - wrappedPacketData.writeVarInt(currentEntityId); - wrappedPacketData.writeBytes(this.nbtRemapper.shouldRemap(targetProtocolVer) ? this.nbtRemapper.remapToMasterVer(entityStatus) : this.nbtRemapper.remapToWorkerVer(entityStatus)); // Remap nbt if needed - - this.sendPluginMessageTo(target, YsmMapperPayloadManager.YSM_CHANNEL_KEY_VELOCITY, wrappedPacketData); - } catch (Exception e) { - Freesia.LOGGER.error("Error in encoding nbt or sending packet!", e); - } - } - - @Override - public void setEntityDataRaw(NBTCompound data) { - final long stamp = this.entityStatusWriteLock.writeLock(); - try { - this.lastYsmEntityStatus = data; - }finally { - this.entityStatusWriteLock.unlockWrite(stamp); - } - } - - @Override - public void refreshToOthers() { - NBTCompound entityStatusCopy; // Copy value - - // Try optimistic read first - long stamp = this.entityStatusWriteLock.tryOptimisticRead(); - if (this.entityStatusWriteLock.validate(stamp)) { - entityStatusCopy = this.lastYsmEntityStatus; - }else { - // Fallback to read lock - try { - stamp = this.entityStatusWriteLock.readLock(); - entityStatusCopy = this.lastYsmEntityStatus; - }finally { - this.entityStatusWriteLock.unlockRead(stamp); - } - } - - // If the player does not have any data - if (entityStatusCopy == null) { - return; - } - - this.sendEntityStateToInternal(this.player, entityStatusCopy); // Sync to self - - Freesia.tracker.getCanSee(this.player.getUniqueId()).whenComplete((beingWatched, exception) -> { // Async tracker check request to backend server - // Exception handling - if (exception != null) { - Freesia.LOGGER.warn("Failed to fetch tracker info!", exception); - return; - } - - if (beingWatched != null) { // When backend is not fully initialized yet or other reasons - for (UUID targetUUID : beingWatched) { - final Optional targetNullable = Freesia.PROXY_SERVER.getPlayer(targetUUID); - - if (targetNullable.isPresent()) { // Skip send to NPCs - final Player target = targetNullable.get(); - - if (!Freesia.mapperManager.isPlayerInstalledYsm(target)) { // Skip if target is not ysm-installed - continue; - } - - this.sendEntityStateToInternal(target, entityStatusCopy); // Sync to target - } - } - } - }); - } - - @Override - public NBTCompound getCurrentEntityState() { - return this.lastYsmEntityStatus; - } - - @Override - public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { - final FriendlyByteBuf mcBuffer = new FriendlyByteBuf(copiedPacketData); - final byte packetId = mcBuffer.readByte(); - - if (packetId == YsmProtocolMetaFile.getS2CPacketId(FreesiaConstants.YsmProtocolMetaConstants.Clientbound.ENTITY_DATA_UPDATE)) { - final int workerEntityId = mcBuffer.readVarInt(); - - try { - if (!this.isEntityStateOfSelf(workerEntityId)) { // Check if the packet is current player and drop to prevent incorrect broadcasting - return ProxyComputeResult.ofDrop(); // Do not process the entity state if it is not ours - } - - Freesia.PROXY_SERVER.getEventManager().fire(new PlayerEntityStateChangeEvent(this.player,workerEntityId, this.nbtRemapper.readBound(mcBuffer))).thenAccept(result -> { // Use NbtRemapper for multi version clients - // Acquire write lock first - final long stamp = this.entityStatusWriteLock.writeLock(); - try { - this.lastYsmEntityStatus = result.getEntityState(); // Update value to the result - }finally { - this.entityStatusWriteLock.unlockWrite(stamp); - } - - this.refreshToOthers(); - }).join(); // Force blocking as we do not wanna break the sequence of the data - } catch (Exception e) { - Freesia.LOGGER.error("Error while in processing tracker!", e); - return ProxyComputeResult.ofDrop(); - } - - return ProxyComputeResult.ofDrop(); - } - - if (packetId == YsmProtocolMetaFile.getS2CPacketId(FreesiaConstants.YsmProtocolMetaConstants.Clientbound.HAND_SHAKE_CONFIRMED)) { - final String backendVersion = mcBuffer.readUtf(); - final boolean canSwitchModel = mcBuffer.readBoolean(); - - Freesia.LOGGER.info("Replying ysm client with server version {}.Can switch model? : {}", backendVersion, canSwitchModel); - - return ProxyComputeResult.ofPass(); - } - - return ProxyComputeResult.ofPass(); - } - - @Override - public ProxyComputeResult processC2S(Key key, ByteBuf copiedPacketData) { - final FriendlyByteBuf mcBuffer = new FriendlyByteBuf(copiedPacketData); - final byte packetId = mcBuffer.readByte(); - - if (packetId == YsmProtocolMetaFile.getC2SPacketId(FreesiaConstants.YsmProtocolMetaConstants.Serverbound.HAND_SHAKE_REQUEST)) { - final ResultedEvent.GenericResult result = Freesia.PROXY_SERVER.getEventManager().fire(new PlayerYsmHandshakeEvent(this.player)).join().getResult(); - - if (!result.isAllowed()) { - return ProxyComputeResult.ofDrop(); - } - - final String clientYsmVersion = mcBuffer.readUtf(); - Freesia.LOGGER.info("Player {} is connected to the backend with ysm version {}", this.player.getUsername(), clientYsmVersion); - Freesia.mapperManager.onClientYsmHandshakePacketReply(this.player); - } - - return ProxyComputeResult.ofPass(); - } -} diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/MapperSessionProcessor.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/MapperSessionProcessor.java index df3d940..20c8860 100644 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/MapperSessionProcessor.java +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/MapperSessionProcessor.java @@ -11,20 +11,21 @@ import org.geysermc.mcprotocollib.network.Session; import org.geysermc.mcprotocollib.network.event.session.*; import org.geysermc.mcprotocollib.network.packet.Packet; -import org.geysermc.mcprotocollib.network.tcp.TcpClientSession; import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundCustomPayloadPacket; import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundPingPacket; import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundPongPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket; -import java.util.concurrent.locks.LockSupport; +import java.util.Optional; +import java.util.UUID; public class MapperSessionProcessor implements SessionListener { private final Player bindPlayer; private final YsmPacketProxy packetProxy; private final YsmMapperPayloadManager mapperPayloadManager; private final MultiThreadedQueue pendingYsmPacketsInbound = new MultiThreadedQueue<>(); + private final MultiThreadedQueue pendingTrackerUpdatesTo = new MultiThreadedQueue<>(); private volatile Session session; private volatile boolean kickMasterWhenDisconnect = true; @@ -34,19 +35,38 @@ public MapperSessionProcessor(Player bindPlayer, YsmPacketProxy packetProxy, Ysm this.mapperPayloadManager = mapperPayloadManager; } - public YsmPacketProxy getPacketProxy() { + protected boolean queueTrackerUpdate(UUID target) { + return this.pendingTrackerUpdatesTo.offer(target); + } + + protected void retireTrackerCallbacks(){ + UUID toSend; + while ((toSend = this.pendingTrackerUpdatesTo.pollOrBlockAdds()) != null) { + final Optional player = Freesia.PROXY_SERVER.getPlayer(toSend); + + if (player.isEmpty()) { + continue; + } + + final Player targetPlayer = player.get(); + + this.packetProxy.sendEntityStateTo(targetPlayer); + } + } + + protected YsmPacketProxy getPacketProxy() { return this.packetProxy; } - public Session getSession() { + protected Session getSession() { return this.session; } - public void setKickMasterWhenDisconnect(boolean kickMasterWhenDisconnect) { + protected void setKickMasterWhenDisconnect(boolean kickMasterWhenDisconnect) { this.kickMasterWhenDisconnect = kickMasterWhenDisconnect; } - public void processPlayerPluginMessage(byte[] packetData) { + protected void processPlayerPluginMessage(byte[] packetData) { final ProxyComputeResult result = this.packetProxy.processC2S(YsmMapperPayloadManager.YSM_CHANNEL_KEY_ADVENTURE, Unpooled.copiedBuffer(packetData)); switch (result.result()) { @@ -69,7 +89,7 @@ public Player getBindPlayer() { return this.bindPlayer; } - public void onBackendReady() { + protected void onBackendReady() { // Process incoming packets that we had not ready to process before PendingPacket pendingYsmPacket; while ((pendingYsmPacket = this.pendingYsmPacketsInbound.pollOrBlockAdds()) != null) { // Destroy(block add operations) the queue @@ -82,9 +102,6 @@ public void packetReceived(Session session, Packet packet) { if (packet instanceof ClientboundLoginPacket loginPacket) { // Notify entity update to notify the tracker update of the player Freesia.mapperManager.updateWorkerPlayerEntityId(this.bindPlayer, loginPacket.getEntityId()); - // Worker connection callbacks, but we are not using it currently - // Considering to remove it in the future - Freesia.mapperManager.onProxyLoggedin(this.bindPlayer, this, ((TcpClientSession) session)); } if (packet instanceof ClientboundCustomPayloadPacket payloadPacket) { @@ -165,7 +182,7 @@ public void disconnected(DisconnectedEvent event) { this.session = null; //Set session to null to finalize the mapper connection } - public void waitForDisconnected() { + protected void waitForDisconnected() { // We will set the session to null after finishing all disconnect logics while (this.session != null) { Thread.onSpinWait(); // Spin wait instead of block waiting diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java new file mode 100644 index 0000000..f035a86 --- /dev/null +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/RealPlayerYsmPacketProxyImpl.java @@ -0,0 +1,93 @@ +package meow.kikir.freesia.velocity.network.ysm; + +import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.proxy.Player; +import meow.kikir.freesia.velocity.FreesiaConstants; +import meow.kikir.freesia.velocity.Freesia; +import meow.kikir.freesia.velocity.YsmProtocolMetaFile; +import meow.kikir.freesia.velocity.events.PlayerYsmHandshakeEvent; +import meow.kikir.freesia.velocity.events.PlayerEntityStateChangeEvent; +import meow.kikir.freesia.velocity.utils.FriendlyByteBuf; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class RealPlayerYsmPacketProxyImpl extends YsmPacketProxyLayer{ + + public RealPlayerYsmPacketProxyImpl(Player player) { + super(player); + } + + @Override + public CompletableFuture> fetchTrackerList(UUID observer) { + return Freesia.tracker.getCanSee(observer); + } + + + @Override + public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) { + final FriendlyByteBuf mcBuffer = new FriendlyByteBuf(copiedPacketData); + final byte packetId = mcBuffer.readByte(); + + if (packetId == YsmProtocolMetaFile.getS2CPacketId(FreesiaConstants.YsmProtocolMetaConstants.Clientbound.ENTITY_DATA_UPDATE)) { + final int workerEntityId = mcBuffer.readVarInt(); + + if (!this.isEntityStateOfSelf(workerEntityId)) { // Check if the packet is current player and drop to prevent incorrect broadcasting + return ProxyComputeResult.ofDrop(); // Do not process the entity state if it is not ours + } + + try { + Freesia.PROXY_SERVER.getEventManager().fire(new PlayerEntityStateChangeEvent(this.player,workerEntityId, this.nbtRemapper.readBound(mcBuffer))).thenAccept(result -> { // Use NbtRemapper for multi version clients + final NBTCompound to = result.getEntityState(); + + this.acquireWriteReference(); // Acquire write reference + + LAST_YSM_ENTITY_DATA_HANDLE.setVolatile(this, to); + + this.releaseWriteReference(); // Release write reference + + this.notifyFullTrackerUpdates(); // Notify updates + }).join(); // Force blocking as we do not want to break the sequence of the data + }catch (Exception e){ + Freesia.LOGGER.error("Failed to process entity state update packet", e); + } + + return ProxyComputeResult.ofDrop(); + } + + if (packetId == YsmProtocolMetaFile.getS2CPacketId(FreesiaConstants.YsmProtocolMetaConstants.Clientbound.HAND_SHAKE_CONFIRMED)) { + final String backendVersion = mcBuffer.readUtf(); + final boolean canSwitchModel = mcBuffer.readBoolean(); + + Freesia.LOGGER.info("Replying ysm client with server version {}.Can switch model? : {}", backendVersion, canSwitchModel); + + return ProxyComputeResult.ofPass(); + } + + return ProxyComputeResult.ofPass(); + } + + @Override + public ProxyComputeResult processC2S(Key key, ByteBuf copiedPacketData) { + final FriendlyByteBuf mcBuffer = new FriendlyByteBuf(copiedPacketData); + final byte packetId = mcBuffer.readByte(); + + if (packetId == YsmProtocolMetaFile.getC2SPacketId(FreesiaConstants.YsmProtocolMetaConstants.Serverbound.HAND_SHAKE_REQUEST)) { + final ResultedEvent.GenericResult result = Freesia.PROXY_SERVER.getEventManager().fire(new PlayerYsmHandshakeEvent(this.player)).join().getResult(); + + if (!result.isAllowed()) { + return ProxyComputeResult.ofDrop(); + } + + final String clientYsmVersion = mcBuffer.readUtf(); + Freesia.LOGGER.info("Player {} is connected to the backend with ysm version {}", this.player.getUsername(), clientYsmVersion); + Freesia.mapperManager.onClientYsmHandshakePacketReply(this.player); + } + + return ProxyComputeResult.ofPass(); + } +} diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/VirtualYsmPacketProxyImpl.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/VirtualYsmPacketProxyImpl.java index 9caba3d..5361905 100644 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/VirtualYsmPacketProxyImpl.java +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/VirtualYsmPacketProxyImpl.java @@ -1,142 +1,30 @@ package meow.kikir.freesia.velocity.network.ysm; -import com.github.retrooper.packetevents.PacketEvents; -import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; -import com.github.retrooper.packetevents.protocol.player.ClientVersion; -import com.velocitypowered.api.proxy.Player; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import meow.kikir.freesia.velocity.Freesia; -import meow.kikir.freesia.velocity.network.mc.NbtRemapper; -import meow.kikir.freesia.velocity.network.mc.impl.StandardNbtRemapperImpl; -import meow.kikir.freesia.velocity.utils.FriendlyByteBuf; import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import java.util.Optional; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; -public class VirtualYsmPacketProxyImpl implements YsmPacketProxy { - private final UUID virtualPlayerUUID; - private final NbtRemapper nbtRemapper = new StandardNbtRemapperImpl(); - private volatile NBTCompound lastYsmEntityStatus = null; // TODO Need an access lock like DefaultYsmPacketProxyImpl? - private volatile int playerEntityId = -1; - +public class VirtualYsmPacketProxyImpl extends YsmPacketProxyLayer { public VirtualYsmPacketProxyImpl(UUID virtualPlayerUUID) { - this.virtualPlayerUUID = virtualPlayerUUID; + super(virtualPlayerUUID); } @Override - public ProxyComputeResult processS2C(Key channelKey, ByteBuf copiedPacketData) { - return null; + public CompletableFuture> fetchTrackerList(UUID observer) { + return Freesia.tracker.getCanSee(observer); } @Override - public ProxyComputeResult processC2S(Key channelKey, ByteBuf copiedPacketData) { + public ProxyComputeResult processS2C(Key channelKey, ByteBuf copiedPacketData) { return null; } @Override - public Player getOwner() { + public ProxyComputeResult processC2S(Key channelKey, ByteBuf copiedPacketData) { return null; } - - @Override - public void setEntityDataRaw(NBTCompound data) { - this.lastYsmEntityStatus = data; - } - - @Override - public void refreshToOthers() { - final NBTCompound entityStatus = this.lastYsmEntityStatus; // Copy the value - - // If the entity dose not have any data - if (entityStatus == null) { - return; - } - - Freesia.tracker.getCanSee(this.virtualPlayerUUID).whenComplete((beingWatched, exception) -> { // Async tracker check request to backend server - if (beingWatched != null) { // Actually there is impossible to be null - for (UUID targetUUID : beingWatched) { - final Optional targetNullable = Freesia.PROXY_SERVER.getPlayer(targetUUID); - - if (targetNullable.isPresent()) { // Skip send to NPCs - final Player target = targetNullable.get(); - - if (!Freesia.mapperManager.isPlayerInstalledYsm(target)) { // Skip if target is not ysm-installed - continue; - } - - this.sendEntityStateToInternal(target, entityStatus); // Sync to target - } - } - } - }); - } - - @Override - public NBTCompound getCurrentEntityState() { - return this.lastYsmEntityStatus; - } - - @Override - public void setPlayerWorkerEntityId(int id) { - // We are no mapper on the worker - } - - @Override - public void setPlayerEntityId(int id) { - final int oldValue = this.playerEntityId; - - this.playerEntityId = id; - - // State changed - if (oldValue != id) { - this.refreshToOthers(); - } - } - - @Override - public int getPlayerEntityId() { - return this.playerEntityId; - } - - @Override - public int getPlayerWorkerEntityId() { - return -1; // No worker entity id - } - - @Override - public void sendEntityStateTo(@NotNull Player target) { - this.sendEntityStateToInternal(target, this.lastYsmEntityStatus); - } - - private void sendEntityStateToInternal(Player target, NBTCompound entityStatus) { - final int currentEntityId = this.playerEntityId; // Get current entity id on the server of the player - - if (entityStatus == null || currentEntityId == -1) { // If no data got or player is not in the backend server currently - return; - } - - try { - final Object targetChannel = PacketEvents.getAPI().getProtocolManager().getChannel(target.getUniqueId()); // Get the netty channel of the player - - if (targetChannel == null) { - return; - } - - final ClientVersion clientVersion = PacketEvents.getAPI().getProtocolManager().getClientVersion(targetChannel); // Get the client version of the player - - final int targetProtocolVer = clientVersion.getProtocolVersion(); // Protocol version(Used for nbt remappers) - final FriendlyByteBuf wrappedPacketData = new FriendlyByteBuf(Unpooled.buffer()); - - wrappedPacketData.writeByte(4); - wrappedPacketData.writeVarInt(currentEntityId); - wrappedPacketData.writeBytes(this.nbtRemapper.shouldRemap(targetProtocolVer) ? this.nbtRemapper.remapToMasterVer(entityStatus) : this.nbtRemapper.remapToWorkerVer(entityStatus)); // Remap nbt if needed - - this.sendPluginMessageTo(target, YsmMapperPayloadManager.YSM_CHANNEL_KEY_VELOCITY, wrappedPacketData); - } catch (Exception e) { - Freesia.LOGGER.error("Error in encoding nbt or sending packet!", e); - } - } } diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmMapperPayloadManager.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmMapperPayloadManager.java index 8b4d916..af900c6 100644 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmMapperPayloadManager.java +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmMapperPayloadManager.java @@ -27,7 +27,6 @@ import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; @@ -50,7 +49,7 @@ public class YsmMapperPayloadManager { private final Function packetProxyCreator; // The players who installed ysm(Used for packet sending reduction) - private final Set ysmInstalledPlayers = Sets.newConcurrentHashSet(); + private final Set ysmInstalledPlayers = Sets.newConcurrentHashSet(); public YsmMapperPayloadManager(Function packetProxyCreator, Function packetProxyCreatorVirtual) { this.packetProxyCreator = packetProxyCreator; @@ -58,12 +57,8 @@ public YsmMapperPayloadManager(Function packetProxyCreat this.backend2Players.put(FreesiaConfig.workerMSessionAddress, 1); //TODO Load balance } - public void onClientYsmHandshakePacketReply(Player target) { - if (this.ysmInstalledPlayers.contains(target)) { - return; - } - - this.ysmInstalledPlayers.add(target); + public void onClientYsmHandshakePacketReply(@NotNull Player target) { + this.ysmInstalledPlayers.add(target.getUniqueId()); } public void updateWorkerPlayerEntityId(Player target, int entityId){ @@ -98,7 +93,7 @@ public CompletableFuture setVirtualPlayerEntityState(UUID playerUUID, N } virtualProxy.setEntityDataRaw(nbt); - virtualProxy.refreshToOthers(); + virtualProxy.notifyFullTrackerUpdates(); final NBTCompound entityData = virtualProxy.getCurrentEntityState(); @@ -222,38 +217,26 @@ public CompletableFuture removeVirtualPlayer(UUID playerUUID) { return callback; } - public void reconnectWorker(@NotNull Player master, @NotNull InetSocketAddress target) { - final MapperSessionProcessor currentMapper = this.mapperSessions.get(master); - - if (currentMapper == null) { - throw new IllegalStateException("Player is not connected to mapper!"); - } - - currentMapper.setKickMasterWhenDisconnect(false); - currentMapper.getSession().disconnect("RECONNECT"); - currentMapper.waitForDisconnected(); - - this.createMapperSession(master, target); + private void disconnectMapper(@NotNull MapperSessionProcessor connection) { + connection.setKickMasterWhenDisconnect(false); + connection.getSession().disconnect("RECONNECT"); + connection.waitForDisconnected(); } - public void reconnectWorker(@NotNull Player master) { - this.reconnectWorker(master, Objects.requireNonNull(this.selectLessPlayer())); - } - - public boolean hasPlayer(@NotNull Player player) { - return this.mapperSessions.containsKey(player); + public void autoCreateMapper(Player player) { + this.createMapperSession(player, Objects.requireNonNull(this.selectLessPlayer())); } - public void firstCreateMapper(Player player) { - this.createMapperSession(player, Objects.requireNonNull(this.selectLessPlayer())); + public boolean isPlayerInstalledYsm(@NotNull Player target) { + return this.ysmInstalledPlayers.contains(target.getUniqueId()); } - public boolean isPlayerInstalledYsm(Player target) { + public boolean isPlayerInstalledYsm(UUID target) { return this.ysmInstalledPlayers.contains(target); } - public void onPlayerDisconnect(Player player) { - this.ysmInstalledPlayers.remove(player); + public void onPlayerDisconnect(@NotNull Player player) { + this.ysmInstalledPlayers.remove(player.getUniqueId()); final MapperSessionProcessor mapperSession = this.mapperSessions.remove(player); @@ -299,6 +282,34 @@ public void onBackendReady(Player player) { mapperSession.onBackendReady(); } + public boolean disconnectAlreadyConnected(Player player) { + final MapperSessionProcessor current = this.mapperSessions.get(player); + + // Not exists or created + if (current == null) { + return false; + } + + // Will do remove in the callback + this.disconnectMapper(current); + return true; + } + + public void initMapperPacketProcessor(@NotNull Player player) { + final MapperSessionProcessor possiblyExisting = this.mapperSessions.get(player); + + if (possiblyExisting != null) { + throw new IllegalStateException("Mapper session already exists for player " + player.getUsername()); + } + + final YsmPacketProxy packetProxy = this.packetProxyCreator.apply(player); + final MapperSessionProcessor processor = new MapperSessionProcessor(player, packetProxy, this); + + packetProxy.setParentHandler(processor); + + this.mapperSessions.put(player, processor); + } + public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddress backend) { // Instance new session final TcpClientSession mapperSession = new TcpClientSession( @@ -313,7 +324,12 @@ public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddre ); // Our packet processor for packet forwarding - final MapperSessionProcessor packetProcessor = new MapperSessionProcessor(player, this.packetProxyCreator.apply(player), this); + final MapperSessionProcessor packetProcessor = this.mapperSessions.get(player); + + if (packetProcessor == null) { + // Should be created in ServerPreConnectEvent + throw new IllegalStateException("Mapper session not found or ready for player " + player.getUsername()); + } mapperSession.addListener(packetProcessor); @@ -321,17 +337,10 @@ public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddre mapperSession.setFlag(BuiltinFlags.READ_TIMEOUT,30_000); mapperSession.setFlag(BuiltinFlags.WRITE_TIMEOUT,30_000); - this.mapperSessions.put(player, packetProcessor); // Add to the mappers - // Do connect mapperSession.connect(true,false); } - @Deprecated - public void onProxyLoggedin(Player player, MapperSessionProcessor packetProcessor, TcpClientSession session){ - // TODO : Are we still using this callback ? - } - public void onVirtualPlayerTrackerUpdate(UUID owner, Player watcher) { final YsmPacketProxy virtualProxy = this.virtualProxies.get(owner); @@ -359,7 +368,10 @@ public void onRealPlayerTrackerUpdate(Player beingWatched, Player watcher) { } if (this.isPlayerInstalledYsm(watcher)) { // Skip players who don't install ysm - mapperSession.getPacketProxy().sendEntityStateTo(watcher); + // Check if ready + if (!mapperSession.queueTrackerUpdate(watcher.getUniqueId())) { + mapperSession.getPacketProxy().sendEntityStateTo(watcher); + } } } diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxy.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxy.java index d701e70..10e54fb 100644 --- a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxy.java +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxy.java @@ -6,20 +6,26 @@ import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public interface YsmPacketProxy { + default void setParentHandler(MapperSessionProcessor processor){ + // No-op by default + } + ProxyComputeResult processS2C(Key channelKey, ByteBuf copiedPacketData); ProxyComputeResult processC2S(Key channelKey, ByteBuf copiedPacketData); + @Nullable Player getOwner(); void sendEntityStateTo(@NotNull Player target); void setEntityDataRaw(NBTCompound data); - void refreshToOthers(); + void notifyFullTrackerUpdates(); NBTCompound getCurrentEntityState(); @@ -32,6 +38,12 @@ public interface YsmPacketProxy { int getPlayerWorkerEntityId(); default void sendPluginMessageToOwner(@NotNull MinecraftChannelIdentifier channel, byte[] data){ + final Player owner = this.getOwner(); + + if (owner == null) { + throw new UnsupportedOperationException(); + } + this.sendPluginMessageTo(this.getOwner(), channel, data); } diff --git a/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxyLayer.java b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxyLayer.java new file mode 100644 index 0000000..86ac8a6 --- /dev/null +++ b/Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmPacketProxyLayer.java @@ -0,0 +1,310 @@ +package meow.kikir.freesia.velocity.network.ysm; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.nbt.NBTCompound; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.velocitypowered.api.proxy.Player; +import io.netty.buffer.Unpooled; +import meow.kikir.freesia.velocity.Freesia; +import meow.kikir.freesia.velocity.network.mc.NbtRemapper; +import meow.kikir.freesia.velocity.network.mc.impl.StandardNbtRemapperImpl; +import meow.kikir.freesia.velocity.utils.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.VarHandle; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * The framework which is used for all ysm packet proxies
+ * Note:
+ *

+ * 1.This object is only can be used for once, any duplicated entity id and worker entity id updates will be ignored + * because it cannot and should not be updated multiple times as we just use them for per mappers

+ *

+ * 2.Any write operations of ysm entity data is ONLY single thread safe!!!

+ * + */ +public abstract class YsmPacketProxyLayer implements YsmPacketProxy{ + protected final Player player; + + protected final UUID playerUUID; + protected final NbtRemapper nbtRemapper = new StandardNbtRemapperImpl(); + + protected volatile MapperSessionProcessor handler; + + private int playerEntityId = -1; + private int workerEntityId = -1; + + private int entityDataReferenceCount = 0; // Used for read-writing lock but writing is always happening on a single thread + private NBTCompound lastYsmEntityData = null; + + private boolean proxyReady = false; + + protected static final VarHandle ENTITY_DATA_REF_COUNT_HANDLE = ConcurrentUtil.getVarHandle(YsmPacketProxyLayer.class, "entityDataReferenceCount", int.class); + protected static final VarHandle PROXY_READY_HANDLE = ConcurrentUtil.getVarHandle(YsmPacketProxyLayer.class, "proxyReady", boolean.class); + + protected static final VarHandle PLAYER_ENTITY_ID_HANDLE = ConcurrentUtil.getVarHandle(YsmPacketProxyLayer.class, "playerEntityId", int.class); + protected static final VarHandle WORKER_ENTITY_ID_HANDLE = ConcurrentUtil.getVarHandle(YsmPacketProxyLayer.class, "workerEntityId", int.class); + + protected static final VarHandle LAST_YSM_ENTITY_DATA_HANDLE = ConcurrentUtil.getVarHandle(YsmPacketProxyLayer.class, "lastYsmEntityData", NBTCompound.class); + + protected YsmPacketProxyLayer(UUID playerUUID) { + this.player = Freesia.PROXY_SERVER.getPlayer(playerUUID).orElse(null); // Get if it is a real player + this.playerUUID = playerUUID; + } + + protected YsmPacketProxyLayer(@NotNull Player player) { + this.player = player; + this.playerUUID = player.getUniqueId(); + } + + // Read and write locks for entity data, we just use them for very very short term operations so there is no need to + // worry the thread contention issue on performance + protected void releaseWriteReference() { + ENTITY_DATA_REF_COUNT_HANDLE.setVolatile(this, 0); // There is no any thread contention because the write locks are currently in our hands + } + + protected void acquireWriteReference() { + int failureCount = 0; + for (;;) { + for (int i = 0; i < failureCount; i++) { + ConcurrentUtil.backoff(); + } + + final int curr = (int) ENTITY_DATA_REF_COUNT_HANDLE.getVolatile(this); + + // Should not be happened because we are just calling entity data update in a single thread + if (curr == -1) { + throw new IllegalStateException("Write lock is already held by another thread!"); + } + + // Reading operations are not finished or another thread is acquiring write or read reference + if (!ENTITY_DATA_REF_COUNT_HANDLE.compareAndSet(this, curr, -1)) { + failureCount++; + continue; + } + + break; + } + } + + protected void releaseReadReference() { + int failureCount = 0; + for (;;) { + for (int i = 0; i < failureCount; i++) { + ConcurrentUtil.backoff(); + } + + final int curr = (int) ENTITY_DATA_REF_COUNT_HANDLE.getVolatile(this); + + if (curr == -1) { + throw new IllegalStateException("Cannot release read reference when write locked"); + } + + if (curr == 0) { + throw new IllegalStateException("Setting reference count down to a value lower than 0!"); + } + + // Another thread is acquiring read reference + if (!ENTITY_DATA_REF_COUNT_HANDLE.compareAndSet(this, curr, curr - 1)) { + failureCount++; + continue; + } + + break; + } + } + + protected void acquireReadReference() { + int failureCount = 0; + for (;;) { + for (int i = 0; i < failureCount; i++) { + ConcurrentUtil.backoff(); + } + + final int curr = (int) ENTITY_DATA_REF_COUNT_HANDLE.getVolatile(this); + + // Write locked + if (curr == -1) { + failureCount++; + continue; + } + + // Another thread is acquiring read or write reference + if (!ENTITY_DATA_REF_COUNT_HANDLE.compareAndSet(this, curr, curr + 1)) { + failureCount++; + continue; + } + + break; + } + } + + // Write and read locks end + + @Nullable + @Override + public Player getOwner() { + return this.player; + } + + protected boolean isEntityStateOfSelf(int entityId){ + final int currentWorkerEntityId = (int) WORKER_ENTITY_ID_HANDLE.getVolatile(this); + + if (currentWorkerEntityId == -1) { + return false; + } + + return currentWorkerEntityId == entityId; + } + + @Override + public void setParentHandler(MapperSessionProcessor handler) { + this.handler = handler; + } + + @Override + public void sendEntityStateTo(@NotNull Player target) { + this.acquireReadReference(); // Acquire read reference + + final int currEntityId = (int) PLAYER_ENTITY_ID_HANDLE.getVolatile(this); + final NBTCompound currEntityData = (NBTCompound) LAST_YSM_ENTITY_DATA_HANDLE.getVolatile(this); + + this.releaseReadReference(); // Release when we copied the value + + // Not fully initialized yet + if (currEntityId == -1 || currEntityData == null) { + return; + } + + this.sendEntityStateToRaw(target.getUniqueId(), currEntityId, currEntityData); + } + + @Override + public void setEntityDataRaw(NBTCompound data) { + LAST_YSM_ENTITY_DATA_HANDLE.setVolatile(this, data); + } + + @Override + public void notifyFullTrackerUpdates() { + this.acquireReadReference(); // Acquire read reference + + final NBTCompound currEntityData = (NBTCompound) LAST_YSM_ENTITY_DATA_HANDLE.getVolatile(this); + final int currEntityId = (int) PLAYER_ENTITY_ID_HANDLE.getVolatile(this); + + this.releaseReadReference(); // Release when we copied the value + + // Not fully initialized yet + if (currEntityId == -1 || currEntityData == null) { + return; + } + + // Prevent race condition + if (PROXY_READY_HANDLE.compareAndSet(this, false, true)) { + // If we have the mapper connection + if (this.handler != null) { + // Done queued tracker updates + this.handler.retireTrackerCallbacks(); + } + } + + // Sync to the owner self + this.sendEntityStateToRaw(this.playerUUID, currEntityId, currEntityData); + + // Fetch can-see list + this.fetchTrackerList(this.playerUUID).whenComplete((result, ex) -> { + // Exception processing + if (ex != null) { + Freesia.LOGGER.warn("Failed to fetch tracker list for player uuid {}: {}", this.player != null ? this.player.getUniqueId(): this.playerUUID, ex); + return; + } + + for (UUID toSend : result) { + final Optional queryResult = Freesia.PROXY_SERVER.getPlayer(toSend); + + // If it is a real player + if (queryResult.isPresent()) { + // Check ysm installed + if (Freesia.mapperManager.isPlayerInstalledYsm(toSend)) { + this.sendEntityStateToRaw(toSend, currEntityId, currEntityData); + } + } + } + }); + } + + public abstract CompletableFuture> fetchTrackerList(UUID observer); + + protected void sendEntityStateToRaw(@NotNull UUID receiverUUID, int entityId, NBTCompound data) { + try { + final Optional queryResult = Freesia.PROXY_SERVER.getPlayer(receiverUUID); + + if (queryResult.isEmpty()) { + return; + } + + final Player receiver = queryResult.get(); + final Object targetChannel = PacketEvents.getAPI().getProtocolManager().getChannel(receiver.getUniqueId()); // Get the netty channel of the player + + if (targetChannel == null) { + return; + } + + final ClientVersion clientVersion = PacketEvents.getAPI().getProtocolManager().getClientVersion(targetChannel); // Get the client version of the player + + final int targetProtocolVer = clientVersion.getProtocolVersion(); // Protocol version(Used for nbt remappers) + final FriendlyByteBuf wrappedPacketData = new FriendlyByteBuf(Unpooled.buffer()); + + wrappedPacketData.writeByte(4); + wrappedPacketData.writeVarInt(entityId); + wrappedPacketData.writeBytes(this.nbtRemapper.shouldRemap(targetProtocolVer) ? + this.nbtRemapper.remapToMasterVer(data) : + this.nbtRemapper.remapToWorkerVer(data) + ); // Remap nbt if needed + + this.sendPluginMessageTo(receiver, YsmMapperPayloadManager.YSM_CHANNEL_KEY_VELOCITY, wrappedPacketData); + } catch (Exception e) { + Freesia.LOGGER.error("Error in encoding nbt or sending packet!", e); + } + } + + @Override + public NBTCompound getCurrentEntityState() { + return (NBTCompound) LAST_YSM_ENTITY_DATA_HANDLE.getVolatile(this); + } + + @Override + public void setPlayerWorkerEntityId(int id){ + // Only update for once + final boolean successfullyUpdated = WORKER_ENTITY_ID_HANDLE.compareAndSet(this, -1, id); + + if (successfullyUpdated) { + this.notifyFullTrackerUpdates(); // If it is the first update + } + } + + @Override + public void setPlayerEntityId(int id) { + // Only update for once + final boolean successfullyUpdated = PLAYER_ENTITY_ID_HANDLE.compareAndSet(this, -1, id); + + if (successfullyUpdated) { + this.notifyFullTrackerUpdates(); // If it is the first update + } + } + + @Override + public int getPlayerEntityId() { + return (int) PLAYER_ENTITY_ID_HANDLE.getVolatile(this); + } + + @Override + public int getPlayerWorkerEntityId() { + return (int) WORKER_ENTITY_ID_HANDLE.getVolatile(this); + } +} diff --git a/Freesia-Velocity/src/main/templates/moew/kikir/freesia/velocity/BuildConstants.java b/Freesia-Velocity/src/main/templates/meow/kikir/freesia/velocity/BuildConstants.java similarity index 78% rename from Freesia-Velocity/src/main/templates/moew/kikir/freesia/velocity/BuildConstants.java rename to Freesia-Velocity/src/main/templates/meow/kikir/freesia/velocity/BuildConstants.java index 78369a2..d97b148 100644 --- a/Freesia-Velocity/src/main/templates/moew/kikir/freesia/velocity/BuildConstants.java +++ b/Freesia-Velocity/src/main/templates/meow/kikir/freesia/velocity/BuildConstants.java @@ -1,4 +1,4 @@ -package moew.kikir.freesia.velocity; +package meow.kikir.freesia.velocity; // The constants are replaced before compilation public class BuildConstants {