2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2026-03-29 03:10:47 +00:00

implement the ProxyManager

untested
This commit is contained in:
Mohammed Alteneiji 2026-03-28 19:31:31 +04:00
parent f9017a5f44
commit dd317c5cce
Signed by: ham1255
GPG Key ID: EF343502046229F4
11 changed files with 311 additions and 2 deletions

View File

@ -0,0 +1,15 @@
description = "Api functions for valiobungee"
java {
withJavadocJar()
withSourcesJar()
}
dependencies {
testImplementation(libs.testing.juipter)
}
tasks.test {
useJUnitPlatform()
}

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License Version 2.0
* which accompanies this distribution, and is available at
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
package net.limework.valiobungee.api;
public interface NetworkPlayer {}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License Version 2.0
* which accompanies this distribution, and is available at
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
package net.limework.valiobungee.api;
/**
* Proxy is an object for online proxy in a network
*
* @author Ham1255
* @since 1.0.0
*/
public interface NetworkProxy {
/**
* @return return the proxy id of this proxy
*/
String proxyId();
/**
* @return online players number in this proxy
*/
int onlinePlayers();
/**
* @return returns true if this object is proxy on it
*/
boolean isMe();
}

View File

@ -33,6 +33,9 @@ subprojects {
} else {
licenseHeaderFile(rootProject.file("copyright_header.txt"))
}
if (project.name == "valiobungee-core") {
targetExclude("**/net/limework/valiobungee/core/proto/**")
}
}
}

View File

@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.blossom)
alias(libs.plugins.indragit)
alias(libs.plugins.protobuf)
}
description = "Core functions for valiobungee"
@ -20,3 +21,24 @@ java {
withJavadocJar()
withSourcesJar()
}
dependencies {
api(project(":valiobungee-api"))
api(libs.protobuf)
api(libs.caffeine)
api(libs.slf4j)
testImplementation(libs.testing.juipter)
testImplementation(libs.testing.slf4j.simple)
}
tasks.test {
useJUnitPlatform()
}
protobuf {
protoc {
artifact = libs.protoc.get().toString()
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU GENERAL PUBLIC LICENSE Version 3
* which accompanies this distribution, and is available at
* https://www.gnu.org/licenses/gpl-3.0.txt
*/
package net.limework.valiobungee.core;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import java.time.Duration;
import java.util.UUID;
import net.limework.valiobungee.core.proto.messages.*;
import net.limework.valiobungee.core.util.logging.LogProviderFactory;
import org.slf4j.Logger;
/**
* This abstract class is responsible for proxy discovery and count online players as its cached
* locally Why abstract? Because it allows us to develop alternative implementation to use other
* software than valkey or redis
*/
public abstract class ProxyManager {
protected final UUID proxyManagerId = UUID.randomUUID();
protected final ValioBungeePlatform platform;
public ProxyManager(ValioBungeePlatform platform) {
this.platform = platform;
}
protected final Logger log = LogProviderFactory.get();
// proxy info class here stores the ProxyManager ID hence we use another cache for online players
// reason we use caffeine cache it allows us to auto remove entries without need for scheduled
// tasks in the proxies when a proxy disappears without sending death message
private final Cache<String, Heartbeat> heartbeats =
Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(30))
.removalListener(
(RemovalListener<String, Heartbeat>)
(key, value, cause) -> {
if (cause == RemovalCause.EXPIRED) {
assert value != null;
log.warn("proxy {} has disconnected but did not send death message", value);
}
})
.build();
protected void handleProxyHeartBeat(Heartbeat payload) {
if (!this.heartbeats.asMap().containsKey(payload.getSender().getProxyId()))
log.info("Proxy {} has connected!", payload.getSender().getProxyId());
this.heartbeats.put(payload.getSender().getProxyId(), payload);
}
protected void handleProxyDeath(Death payload) {
this.heartbeats.invalidate(payload.getSender().getProxyId());
log.info("Proxy {} has disconnected", payload.getSender().getProxyId());
}
public int onlinePlayersCount() {
// reason we + local online players because our proxy is not inside it own heartbeat cache
return heartbeats.asMap().values().stream().mapToInt(Heartbeat::getOnlinePlayersCount).sum()
+ platform.localOnlinePlayers();
}
public int onlinePlayersCount(String proxyId) {
if (proxyId.equals(this.platform.proxyId())) {
return platform.localOnlinePlayers();
} else {
if (!heartbeats.asMap().containsKey(proxyId)) return 0; // don't error?
return heartbeats.asMap().get(proxyId).getOnlinePlayersCount();
}
}
protected ProxyInfo createProxyInfo() {
return ProxyInfo.newBuilder()
.setProxyId(platform.proxyId())
.setProxyManagerId(this.proxyManagerId.toString())
.build();
}
protected Death createDeathPayload() {
return Death.newBuilder().setSender(createProxyInfo()).setReason(DeathReason.SHUTDOWN).build();
}
protected Heartbeat createHeartbeatPayload() {
return Heartbeat.newBuilder()
.setSender(createProxyInfo())
.setOnlinePlayersCount(platform.localOnlinePlayers())
.build();
}
protected abstract void publishDeathPayload();
protected abstract void publishHeartbeatPayload();
public abstract void init();
public abstract void close();
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU GENERAL PUBLIC LICENSE Version 3
* which accompanies this distribution, and is available at
* https://www.gnu.org/licenses/gpl-3.0.txt
*/
package net.limework.valiobungee.core;
public interface ValioBungeePlatform {
int localOnlinePlayers();
String platformProxyVendor();
String networkId();
String proxyId();
ProxyManager proxyManager();
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU GENERAL PUBLIC LICENSE Version 3
* which accompanies this distribution, and is available at
* https://www.gnu.org/licenses/gpl-3.0.txt
*/
package net.limework.valiobungee.core.api.impl;
import net.limework.valiobungee.api.NetworkProxy;
import net.limework.valiobungee.core.ValioBungeePlatform;
public class ImplNetworkProxy implements NetworkProxy {
private final ValioBungeePlatform platform;
private final String proxyId;
public ImplNetworkProxy(ValioBungeePlatform platform, String proxyId) {
this.platform = platform;
this.proxyId = proxyId;
}
@Override
public String proxyId() {
return this.proxyId;
}
@Override
public int onlinePlayers() {
return this.platform.proxyManager().onlinePlayersCount(this.proxyId);
}
@Override
public boolean isMe() {
return this.platform.proxyId().equals(proxyId);
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2026-present ValioBungee contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU GENERAL PUBLIC LICENSE Version 3
* which accompanies this distribution, and is available at
* https://www.gnu.org/licenses/gpl-3.0.txt
*/
package net.limework.valiobungee.core.util.logging;
import org.slf4j.Logger;
public class LogProviderFactory {
private static Logger instance;
public static void register(Logger logger) {
if (instance != null) throw new IllegalStateException("Logger already registered");
instance = logger;
}
public static Logger get() {
if (instance == null) throw new IllegalStateException("No logger registered");
return instance;
}
}

View File

@ -0,0 +1,28 @@
syntax = "proto3";
package net.limework.valiobungee.core.proto.messages;
option java_multiple_files = true;
message ProxyInfo {
string proxy_id = 1; // UNIQUE ID
string proxy_manager_id = 2; // ProxyManager id. changes every reboot, used to detect duplicate proxy names
}
// heartbeat ._.?
message Heartbeat {
ProxyInfo sender = 1;
int32 online_players_count = 2;
}
// death
enum DeathReason {
UNSPECIFIED = 0; // TESTING ONLY
RELOAD = 1; // plugin reload
SHUTDOWN = 2; // proxy shutdown
}
message Death {
ProxyInfo sender = 1; // proxy that died
DeathReason reason = 2;
}

View File

@ -1,10 +1,24 @@
[versions]
protobuf-plugin = "0.9.5"
protobuf = "3.25.8" # needed for reference to be used for protoc
slf4j = "2.0.17"
[plugins]
blossom = "net.kyori.blossom:2.2.0"
indragit = "net.kyori.indra.git:4.0.0"
shadow = "com.gradleup.shadow:9.3.1"
spotless = "com.diffplug.spotless:8.2.0"
protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" }
[libraries]
redisson = "org.redisson:redisson:4.3.0"
protobuf = { group = "com.google.protobuf", name = "protobuf-java", version.ref = "protobuf" }
protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" }
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.3"
# logging
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
# testing
testing-juipter = "org.junit.jupiter:junit-jupiter:6.0.3"
testing-slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }