Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 85 additions & 87 deletions src/main/java/pw/kaboom/extras/modules/player/PlayerMessaging.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.messaging.Messenger;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NonNull;
import pw.kaboom.extras.Main;

import java.io.*;
Expand All @@ -33,7 +33,7 @@ public PlayerMessaging(final Main plugin) {
this.plugin = plugin;
}

private final Map<String, Set<Player>> listening = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Map<Player, Short>> listening = new HashMap<>();

private static String readString(final DataInput dataInput) throws IOException {
final byte[] buf = new byte[255];
Expand All @@ -51,100 +51,104 @@ private static String readString(final DataInput dataInput) throws IOException {
return new String(buf, 0, idx, StandardCharsets.US_ASCII);
}

private void handleRegister(final Player player, final DataInput input) throws IOException {
this.listening.compute(readString(input), (k, v) -> {
v = v == null ?
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap<>()
)
)
:
v;
v.add(player);
return v;
});
private void handleRegister(final Player player,
final DataInputStream input) throws IOException {
final var name = readString(input);
// 2 bytes = short
Short limit = input.available() >= 2 ? (short) Math.max(0, input.readShort()) : null;
if (Objects.equals(limit, Short.MAX_VALUE)) limit = null;

this.listening.computeIfAbsent(
name,
_ -> new IdentityHashMap<>()
).put(player, limit);
}

private void handleUnregister(final Player player,
final DataInput input) throws IOException {
this.listening.computeIfPresent(readString(input), (k, v) -> {
this.listening.computeIfPresent(readString(input), (_, v) -> {
v.remove(player);
return v;
return v.isEmpty() ? null : v;
});
}

private void handleMessage(final Player player,
final DataInputStream input)
throws IOException {
final String channelName = readString(input);
final Set<Player> players = this.listening.get(channelName);
if (players == null) return;
final Map<Player, Short> playerToLimit = this.listening.get(channelName);
if (playerToLimit == null) return;
final Set<Map.Entry<Player, Short>> players = playerToLimit.entrySet();

synchronized (players) {
// we initialize as null so that we do not read the incoming
// data and serialize the payload if the only recipient
// we initialize as null so that we do not read the incoming
// data and serialize the payload if the only recipient
// would be the sender, who we do not send to
byte[] msg = null;

for (final Player playerInSet : players) {
if (playerInSet == player) continue;
if (msg == null) {
final int remaining = input.available();

// remaining count + channel name + uuid
// note: calls to channelName.length() are safe because we only read ASCII
final int realLength = remaining + channelName.length() + 16;
if (realLength > Messenger.MAX_MESSAGE_SIZE) {
player.sendMessage(ERROR);
return;
}

msg = new byte[realLength];
int offset = 0;

final byte[] nameBytes = channelName.getBytes(StandardCharsets.US_ASCII);
nameBytes[nameBytes.length - 1] |= END_CHAR_MASK;
System.arraycopy(
nameBytes,
0,
msg,
offset,
channelName.length()
);
offset += channelName.length();

final UUID uuid = player.getUniqueId();
System.arraycopy(
Longs.toByteArray(uuid.getMostSignificantBits()),
0,
msg,
offset,
8)
;
offset += 8;

System.arraycopy(
Longs.toByteArray(uuid.getLeastSignificantBits()),
0,
msg,
offset,
8
);
offset += 8;

input.readFully(msg, offset, remaining);
byte[] msg = null;
int len = -1;

for (final Map.Entry<Player, Short> entry: players) {
final Player playerInSet = entry.getKey();
if (playerInSet == player) continue;
if (msg == null) {
final int remaining = input.available();
len = remaining;

// remaining count + channel name + uuid
// note: calls to channelName.length() are safe because we only read ASCII
final int realLength = remaining + channelName.length() + 16;
if (realLength > Messenger.MAX_MESSAGE_SIZE) {
player.sendMessage(ERROR);
return;
}

playerInSet.sendPluginMessage(this.plugin, MESSAGE, msg);
msg = new byte[realLength];
int offset = 0;

final byte[] nameBytes = channelName.getBytes(StandardCharsets.US_ASCII);
nameBytes[nameBytes.length - 1] |= END_CHAR_MASK;

System.arraycopy(
nameBytes,
0,
msg,
offset,
channelName.length()
);
offset += channelName.length();

final UUID uuid = player.getUniqueId();
System.arraycopy(
Longs.toByteArray(uuid.getMostSignificantBits()),
0,
msg,
offset,
8
);
offset += 8;

System.arraycopy(
Longs.toByteArray(uuid.getLeastSignificantBits()),
0,
msg,
offset,
8
);
offset += 8;

input.readFully(msg, offset, remaining);
}

final Short limit = entry.getValue();
// does the message exceed this listener's limit?
if (limit != null && len > limit) continue;
playerInSet.sendPluginMessage(this.plugin, MESSAGE, msg);
}
}

@Override
public void onPluginMessageReceived(final @NotNull String channelName,
final @NotNull Player player,
final byte[] bytes) {
public void onPluginMessageReceived(final @NonNull String channelName,
final @NonNull Player player,
final byte @NonNull [] bytes) {
try {
switch (channelName) {
case REGISTER -> handleRegister(
Expand All @@ -169,19 +173,13 @@ public void onPluginMessageReceived(final @NotNull String channelName,
public void onPlayerQuit(PlayerQuitEvent event) {
final Player removedPlayer = event.getPlayer();

synchronized (this.listening) {
final Iterator<Map.Entry<String, Set<Player>>> listeningIterator =
this.listening.entrySet().iterator();

while (listeningIterator.hasNext()) {
final Map.Entry<String, Set<Player>> entry = listeningIterator.next();
final Set<Player> players = entry.getValue();
synchronized (players) {
players.remove(removedPlayer);
final var iterator = this.listening.entrySet().iterator();

if (players.isEmpty()) listeningIterator.remove();
}
}
while (iterator.hasNext()) {
final var entry = iterator.next();
final var listeners = entry.getValue();
if (listeners.remove(removedPlayer) == null || !listeners.isEmpty()) continue;
iterator.remove();
}
}
}