Skip to content

Commit a8c92ef

Browse files
committed
changes
1 parent 3318774 commit a8c92ef

File tree

19 files changed

+654
-25
lines changed

19 files changed

+654
-25
lines changed

core/src/main/java/net/josscoder/redisbridge/core/Main.java

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package net.josscoder.redisbridge.core;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
import net.josscoder.redisbridge.core.data.InstanceInfo;
6+
import net.josscoder.redisbridge.core.logger.ILogger;
7+
import net.josscoder.redisbridge.core.manager.InstanceManager;
8+
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
9+
import redis.clients.jedis.Jedis;
10+
import redis.clients.jedis.JedisPool;
11+
import redis.clients.jedis.JedisPubSub;
12+
13+
public class RedisBridgeCore {
14+
15+
private static final Gson GSON = new GsonBuilder().create();
16+
public static final String INSTANCE_HEARTBEAT_CHANNEL = "instance_heartbeat_channel";
17+
public static final String INSTANCE_REMOVE_CHANNEL = "instance_removed_channel";
18+
19+
private JedisPool jedisPool = null;
20+
private Thread listenerThread;
21+
22+
public void connect(String host, int port, String password, ILogger logger) {
23+
ClassLoader previous = Thread.currentThread().getContextClassLoader();
24+
Thread.currentThread().setContextClassLoader(RedisBridgeCore.class.getClassLoader());
25+
26+
int timeout = 5000;
27+
if (password != null && !password.trim().isEmpty()) {
28+
jedisPool = new JedisPool(new GenericObjectPoolConfig<>(), host, port, timeout, password);
29+
} else {
30+
jedisPool = new JedisPool(new GenericObjectPoolConfig<>(), host, port, timeout);
31+
}
32+
33+
Thread.currentThread().setContextClassLoader(previous);
34+
35+
listenerThread = new Thread(() -> {
36+
while (!Thread.currentThread().isInterrupted() && !jedisPool.isClosed()) {
37+
try (Jedis jedis = jedisPool.getResource()) {
38+
jedis.subscribe(new JedisPubSub() {
39+
@Override
40+
public void onMessage(String channel, String message) {
41+
if (channel.equals(INSTANCE_HEARTBEAT_CHANNEL)) {
42+
InstanceInfo data = GSON.fromJson(message, InstanceInfo.class);
43+
InstanceManager.INSTANCE_CACHE.put(data.getId(), data);
44+
} else if (channel.equals(INSTANCE_REMOVE_CHANNEL)) {
45+
InstanceInfo data = GSON.fromJson(message, InstanceInfo.class);
46+
InstanceManager.INSTANCE_CACHE.invalidate(data.getId());
47+
}
48+
}
49+
}, INSTANCE_REMOVE_CHANNEL, INSTANCE_HEARTBEAT_CHANNEL);
50+
} catch (Exception e) {
51+
logger.error("RedisBridge encountered an error, will retry in 1 second", e);
52+
try {
53+
Thread.sleep(1000);
54+
} catch (InterruptedException ie) {
55+
Thread.currentThread().interrupt();
56+
}
57+
}
58+
}
59+
}, "RedisBridge");
60+
61+
listenerThread.setDaemon(true);
62+
listenerThread.start();
63+
}
64+
65+
public void publish(String message, String channel) {
66+
try (Jedis jedis = jedisPool.getResource()) {
67+
jedis.publish(channel, message);
68+
}
69+
}
70+
71+
public void publishInstanceInfo(InstanceInfo info) {
72+
publish(GSON.toJson(info), INSTANCE_HEARTBEAT_CHANNEL);
73+
}
74+
75+
public void publishInstanceRemove(InstanceInfo info) {
76+
publish(GSON.toJson(info), INSTANCE_REMOVE_CHANNEL);
77+
}
78+
79+
public void close() {
80+
if (listenerThread != null && listenerThread.isAlive()) {
81+
listenerThread.interrupt();
82+
}
83+
if (jedisPool != null && !jedisPool.isClosed()) {
84+
jedisPool.close();
85+
}
86+
}
87+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package net.josscoder.redisbridge.core.data;
2+
3+
import lombok.Data;
4+
import lombok.RequiredArgsConstructor;
5+
6+
@RequiredArgsConstructor
7+
@Data
8+
public class InstanceInfo {
9+
10+
private final String id;
11+
private final String host;
12+
private final int port;
13+
private final String group;
14+
private final int maxPlayers;
15+
private int players;
16+
17+
public boolean isFull() {
18+
return players >= maxPlayers;
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package net.josscoder.redisbridge.core.logger;
2+
3+
public interface ILogger {
4+
5+
void info(String message);
6+
void warn(String message);
7+
void debug(String message);
8+
void error(String message, Throwable throwable);
9+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package net.josscoder.redisbridge.core.manager;
2+
3+
import com.google.common.cache.Cache;
4+
import com.google.common.cache.CacheBuilder;
5+
import lombok.Getter;
6+
import net.josscoder.redisbridge.core.data.InstanceInfo;
7+
8+
import java.util.*;
9+
import java.util.concurrent.TimeUnit;
10+
import java.util.stream.Collectors;
11+
12+
public class InstanceManager {
13+
14+
public static final Cache<String, InstanceInfo> INSTANCE_CACHE = CacheBuilder.newBuilder()
15+
.expireAfterWrite(10, TimeUnit.SECONDS)
16+
.build();
17+
18+
@Getter
19+
private static final InstanceManager instance = new InstanceManager();
20+
21+
public Map<String, InstanceInfo> map() {
22+
return INSTANCE_CACHE.asMap();
23+
}
24+
25+
public int getTotalPlayerCount() {
26+
return map().values().stream()
27+
.mapToInt(InstanceInfo::getPlayers)
28+
.sum();
29+
}
30+
31+
public int getTotalMaxPlayers() {
32+
return map().values().stream()
33+
.mapToInt(InstanceInfo::getMaxPlayers)
34+
.sum();
35+
}
36+
37+
public InstanceInfo getInstanceById(String id) {
38+
return INSTANCE_CACHE.getIfPresent(id);
39+
}
40+
41+
public Set<InstanceInfo> getGroupInstances(String group) {
42+
return map().values().stream()
43+
.filter(instance -> instance.getGroup().equalsIgnoreCase(group))
44+
.collect(Collectors.toSet());
45+
}
46+
47+
public int getGroupPlayerCount(String group) {
48+
return getGroupInstances(group).stream()
49+
.mapToInt(InstanceInfo::getPlayers)
50+
.sum();
51+
}
52+
53+
public int getGroupMaxPlayers(String group) {
54+
return getGroupInstances(group).stream()
55+
.mapToInt(InstanceInfo::getMaxPlayers)
56+
.sum();
57+
}
58+
59+
public enum SelectionStrategy {
60+
RANDOM,
61+
LOWEST_PLAYERS,
62+
MOST_PLAYERS_AVAILABLE
63+
}
64+
65+
private Optional<InstanceInfo> findInstanceByGroupAndStrategy(String group, SelectionStrategy strategy) {
66+
List<InstanceInfo> instances = getGroupInstances(group).stream()
67+
.filter(instance -> !instance.isFull())
68+
.distinct()
69+
.collect(Collectors.toList());
70+
71+
switch (strategy) {
72+
case RANDOM:
73+
return Optional.of(instances.get(new Random().nextInt(instances.size())));
74+
case LOWEST_PLAYERS:
75+
return instances.stream()
76+
.min(Comparator.comparingInt(InstanceInfo::getPlayers));
77+
case MOST_PLAYERS_AVAILABLE:
78+
return instances.stream()
79+
.max(Comparator.comparingInt(InstanceInfo::getPlayers));
80+
default:
81+
return Optional.empty();
82+
}
83+
}
84+
85+
public InstanceInfo selectAvailableInstance(String group, SelectionStrategy strategy) {
86+
return findInstanceByGroupAndStrategy(group, strategy).orElse(null);
87+
}
88+
89+
public List<InstanceInfo> selectAvailableInstances(String group, SelectionStrategy strategy) {
90+
return findInstanceByGroupAndStrategy(group, strategy)
91+
.map(Collections::singletonList)
92+
.orElse(Collections.emptyList());
93+
}
94+
}

nukkit/src/main/java/net/josscoder/redisbridge/nukkit/RedisBridgePlugin.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package net.josscoder.redisbridge.nukkit;
22

33
import cn.nukkit.plugin.PluginBase;
4+
import cn.nukkit.scheduler.TaskHandler;
5+
import cn.nukkit.utils.Config;
46
import cn.nukkit.utils.TextFormat;
57
import lombok.Getter;
8+
import net.josscoder.redisbridge.core.RedisBridgeCore;
9+
import net.josscoder.redisbridge.core.data.InstanceInfo;
610
import net.josscoder.redisbridge.nukkit.command.LobbyCommand;
711
import net.josscoder.redisbridge.nukkit.command.TransferCommand;
12+
import net.josscoder.redisbridge.nukkit.logger.Logger;
813

914
import java.util.Arrays;
1015

@@ -13,20 +18,76 @@ public class RedisBridgePlugin extends PluginBase {
1318
@Getter
1419
private static RedisBridgePlugin instance;
1520

21+
@Getter
22+
private RedisBridgeCore core;
23+
24+
@Getter
25+
private InstanceInfo currentInstanceInfo;
26+
27+
private TaskHandler heartbeatTask;
28+
1629
@Override
1730
public void onLoad() {
1831
instance = this;
1932
}
2033

2134
@Override
2235
public void onEnable() {
36+
saveDefaultConfig();
37+
38+
setupCore();
39+
setupInstance();
40+
2341
getServer().getCommandMap().registerAll("redisbridge", Arrays.asList(new LobbyCommand(), new TransferCommand()));
2442

43+
scheduleInstanceHeartbeatTask();
44+
2545
getLogger().info(TextFormat.GREEN + "RedisBridge Plugin Enabled");
2646
}
2747

48+
private void setupCore() {
49+
Config config = getConfig();
50+
51+
core = new RedisBridgeCore();
52+
core.connect(
53+
config.getString("redis.host"),
54+
config.getInt("redis.port"),
55+
config.getString("redis.password"),
56+
new Logger()
57+
);
58+
}
59+
60+
private void setupInstance() {
61+
Config config = getConfig();
62+
63+
currentInstanceInfo = new InstanceInfo(
64+
config.getString("instance.id"),
65+
config.getString("instance.host"),
66+
getServer().getPort(),
67+
config.getString("instance.group"),
68+
getServer().getMaxPlayers()
69+
);
70+
}
71+
72+
private void scheduleInstanceHeartbeatTask() {
73+
heartbeatTask = getServer().getScheduler().scheduleRepeatingTask(this, () -> {
74+
currentInstanceInfo.setPlayers(getServer().getOnlinePlayers().size());
75+
core.publishInstanceInfo(currentInstanceInfo);
76+
}, 20);
77+
}
78+
2879
@Override
2980
public void onDisable() {
81+
if (heartbeatTask != null) {
82+
heartbeatTask.cancel();
83+
}
84+
85+
core.publishInstanceRemove(currentInstanceInfo);
86+
87+
if (core != null) {
88+
core.close();
89+
}
90+
3091
getLogger().info(TextFormat.RED + "RedisBridge Plugin Disabled");
3192
}
3293
}

nukkit/src/main/java/net/josscoder/redisbridge/nukkit/command/LobbyCommand.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package net.josscoder.redisbridge.nukkit.command;
22

3+
import cn.nukkit.Player;
34
import cn.nukkit.command.Command;
45
import cn.nukkit.command.CommandSender;
6+
import cn.nukkit.utils.TextFormat;
7+
import net.josscoder.redisbridge.core.data.InstanceInfo;
8+
import net.josscoder.redisbridge.core.manager.InstanceManager;
59

610
public class LobbyCommand extends Command {
711

@@ -11,6 +15,26 @@ public LobbyCommand() {
1115

1216
@Override
1317
public boolean execute(CommandSender sender, String label, String[] args) {
14-
return false;
18+
if (!(sender instanceof Player)) {
19+
sender.sendMessage("Must be a player!");
20+
21+
return false;
22+
}
23+
24+
Player player = (Player) sender;
25+
26+
InstanceInfo availableInstance = InstanceManager.getInstance().selectAvailableInstance(
27+
"lobby",
28+
InstanceManager.SelectionStrategy.MOST_PLAYERS_AVAILABLE
29+
);
30+
if (availableInstance == null) {
31+
player.sendMessage(TextFormat.RED + "There are no lobbies available!");
32+
33+
return false;
34+
}
35+
36+
player.transfer(availableInstance.getId(), 0);
37+
38+
return true;
1539
}
1640
}

0 commit comments

Comments
 (0)