Skip to content

Commit 8084694

Browse files
Merge pull request #16 from Lora4967/patch-1
2 parents 6a5b467 + c81c4d3 commit 8084694

11 files changed

Lines changed: 154 additions & 19 deletions

File tree

Freesia-Backend/src/main/java/meow/kikir/freesia/backend/FreesiaBackend.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ public final class FreesiaBackend extends JavaPlugin {
1414
@Override
1515
public void onEnable() {
1616
INSTANCE = this;
17+
18+
// TODO- De-hard-coding?
1719
Bukkit.getMessenger().registerIncomingPluginChannel(this, "freesia:tracker_sync", this.trackerProcessor);
1820
Bukkit.getMessenger().registerOutgoingPluginChannel(this, "freesia:tracker_sync");
21+
22+
// TODO- De-hard-coding?
1923
Bukkit.getMessenger().registerIncomingPluginChannel(this, "freesia:virtual_player_management", this.virtualPlayerManager);
2024
Bukkit.getMessenger().registerOutgoingPluginChannel(this, "freesia:virtual_player_management");
2125

26+
2227
Bukkit.getPluginManager().registerEvents(this.trackerProcessor, this);
2328
}
2429

Freesia-Backend/src/main/java/meow/kikir/freesia/backend/tracker/TrackerProcessor.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ public void onPlayerAddedToWorld(@NotNull EntityAddToWorldEvent event) {
4141
}
4242
}
4343

44-
4544
private void playerTrackedPlayer(@NotNull Player beSeen, @NotNull Player seeing) {
45+
// Fire tracker update events
4646
if (!new CyanidinRealPlayerTrackerUpdateEvent(seeing, beSeen).callEvent()) {
4747
return;
4848
}
4949

50+
// The true tracker update caller
5051
this.notifyTrackerUpdate(seeing.getUniqueId(), beSeen.getUniqueId());
5152
}
5253

@@ -57,6 +58,7 @@ public void notifyTrackerUpdate(UUID watcher, UUID beWatched) {
5758
wrappedUpdatePacket.writeUUID(beWatched);
5859
wrappedUpdatePacket.writeUUID(watcher);
5960

61+
// Find a payload
6062
final Player payload = Utils.randomPlayerIfNotFound(watcher);
6163

6264
if (payload == null) {
@@ -90,6 +92,7 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player sen
9092

9193
final CyanidinTrackerScanEvent trackerScanEvent = new CyanidinTrackerScanEvent(result, toScan);
9294

95+
// We need to schedule back to pass the dumb async catchers as it was firing from both netty threads and main threads
9396
sender.getScheduler().execute(
9497
FreesiaBackend.INSTANCE,
9598
() -> {

Freesia-Backend/src/main/java/meow/kikir/freesia/backend/utils/FriendlyByteBuf.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
import java.nio.charset.StandardCharsets;
2020
import java.util.UUID;
2121

22+
/**
23+
* A simplified FriendlyByteBuf reimplementation
24+
* Taken from minecraft-stress-test(<a href="https://github.com/PureGero/minecraft-stress-test">...</a>)
25+
*/
2226
public class FriendlyByteBuf extends ByteBuf {
2327
private final ByteBuf source;
2428

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package meow.kikir.freesia.common.utils;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import java.lang.invoke.MethodHandles;
6+
import java.lang.invoke.VarHandle;
7+
import java.util.concurrent.DelayQueue;
8+
import java.util.concurrent.Delayed;
9+
import java.util.concurrent.TimeUnit;
10+
11+
public abstract class TimeExpiringCallbacker implements Delayed {
12+
private static final DelayQueue<TimeExpiringCallbacker> QUEUE = new DelayQueue<>();
13+
public static final Object NIL = new Object();
14+
15+
private static final VarHandle RESULT_HANDLE;
16+
17+
static {
18+
try {
19+
RESULT_HANDLE = MethodHandles.lookup().findVarHandle(
20+
TimeExpiringCallbacker.class,
21+
"result",
22+
Object.class
23+
);
24+
} catch (NoSuchFieldException | IllegalAccessException e) {
25+
throw new RuntimeException(e);
26+
}
27+
28+
Thread checkerThread = new Thread(() -> {
29+
while (!Thread.currentThread().isInterrupted()) {
30+
try {
31+
TimeExpiringCallbacker callbacker = QUEUE.take();
32+
callbacker.handleTimeout();
33+
} catch (InterruptedException e) {
34+
Thread.currentThread().interrupt();
35+
break;
36+
} catch (Exception e) {
37+
e.printStackTrace();
38+
}
39+
}
40+
}, "Freesia-TimeExpiringCallbacker-Checker");
41+
42+
checkerThread.setDaemon(true);
43+
checkerThread.setPriority(Thread.NORM_PRIORITY - 2);
44+
checkerThread.start();
45+
}
46+
47+
private final long expirationDeadline;
48+
private Object result = null;
49+
50+
public TimeExpiringCallbacker(long timeoutMs) {
51+
this.expirationDeadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
52+
QUEUE.add(this);
53+
}
54+
55+
public abstract void onFinished(Object result);
56+
57+
private void handleTimeout() {
58+
if (RESULT_HANDLE.compareAndSet(this, null, NIL)) {
59+
this.onFinished(NIL);
60+
}
61+
}
62+
63+
public void done(Object result) {
64+
if (RESULT_HANDLE.compareAndSet(this, null, result)) {
65+
66+
QUEUE.remove(this);
67+
68+
this.onFinished(result);
69+
}
70+
}
71+
72+
@Override
73+
public long getDelay(@NotNull TimeUnit unit) {
74+
return unit.convert(expirationDeadline - System.nanoTime(), TimeUnit.NANOSECONDS);
75+
}
76+
77+
@Override
78+
public int compareTo(@NotNull Delayed input) {
79+
return Long.compare(this.expirationDeadline, ((TimeExpiringCallbacker) input).expirationDeadline);
80+
}
81+
}

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/Freesia.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,21 @@ public EventTask onPlayerConnected(@NotNull ServerConnectedEvent event) {
139139
final Player targetPlayer = event.getPlayer();
140140

141141
return EventTask.async(() -> {
142+
// On first connect
142143
if (!mapperManager.hasPlayer(targetPlayer)) {
143144
this.logger.info("Initiating mapper session for player {}", targetPlayer.getUsername());
144145

146+
// Create mapper session
145147
mapperManager.firstCreateMapper(targetPlayer);
146-
kickChecker.onPlayerJoin(targetPlayer);
147148

149+
// Add to client kicker
150+
kickChecker.onPlayerJoin(targetPlayer);
148151
return;
149152
}
150153

151-
logger.info("Player {} has changed backend server.Reconnecting mapper session", targetPlayer.getUsername());
154+
// Player might switch its current server
155+
logger.info("Player {} has changed backend server. Reconnecting mapper session", targetPlayer.getUsername());
156+
// So, reconnect mapper session
152157
mapperManager.reconnectWorker(targetPlayer);
153158
});
154159
}
@@ -166,6 +171,7 @@ public void onChannelMsg(@NotNull PluginMessageEvent event) {
166171
if ((identifier instanceof MinecraftChannelIdentifier mineId) && (event.getSource() instanceof Player player)) {
167172
event.setResult(PluginMessageEvent.ForwardResult.handled());
168173

174+
// TODO Need a packet rate limiter here?
169175
mapperManager.onPluginMessageIn(player, mineId, data);
170176
}
171177
}

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/events/PlayerEntityStateChangeEvent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package meow.kikir.freesia.velocity.events;
22

33
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
4+
import com.google.common.annotations.Beta;
45
import com.velocitypowered.api.proxy.Player;
56

67
/**
78
* 当玩家更改模型时或worker设置玩家时该事件会被触发
89
* 获取到的Nbt是要发送给玩家的
910
* 注意:修改过后的nbt并不会被持久化即只会在当前进程发生作用而在重启后失效
1011
*/
12+
@Beta
1113
public class PlayerEntityStateChangeEvent {
1214
private final Player actualPlayer;
1315
private final int entityId;

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/DefaultYsmPacketProxyImpl.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020

2121
import java.util.Optional;
2222
import java.util.UUID;
23-
import java.util.concurrent.locks.Lock;
24-
import java.util.concurrent.locks.ReentrantLock;
23+
import java.util.concurrent.locks.StampedLock;
2524

2625
public class DefaultYsmPacketProxyImpl implements YsmPacketProxy{
2726
private final Player player;
2827
private final NbtRemapper nbtRemapper = new StandardNbtRemapperImpl();
2928

3029
private volatile NBTCompound lastYsmEntityStatus = null;
31-
private final Lock entityStatusWriteLock = new ReentrantLock(true); // We need to keep its order
30+
private final StampedLock entityStatusWriteLock = new StampedLock(); // Use optimistic locks
3231

3332
private volatile int playerEntityId = -1;
3433
private volatile int workerPlayerEntityId = -1;
@@ -117,17 +116,31 @@ private void sendEntityStateToInternal(Player target, NBTCompound entityStatus)
117116

118117
@Override
119118
public void setEntityDataRaw(NBTCompound data) {
120-
this.entityStatusWriteLock.lock();
119+
final long stamp = this.entityStatusWriteLock.writeLock();
121120
try {
122121
this.lastYsmEntityStatus = data;
123122
}finally {
124-
this.entityStatusWriteLock.unlock();
123+
this.entityStatusWriteLock.unlockWrite(stamp);
125124
}
126125
}
127126

128127
@Override
129128
public void refreshToOthers() {
130-
final NBTCompound entityStatusCopy = this.lastYsmEntityStatus; // Copy value
129+
NBTCompound entityStatusCopy; // Copy value
130+
131+
// Try optimistic read first
132+
long stamp = this.entityStatusWriteLock.tryOptimisticRead();
133+
if (this.entityStatusWriteLock.validate(stamp)) {
134+
entityStatusCopy = this.lastYsmEntityStatus;
135+
}else {
136+
// Fallback to read lock
137+
try {
138+
stamp = this.entityStatusWriteLock.readLock();
139+
entityStatusCopy = this.lastYsmEntityStatus;
140+
}finally {
141+
this.entityStatusWriteLock.unlockRead(stamp);
142+
}
143+
}
131144

132145
// If the player does not have any data
133146
if (entityStatusCopy == null) {
@@ -179,18 +192,17 @@ public ProxyComputeResult processS2C(Key key, ByteBuf copiedPacketData) {
179192
return ProxyComputeResult.ofDrop(); // Do not process the entity state if it is not ours
180193
}
181194

182-
// We process this actions async
183-
// TODO : Is here any race condition ?
184-
Freesia.PROXY_SERVER.getEventManager().fire(new PlayerEntityStateChangeEvent(this.player,workerEntityId, this.nbtRemapper.readBound(mcBuffer))).thenAccept(result -> {
185-
this.entityStatusWriteLock.lock();
195+
Freesia.PROXY_SERVER.getEventManager().fire(new PlayerEntityStateChangeEvent(this.player,workerEntityId, this.nbtRemapper.readBound(mcBuffer))).thenAccept(result -> { // Use NbtRemapper for multi version clients
196+
// Acquire write lock first
197+
final long stamp = this.entityStatusWriteLock.writeLock();
186198
try {
187-
this.lastYsmEntityStatus = result.getEntityState(); // Read using the protocol version matched for the worker
199+
this.lastYsmEntityStatus = result.getEntityState(); // Update value to the result
188200
}finally {
189-
this.entityStatusWriteLock.unlock();
201+
this.entityStatusWriteLock.unlockWrite(stamp);
190202
}
191203

192204
this.refreshToOthers();
193-
});
205+
}).join(); // Force blocking as we do not wanna break the sequence of the data
194206
} catch (Exception e) {
195207
Freesia.LOGGER.error("Error while in processing tracker!", e);
196208
return ProxyComputeResult.ofDrop();

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/MapperSessionProcessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,19 @@ public void disconnecting(DisconnectingEvent event) {
154154
@Override
155155
public void disconnected(DisconnectedEvent event) {
156156
Freesia.LOGGER.info("Mapper session has disconnected for reason(non-deserialized): {}", event.getReason()); // Log disconnected
157+
158+
// Log exceptions
157159
if (event.getCause() != null) {
158160
Freesia.LOGGER.info("Mapper session has disconnected for throwable: {}", event.getCause().getLocalizedMessage()); // Log errors
159161
}
162+
163+
// Remove callback
160164
this.mapperPayloadManager.onWorkerSessionDisconnect(this, this.kickMasterWhenDisconnect, event.getReason()); // Fire events
161165
this.session = null; //Set session to null to finalize the mapper connection
162166
}
163167

164168
public void waitForDisconnected() {
169+
// We will set the session to null after finishing all disconnect logics
165170
while (this.session != null) {
166171
Thread.onSpinWait(); // Spin wait instead of block waiting
167172
}

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/VirtualYsmPacketProxyImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
public class VirtualYsmPacketProxyImpl implements YsmPacketProxy {
2020
private final UUID virtualPlayerUUID;
2121
private final NbtRemapper nbtRemapper = new StandardNbtRemapperImpl();
22-
private volatile NBTCompound lastYsmEntityStatus = null;
22+
private volatile NBTCompound lastYsmEntityStatus = null; // TODO Need an access lock like DefaultYsmPacketProxyImpl?
2323
private volatile int playerEntityId = -1;
2424

2525
public VirtualYsmPacketProxyImpl(UUID virtualPlayerUUID) {

Freesia-Velocity/src/main/java/meow/kikir/freesia/velocity/network/ysm/YsmMapperPayloadManager.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,19 +265,23 @@ public void onPlayerDisconnect(Player player) {
265265
}
266266

267267
protected void onWorkerSessionDisconnect(@NotNull MapperSessionProcessor mapperSession, boolean kickMaster, Component reason) {
268+
// Kick the master it binds
268269
if (kickMaster)
269270
mapperSession.getBindPlayer().disconnect(Freesia.languageManager.i18n(FreesiaConstants.LanguageConstants.WORKER_TERMINATED_CONNECTION, List.of("reason"), List.of(reason)));
271+
// Remove from list
270272
this.mapperSessions.remove(mapperSession.getBindPlayer());
271273
}
272274

273275
public void onPluginMessageIn(@NotNull Player player, @NotNull MinecraftChannelIdentifier channel, byte[] packetData) {
276+
// Check if it is the message of ysm
274277
if (!channel.equals(YSM_CHANNEL_KEY_VELOCITY)) {
275278
return;
276279
}
277280

278281
final MapperSessionProcessor mapperSession = this.mapperSessions.get(player);
279282

280283
if (mapperSession == null) {
284+
// Actually it shouldn't be and never be happened
281285
throw new IllegalStateException("Mapper session not found or ready for player " + player.getUsername());
282286
}
283287

@@ -296,6 +300,7 @@ public void onBackendReady(Player player) {
296300
}
297301

298302
public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddress backend) {
303+
// Instance new session
299304
final TcpClientSession mapperSession = new TcpClientSession(
300305
backend.getHostName(),
301306
backend.getPort(),
@@ -307,10 +312,12 @@ public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddre
307312
)
308313
);
309314

315+
// Our packet processor for packet forwarding
310316
final MapperSessionProcessor packetProcessor = new MapperSessionProcessor(player, this.packetProxyCreator.apply(player), this);
311317

312318
mapperSession.addListener(packetProcessor);
313319

320+
// Default as Minecraft client
314321
mapperSession.setFlag(BuiltinFlags.READ_TIMEOUT,30_000);
315322
mapperSession.setFlag(BuiltinFlags.WRITE_TIMEOUT,30_000);
316323

@@ -320,6 +327,7 @@ public void createMapperSession(@NotNull Player player, @NotNull InetSocketAddre
320327
mapperSession.connect(true,false);
321328
}
322329

330+
@Deprecated
323331
public void onProxyLoggedin(Player player, MapperSessionProcessor packetProcessor, TcpClientSession session){
324332
// TODO : Are we still using this callback ?
325333
}
@@ -344,10 +352,13 @@ public void onRealPlayerTrackerUpdate(Player beingWatched, Player watcher) {
344352
// so as the result, we could simply pass it down directly
345353
if (mapperSession == null) {
346354
// Should not be happened
347-
throw new IllegalStateException("???");
355+
// We use random player as the payload of custom payload of freesia tracker, so there is a possibility
356+
// that race condition would happen between the disconnect logic and tracker update logic
357+
//throw new IllegalStateException("???");
358+
return;
348359
}
349360

350-
if (this.isPlayerInstalledYsm(watcher)) {
361+
if (this.isPlayerInstalledYsm(watcher)) { // Skip players who don't install ysm
351362
mapperSession.getPacketProxy().sendEntityStateTo(watcher);
352363
}
353364
}

0 commit comments

Comments
 (0)