From f8e100f0356a7d841d30a9e72723f028fac4715c Mon Sep 17 00:00:00 2001 From: Josscoder Date: Mon, 21 Jul 2025 11:47:31 -0500 Subject: [PATCH 1/2] impl communication --- core/pom.xml | 12 +++ .../redisbridge/core/RedisBridgeCore.java | 76 +++++++++++++------ .../redisbridge/core/data/InstanceInfo.java | 20 ----- .../core/instance/InstanceInfo.java | 21 +++++ .../InstanceManager.java | 3 +- .../redisbridge/core/message/MessageBase.java | 11 +++ .../core/message/MessageHandler.java | 5 ++ .../core/message/MessageHandlerRegistry.java | 18 +++++ .../core/message/MessageRegistry.java | 17 +++++ .../defaults/InstanceHeartbeatMessage.java | 19 +++++ .../defaults/InstanceShutdownMessage.java | 12 +++ .../redisbridge/core/utils/JsonUtils.java | 35 +++++++++ .../redisbridge/nukkit/RedisBridgePlugin.java | 31 +++++--- .../nukkit/command/LobbyCommand.java | 4 +- .../nukkit/command/TransferCommand.java | 6 +- .../waterdogpe/RedisBridgePlugin.java | 1 + .../waterdogpe/event/ProxyEvents.java | 2 +- .../waterdogpe/handler/LobbyJoinHandler.java | 4 +- .../handler/ReconnectLobbyHandler.java | 4 +- .../task/InstanceRegistrationTask.java | 4 +- .../redisbridge/waterdogpe/utils/Utils.java | 2 +- 21 files changed, 238 insertions(+), 69 deletions(-) delete mode 100644 core/src/main/java/net/josscoder/redisbridge/core/data/InstanceInfo.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceInfo.java rename core/src/main/java/net/josscoder/redisbridge/core/{manager => instance}/InstanceManager.java (96%) create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/MessageBase.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandler.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandlerRegistry.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/MessageRegistry.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceHeartbeatMessage.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceShutdownMessage.java create mode 100644 core/src/main/java/net/josscoder/redisbridge/core/utils/JsonUtils.java diff --git a/core/pom.xml b/core/pom.xml index a8d8a24..3d13a0c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -31,6 +31,18 @@ 33.4.8-jre compile + + com.fasterxml.jackson.core + jackson-core + 2.19.2 + compile + + + com.fasterxml.jackson.core + jackson-databind + 2.17.2 + compile + \ No newline at end of file diff --git a/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java b/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java index ccc5a40..12b591c 100644 --- a/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java +++ b/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java @@ -1,10 +1,15 @@ package net.josscoder.redisbridge.core; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import net.josscoder.redisbridge.core.data.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; +import net.josscoder.redisbridge.core.message.MessageBase; +import net.josscoder.redisbridge.core.message.MessageHandler; +import net.josscoder.redisbridge.core.message.MessageHandlerRegistry; +import net.josscoder.redisbridge.core.message.MessageRegistry; import net.josscoder.redisbridge.core.logger.ILogger; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.message.defaults.InstanceHeartbeatMessage; +import net.josscoder.redisbridge.core.message.defaults.InstanceShutdownMessage; +import net.josscoder.redisbridge.core.utils.JsonUtils; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; @@ -12,9 +17,7 @@ public class RedisBridgeCore { - private static final Gson GSON = new GsonBuilder().create(); - public static final String INSTANCE_HEARTBEAT_CHANNEL = "instance_heartbeat_channel"; - public static final String INSTANCE_REMOVE_CHANNEL = "instance_removed_channel"; + public static final String CHANNEL = "redis-bridge-channel"; private JedisPool jedisPool = null; private Thread listenerThread; @@ -37,16 +40,30 @@ public void connect(String host, int port, String password, ILogger logger) { try (Jedis jedis = jedisPool.getResource()) { jedis.subscribe(new JedisPubSub() { @Override - public void onMessage(String channel, String message) { - if (channel.equals(INSTANCE_HEARTBEAT_CHANNEL)) { - InstanceInfo data = GSON.fromJson(message, InstanceInfo.class); - InstanceManager.INSTANCE_CACHE.put(data.getId(), data); - } else if (channel.equals(INSTANCE_REMOVE_CHANNEL)) { - InstanceInfo data = GSON.fromJson(message, InstanceInfo.class); - InstanceManager.INSTANCE_CACHE.invalidate(data.getId()); + public void onMessage(String channel, String messageJson) { + try { + String type = JsonUtils.extractType(messageJson); + + Class clazz = MessageRegistry.getClass(type); + if (clazz == null) { + logger.debug("Unregistered message type: " + type); + + return; + } + + MessageBase message = JsonUtils.fromJson(messageJson, clazz); + + MessageHandler handler = MessageHandlerRegistry.getHandler(type); + if (handler != null) { + handler.handle(message); + } else { + logger.debug("No handler found for message type: " + type); + } + } catch (Exception e) { + logger.error("Error handling message", e); } } - }, INSTANCE_REMOVE_CHANNEL, INSTANCE_HEARTBEAT_CHANNEL); + }, CHANNEL); } catch (Exception e) { logger.error("RedisBridge encountered an error, will retry in 1 second", e); try { @@ -62,18 +79,33 @@ public void onMessage(String channel, String message) { listenerThread.start(); } - public void publish(String message, String channel) { + public void publish(MessageBase message, String sender) { try (Jedis jedis = jedisPool.getResource()) { - jedis.publish(channel, message); + message.setTimestamp(System.currentTimeMillis()); + message.setSender(sender); + + String json = JsonUtils.toJson(message); + jedis.publish(CHANNEL, json); } } - public void publishInstanceInfo(InstanceInfo info) { - publish(GSON.toJson(info), INSTANCE_HEARTBEAT_CHANNEL); - } + public void registerDefaultMessages() { + MessageRegistry.register(InstanceHeartbeatMessage.TYPE, InstanceHeartbeatMessage.class); + MessageHandlerRegistry.register(InstanceHeartbeatMessage.TYPE, new MessageHandler() { + @Override + public void handle(InstanceHeartbeatMessage message) { + InstanceInfo instance = message.getInstance(); + InstanceManager.INSTANCE_CACHE.put(instance.getId(), instance); + } + }); - public void publishInstanceRemove(InstanceInfo info) { - publish(GSON.toJson(info), INSTANCE_REMOVE_CHANNEL); + MessageRegistry.register(InstanceShutdownMessage.TYPE, InstanceShutdownMessage.class); + MessageHandlerRegistry.register(InstanceShutdownMessage.TYPE, new MessageHandler() { + @Override + public void handle(InstanceShutdownMessage message) { + InstanceManager.INSTANCE_CACHE.invalidate(message.getSender()); + } + }); } public void close() { diff --git a/core/src/main/java/net/josscoder/redisbridge/core/data/InstanceInfo.java b/core/src/main/java/net/josscoder/redisbridge/core/data/InstanceInfo.java deleted file mode 100644 index 72a058f..0000000 --- a/core/src/main/java/net/josscoder/redisbridge/core/data/InstanceInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.josscoder.redisbridge.core.data; - -import lombok.Data; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Data -public class InstanceInfo { - - private final String id; - private final String host; - private final int port; - private final String group; - private final int maxPlayers; - private int players; - - public boolean isFull() { - return players >= maxPlayers; - } -} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceInfo.java b/core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceInfo.java new file mode 100644 index 0000000..90824af --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceInfo.java @@ -0,0 +1,21 @@ +package net.josscoder.redisbridge.core.instance; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class InstanceInfo { + private String id; + private String host; + private int port; + private String group; + private int maxPlayers; + private int players; + + public boolean isFull() { + return players >= maxPlayers; + } +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/manager/InstanceManager.java b/core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceManager.java similarity index 96% rename from core/src/main/java/net/josscoder/redisbridge/core/manager/InstanceManager.java rename to core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceManager.java index 3fd8d42..8682037 100644 --- a/core/src/main/java/net/josscoder/redisbridge/core/manager/InstanceManager.java +++ b/core/src/main/java/net/josscoder/redisbridge/core/instance/InstanceManager.java @@ -1,9 +1,8 @@ -package net.josscoder.redisbridge.core.manager; +package net.josscoder.redisbridge.core.instance; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import lombok.Getter; -import net.josscoder.redisbridge.core.data.InstanceInfo; import java.util.*; import java.util.concurrent.TimeUnit; diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/MessageBase.java b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageBase.java new file mode 100644 index 0000000..0414551 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageBase.java @@ -0,0 +1,11 @@ +package net.josscoder.redisbridge.core.message; + +import lombok.Data; + +@Data +public abstract class MessageBase { + + private final String type; + private String sender; + private Long timestamp; +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandler.java b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandler.java new file mode 100644 index 0000000..a838537 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandler.java @@ -0,0 +1,5 @@ +package net.josscoder.redisbridge.core.message; + +public abstract class MessageHandler { + public abstract void handle(T message); +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandlerRegistry.java b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandlerRegistry.java new file mode 100644 index 0000000..502315f --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageHandlerRegistry.java @@ -0,0 +1,18 @@ +package net.josscoder.redisbridge.core.message; + +import java.util.HashMap; +import java.util.Map; + +public class MessageHandlerRegistry { + + private static final Map> handlers = new HashMap<>(); + + public static void register(String type, MessageHandler handler) { + handlers.put(type, handler); + } + + @SuppressWarnings("unchecked") + public static MessageHandler getHandler(String type) { + return (MessageHandler) handlers.get(type); + } +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/MessageRegistry.java b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageRegistry.java new file mode 100644 index 0000000..3385599 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/MessageRegistry.java @@ -0,0 +1,17 @@ +package net.josscoder.redisbridge.core.message; + +import java.util.HashMap; +import java.util.Map; + +public class MessageRegistry { + + private static final Map> messages = new HashMap<>(); + + public static void register(String type, Class aClass) { + messages.put(type, aClass); + } + + public static Class getClass(String type) { + return messages.get(type); + } +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceHeartbeatMessage.java b/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceHeartbeatMessage.java new file mode 100644 index 0000000..b982100 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceHeartbeatMessage.java @@ -0,0 +1,19 @@ +package net.josscoder.redisbridge.core.message.defaults; + +import lombok.Getter; +import lombok.Setter; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.message.MessageBase; + +@Setter +@Getter +public class InstanceHeartbeatMessage extends MessageBase { + + public static final String TYPE = "instance_heartbeat"; + + private InstanceInfo instance; + + public InstanceHeartbeatMessage() { + super(TYPE); + } +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceShutdownMessage.java b/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceShutdownMessage.java new file mode 100644 index 0000000..a844f40 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/message/defaults/InstanceShutdownMessage.java @@ -0,0 +1,12 @@ +package net.josscoder.redisbridge.core.message.defaults; + +import net.josscoder.redisbridge.core.message.MessageBase; + +public class InstanceShutdownMessage extends MessageBase { + + public static final String TYPE = "instance_shutdown"; + + public InstanceShutdownMessage() { + super(TYPE); + } +} diff --git a/core/src/main/java/net/josscoder/redisbridge/core/utils/JsonUtils.java b/core/src/main/java/net/josscoder/redisbridge/core/utils/JsonUtils.java new file mode 100644 index 0000000..1b352f8 --- /dev/null +++ b/core/src/main/java/net/josscoder/redisbridge/core/utils/JsonUtils.java @@ -0,0 +1,35 @@ +package net.josscoder.redisbridge.core.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.josscoder.redisbridge.core.message.MessageBase; + +public class JsonUtils { + + private static final ObjectMapper mapper = new ObjectMapper(); + + public static String toJson(MessageBase obj) { + try { + return mapper.writeValueAsString(obj); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static T fromJson(String json, Class clazz) { + try { + return mapper.readValue(json, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static String extractType(String json) { + try { + JsonNode node = mapper.readTree(json); + return node.get("type").asText(); + } catch (Exception e) { + throw new RuntimeException("Could not extract type", e); + } + } +} diff --git a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/RedisBridgePlugin.java b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/RedisBridgePlugin.java index 17dd6c7..7912670 100644 --- a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/RedisBridgePlugin.java +++ b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/RedisBridgePlugin.java @@ -6,7 +6,9 @@ import cn.nukkit.utils.TextFormat; import lombok.Getter; import net.josscoder.redisbridge.core.RedisBridgeCore; -import net.josscoder.redisbridge.core.data.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.message.defaults.InstanceHeartbeatMessage; +import net.josscoder.redisbridge.core.message.defaults.InstanceShutdownMessage; import net.josscoder.redisbridge.nukkit.command.LobbyCommand; import net.josscoder.redisbridge.nukkit.command.TransferCommand; import net.josscoder.redisbridge.nukkit.logger.Logger; @@ -22,7 +24,7 @@ public class RedisBridgePlugin extends PluginBase { private RedisBridgeCore core; @Getter - private InstanceInfo currentInstanceInfo; + private InstanceInfo instanceInfo; private TaskHandler heartbeatTask; @@ -55,24 +57,28 @@ private void setupCore() { config.getString("redis.password"), new Logger() ); + core.registerDefaultMessages(); } private void setupInstance() { Config config = getConfig(); - currentInstanceInfo = new InstanceInfo( - config.getString("instance.id"), - config.getString("instance.host"), - getServer().getPort(), - config.getString("instance.group"), - getServer().getMaxPlayers() - ); + instanceInfo = new InstanceInfo(); + instanceInfo.setId(config.getString("instance.id")); + instanceInfo.setHost(config.getString("instance.host")); + instanceInfo.setPort(getServer().getPort()); + instanceInfo.setGroup(config.getString("instance.group")); + instanceInfo.setMaxPlayers(getServer().getMaxPlayers()); } private void scheduleInstanceHeartbeatTask() { heartbeatTask = getServer().getScheduler().scheduleRepeatingTask(this, () -> { - currentInstanceInfo.setPlayers(getServer().getOnlinePlayers().size()); - core.publishInstanceInfo(currentInstanceInfo); + instanceInfo.setPlayers(getServer().getOnlinePlayers().size()); + + InstanceHeartbeatMessage message = new InstanceHeartbeatMessage(); + message.setInstance(instanceInfo); + + core.publish(message, instanceInfo.getId()); }, 20); } @@ -82,7 +88,8 @@ public void onDisable() { heartbeatTask.cancel(); } - core.publishInstanceRemove(currentInstanceInfo); + InstanceShutdownMessage message = new InstanceShutdownMessage(); + core.publish(message, instanceInfo.getId()); if (core != null) { core.close(); diff --git a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java index d9b7439..aa7a4c0 100644 --- a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java +++ b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java @@ -4,8 +4,8 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.utils.TextFormat; -import net.josscoder.redisbridge.core.data.InstanceInfo; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; public class LobbyCommand extends Command { diff --git a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/TransferCommand.java b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/TransferCommand.java index 9defbf7..1a969a6 100644 --- a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/TransferCommand.java +++ b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/TransferCommand.java @@ -4,8 +4,8 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.utils.TextFormat; -import net.josscoder.redisbridge.core.data.InstanceInfo; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; import net.josscoder.redisbridge.nukkit.RedisBridgePlugin; public class TransferCommand extends Command { @@ -40,7 +40,7 @@ public boolean execute(CommandSender sender, String label, String[] args) { return false; } - if (instance.getId().equalsIgnoreCase(RedisBridgePlugin.getInstance().getCurrentInstanceInfo().getId())) { + if (instance.getId().equalsIgnoreCase(RedisBridgePlugin.getInstance().getInstanceInfo().getId())) { player.sendMessage(TextFormat.RED + "You are already on this server!"); return false; diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/RedisBridgePlugin.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/RedisBridgePlugin.java index 7f68c9d..a7966c0 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/RedisBridgePlugin.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/RedisBridgePlugin.java @@ -58,6 +58,7 @@ private void setupCore() { config.getString("redis.password"), new Logger() ); + core.registerDefaultMessages(); } private void setupHandlers() { diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/event/ProxyEvents.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/event/ProxyEvents.java index fee347e..9abc69f 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/event/ProxyEvents.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/event/ProxyEvents.java @@ -6,7 +6,7 @@ import dev.waterdog.waterdogpe.logger.Color; import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; import dev.waterdog.waterdogpe.player.ProxiedPlayer; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceManager; public class ProxyEvents { diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java index 5989f1d..461c5fb 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java @@ -3,8 +3,8 @@ import dev.waterdog.waterdogpe.network.connection.handler.IJoinHandler; import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; import dev.waterdog.waterdogpe.player.ProxiedPlayer; -import net.josscoder.redisbridge.core.data.InstanceInfo; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; import net.josscoder.redisbridge.waterdogpe.utils.Utils; public class LobbyJoinHandler implements IJoinHandler { diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java index 20bfc14..9354764 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java @@ -5,8 +5,8 @@ import dev.waterdog.waterdogpe.network.connection.handler.ReconnectReason; import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; import dev.waterdog.waterdogpe.player.ProxiedPlayer; -import net.josscoder.redisbridge.core.data.InstanceInfo; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; import net.josscoder.redisbridge.waterdogpe.RedisBridgePlugin; import net.josscoder.redisbridge.waterdogpe.utils.Utils; diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/task/InstanceRegistrationTask.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/task/InstanceRegistrationTask.java index ec36eb6..e75e681 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/task/InstanceRegistrationTask.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/task/InstanceRegistrationTask.java @@ -2,8 +2,8 @@ import dev.waterdog.waterdogpe.ProxyServer; import dev.waterdog.waterdogpe.scheduler.Task; -import net.josscoder.redisbridge.core.data.InstanceInfo; -import net.josscoder.redisbridge.core.manager.InstanceManager; +import net.josscoder.redisbridge.core.instance.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceManager; import net.josscoder.redisbridge.waterdogpe.RedisBridgePlugin; import net.josscoder.redisbridge.waterdogpe.utils.Utils; import org.apache.logging.log4j.Logger; diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/utils/Utils.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/utils/Utils.java index d6d0d93..231bb78 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/utils/Utils.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/utils/Utils.java @@ -1,7 +1,7 @@ package net.josscoder.redisbridge.waterdogpe.utils; import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo; -import net.josscoder.redisbridge.core.data.InstanceInfo; +import net.josscoder.redisbridge.core.instance.InstanceInfo; import java.net.InetSocketAddress; From 37049559048681f66ab4b6e340e4ba1695eca661 Mon Sep 17 00:00:00 2001 From: Josscoder Date: Mon, 21 Jul 2025 21:53:00 -0500 Subject: [PATCH 2/2] changed to LOWEST_PLAYERS for lobby balance & impl docs --- README.md | 102 ++++++++++++++++++ .../redisbridge/core/RedisBridgeCore.java | 4 + .../nukkit/command/LobbyCommand.java | 2 +- .../waterdogpe/handler/LobbyJoinHandler.java | 2 +- .../handler/ReconnectLobbyHandler.java | 2 +- 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 937e99d..c95aa81 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,108 @@ **RedisBridge** is a complete rewrite of my previous plugin [JBridge](https://github.com/JossArchived/JBridge), developed for Nukkit and WaterdogPE. It provides automatic server registration, player management, and seamless communication between backend servers and the proxy. +# ⚙️ Available Commands +RedisBridge includes ready-to-use commands for your proxy (WaterdogPE, Velocity, BungeeCord) or backend servers: + +- `/lobby` +Teleports the player to an available lobby instance using the `LOWEST_PLAYERS` strategy to avoid overloading a single lobby while keeping activity balanced. + +- `/transfer ` +Transfers the player to a specific instance if available. Useful for networks with multiple game modes. + +- `/whereami` +Displays information to the player showing which instance and group they are currently in, along with the number of players and instance capacity. + +# 📡 Instance Management Usage +RedisBridge includes a **distributed instance discovery and selection system** for minigame servers, lobbies, or backend servers using Redis and a low-latency distributed cache. + +Each server instance sends **automatic heartbeats** via `InstanceHeartbeatMessage` containing: + +- `id` (unique identifier) + +- `group` (e.g., `lobby`, `solo_skywars`, `duels`) + +- `players` (current online players) + +- `maxPlayers` (maximum capacity) + +- `host` and `port` + +This allows other servers and the proxy to know in real time which instances are available, their capacity, and their status. + +The `InstanceManager`: + +- Uses a local cache with a 10-second expiration to keep instance state updated efficiently. + +- Allows you to: + + - Retrieve instances by ID (`getInstanceById`) + + - Retrieve all instances in a group (`getGroupInstances`) + + - Get total player counts or per group (`getTotalPlayerCount`, `getGroupPlayerCount`) + + - Get total maximum player capacity or per group (`getTotalMaxPlayers`, `getGroupMaxPlayers`) + +Provides **automatic available instance selection** using different strategies: + +- `RANDOM`: Selects a random instance in the group. + +- `LOWEST_PLAYERS`: Selects the instance with the fewest players. + +- `MOST_PLAYERS_AVAILABLE`: Selects the instance with the most players. + +Example: + +```java +InstanceInfo instance = InstanceManager.getInstance().selectAvailableInstance("lobby", InstanceManager.SelectionStrategy.LOWEST_PLAYERS); + +if (instance != null) { + // Connect player to this instance +} +``` + +This system enables your network to distribute players dynamically without relying on a heavy centralized matchmaking server. + +# 🚀 Communication Usage +RedisBridge simplifies inter-server communication over Redis, enabling you to publish and subscribe to messages seamlessly between your proxy and backend servers. + +## How it works? +- Uses Redis Pub/Sub on a single channel (redis-bridge-channel) for all message transmission. + +- Messages are serialized in JSON and identified using their type field. + +- Each message type can have: + + - A registered class (MessageRegistry) for deserialization. + + - An optional handler (MessageHandlerRegistry) for automatic processing when received. + +- Includes default messages for instance heartbeat and shutdown announcements to enable automatic instance tracking. + +## Publishing Messages +To send a message to all connected instances: +```java +YourCustomMessage message = new YourCustomMessage(); +// fill your message data here + +redisBridge.publish(message, "sender-id"); +``` + +## Handling Incoming Messages +- Register your message type: + ```java + MessageRegistry.register("your-message-type", YourCustomMessage.class); + ``` +- Register your message handler: + ```java + MessageHandlerRegistry.register("your-message-type", new MessageHandler() { + @Override + public void handle(YourCustomMessage message) { + // handle your message here + } + }); + ``` ## License **RedisBridge** is licensed under the [MIT License](./LICENSE). Feel free to use, modify, and distribute it in your projects. \ No newline at end of file diff --git a/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java b/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java index 12b591c..c49c90a 100644 --- a/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java +++ b/core/src/main/java/net/josscoder/redisbridge/core/RedisBridgeCore.java @@ -15,6 +15,10 @@ import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPubSub; +/** + * Part of this code is taken from: + * DynamicServers + */ public class RedisBridgeCore { public static final String CHANNEL = "redis-bridge-channel"; diff --git a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java index aa7a4c0..80eeb6b 100644 --- a/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java +++ b/nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java @@ -25,7 +25,7 @@ public boolean execute(CommandSender sender, String label, String[] args) { InstanceInfo availableInstance = InstanceManager.getInstance().selectAvailableInstance( "lobby", - InstanceManager.SelectionStrategy.MOST_PLAYERS_AVAILABLE + InstanceManager.SelectionStrategy.LOWEST_PLAYERS ); if (availableInstance == null) { player.sendMessage(TextFormat.RED + "There are no lobbies available!"); diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java index 461c5fb..e3930c6 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/LobbyJoinHandler.java @@ -13,7 +13,7 @@ public class LobbyJoinHandler implements IJoinHandler { public ServerInfo determineServer(ProxiedPlayer proxiedPlayer) { InstanceInfo availableInstance = InstanceManager.getInstance().selectAvailableInstance( "lobby", - InstanceManager.SelectionStrategy.MOST_PLAYERS_AVAILABLE + InstanceManager.SelectionStrategy.LOWEST_PLAYERS ); if (availableInstance == null) { return null; diff --git a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java index 9354764..490b4ee 100644 --- a/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java +++ b/waterdogpe/src/main/java/net/josscoder/redisbridge/waterdogpe/handler/ReconnectLobbyHandler.java @@ -23,7 +23,7 @@ public ServerInfo getFallbackServer(ProxiedPlayer player, ServerInfo oldServer, InstanceInfo availableInstance = InstanceManager.getInstance().selectAvailableInstance( "lobby", - InstanceManager.SelectionStrategy.MOST_PLAYERS_AVAILABLE + InstanceManager.SelectionStrategy.LOWEST_PLAYERS ); if (availableInstance == null) { return null;