Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
name: 'Build'
runs-on: ubuntu-latest
steps:
- name: 'Setup JDK 21 📦'
- name: 'Setup JDK 25 📦'
uses: actions/setup-java@v4
with:
java-version: '21'
java-version: '25'
distribution: 'temurin'
- name: 'Setup Gradle 8.10 🏗️'
uses: gradle/actions/setup-gradle@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
- name: Set up JDK 25
uses: actions/setup-java@v3
with:
java-version: '21'
java-version: '25'
distribution: 'temurin'
- name: Test Pull Request
uses: gradle/gradle-build-action@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
- name: Set up JDK 25
uses: actions/setup-java@v3
with:
java-version: '21'
java-version: '25'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
Expand Down
41 changes: 31 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import org.apache.tools.ant.filters.ReplaceTokens

plugins {
id 'com.gradleup.shadow' version '9.0.0-beta17'
id 'org.cadixdev.licenser' version '0.6.1' apply false
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
id 'dev.architectury.loom' version '1.14-SNAPSHOT' apply false
id 'dev.architectury.loom-no-remap' version '1.14-SNAPSHOT' apply false
id 'gg.essential.multi-version.root' apply false
id 'org.ajoberstar.grgit' version '5.3.2'
id 'maven-publish'
Expand Down Expand Up @@ -57,7 +57,6 @@ allprojects {
}

apply plugin: 'com.gradleup.shadow'
apply plugin: 'org.cadixdev.licenser'
apply plugin: 'java'

compileJava.options.encoding = 'UTF-8'
Expand Down Expand Up @@ -93,10 +92,26 @@ allprojects {
useJUnitPlatform()
}

license {
header = rootProject.file('HEADER')
include '**/*.java'
newLine = true
def licenseHeader = '/*\n' + rootProject.file('HEADER').readLines().collect {
" *${it ? " $it" : ''}"
}.join('\n') + '\n */'
def javaSources = { fileTree(projectDir) { include 'src/**/*.java' } }

tasks.register('licenseCheck') {
doLast {
def missing = javaSources().findAll { !it.text.startsWith(licenseHeader) }
if (!missing.isEmpty()) {
throw new GradleException("Missing license header: ${missing*.path.join(', ')}")
}
}
}

tasks.register('licenseFormat') {
doLast {
javaSources().findAll { !it.text.startsWith(licenseHeader) }.each {
it.text = "${licenseHeader}\n\n${it.text}"
}
}
}

processResources {
Expand Down Expand Up @@ -127,11 +142,17 @@ subprojects {
} else {
name += "-${project.name.capitalize()}"
}
archivesBaseName = name
base {
archivesName = name
}

if (project.parent?.name?.equals('fabric')) {
apply plugin: 'dev.architectury.loom'
compileJava.options.release.set (project.name == '1.20.1' ? 17 : 21) // 1.20.1 requires Java 17
def fabricMajorVersion = project.name.split('\\.')[0] as int
apply plugin: fabricMajorVersion >= 26 ? 'dev.architectury.loom-no-remap' : 'dev.architectury.loom'
compileJava.options.release.set(project.name == '1.20.1' ? 17 : fabricMajorVersion >= 26 ? 25 : 21)
if (fabricMajorVersion >= 26) {
java.toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}
version += "+mc.${project.name}"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public void onEnable() {
@Override
public void onDisable() {
messenger.onDisable();
executorService.shutdownNow();
}

private void setupMetrics() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import com.google.common.collect.Maps;
import lombok.Getter;
import lombok.Setter;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
Expand Down Expand Up @@ -79,10 +79,6 @@ public void onDisable() {
getProxy().getPluginManager().unregisterListener(this);
}

@EventHandler
public void onPluginMessageReceived(PluginMessageEvent event) {
this.handleMessage(this, event.getTag(), event.getData(), false);
}

@EventHandler
public void onJoin(PostLoginEvent event) {
Expand All @@ -91,7 +87,7 @@ public void onJoin(PostLoginEvent event) {
}

@EventHandler
public void onQuit(PostLoginEvent event) {
public void onQuit(PlayerDisconnectEvent event) {
final BungeeUser user = BungeeUser.adapt(event.getPlayer());
users.remove(user.getUniqueId());
PlaceholderAPI.clearCache(event.getPlayer().getUniqueId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.william278.papiproxybridge.BungeePAPIProxyBridge;
Expand Down Expand Up @@ -61,6 +62,14 @@ public void sendMessage(@NotNull UUID uuid, @NotNull String channel, byte @NotNu

@EventHandler
public void onPluginMessageReceived(net.md_5.bungee.api.event.PluginMessageEvent event) {
if (!event.getTag().equals(PAPIProxyBridge.getChannel(false))
&& !event.getTag().equals(PAPIProxyBridge.getComponentChannel(false))) {
return;
}
event.setCancelled(true);
if (!(event.getSender() instanceof Server)) {
return;
}
plugin.handleMessage(plugin, event.getTag(), event.getData(), false);
}

Expand Down
5 changes: 5 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ dependencies {
compileOnly "net.kyori:adventure-text-serializer-gson:4.23.0"
compileOnly 'io.lettuce:lettuce-core:6.5.5.RELEASE'
compileOnly 'de.exlll:configlib-yaml:4.6.1'

testImplementation 'com.google.guava:guava:33.4.8-jre'
testImplementation 'net.kyori:adventure-api:4.23.0'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.23.0'
testImplementation 'de.exlll:configlib-yaml:4.6.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,19 @@ default void handleMessage(@NotNull PAPIProxyBridge plugin, @NotNull String chan
return;
}

final ByteArrayDataInput inputStream = ByteStreams.newDataInput(message);
final long mostSignificantBits = inputStream.readLong();
final long leastSignificantBits = inputStream.readLong();
final UUID uuid = new UUID(mostSignificantBits, leastSignificantBits);
final OnlineUser user = plugin.findPlayer(uuid).orElse(null);
if (user == null) {
return;
}

try {
final short messageLength = inputStream.readShort();
final ByteArrayDataInput inputStream = ByteStreams.newDataInput(message);
final UUID uuid = new UUID(inputStream.readLong(), inputStream.readLong());
final OnlineUser user = plugin.findPlayer(uuid).orElse(null);
if (user == null) {
return;
}

final int messageLength = inputStream.readUnsignedShort();
final byte[] messageBody = new byte[messageLength];
inputStream.readFully(messageBody);
user.handleMessage(plugin, Request.deserialize(messageBody), channel.equals(getComponentChannel(isRequest)));
} catch (IOException | ClassNotFoundException | IllegalStateException e) {
} catch (IOException | ClassNotFoundException | RuntimeException e) {
plugin.log(Level.SEVERE, "Failed to fully read plugin message. Is PAPIProxyBridge up-to-date and installed on all servers?", e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public final class PlaceholderAPI {
private static PAPIProxyBridge plugin;
private final static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, r -> new Thread(r, "PAPIProxyBridge-PlaceholderAPI-ScheduledThread"));
private final static String PLACEHOLDER_DELIMITER = "%%%-%%%";
private final ConcurrentMap<UUID, ExpiringMap<String, String>> cache;
private final ConcurrentMap<UUID, ExpiringMap<String, Component>> componentCache;
private final ConcurrentMap<CacheKey, ExpiringMap<String, String>> cache;
private final ConcurrentMap<CacheKey, ExpiringMap<String, Component>> componentCache;
private long requestTimeout = 1000;
private long cacheExpiry = 30000;
private int retryTimes = 3;
Expand Down Expand Up @@ -116,11 +116,18 @@ public static void register(@NotNull PAPIProxyBridge plugin) {
@ApiStatus.Internal
public static void clearCache(@NotNull UUID player) {
instances.forEach(instance -> {
instance.cache.remove(player);
instance.componentCache.remove(player);
instance.cache.keySet().removeIf(key -> key.contains(player));
instance.componentCache.keySet().removeIf(key -> key.contains(player));
});
}

private record CacheKey(@NotNull UUID requester, @NotNull UUID formatFor) {

private boolean contains(@NotNull UUID player) {
return requester.equals(player) || formatFor.equals(player);
}
}

@SuppressWarnings("unchecked")
private static <T> CompletableFuture<T> orTimeoutAsync(CompletableFuture<T> future, long timeout) {
final CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
Expand Down Expand Up @@ -161,36 +168,41 @@ private CompletableFuture<String> formatPlaceholders(@NotNull String text, @NotN
if (!requester.isConnected()) {
return CompletableFuture.completedFuture(text);
}
if (cacheExpiry > 0 && cache.containsKey(formatFor) && cache.get(formatFor).containsKey(text)) {
return CompletableFuture.completedFuture(cache.get(formatFor).get(text));
final CacheKey cacheKey = new CacheKey(requester.getUniqueId(), formatFor);
if (cacheExpiry > 0 && cache.containsKey(cacheKey) && cache.get(cacheKey).containsKey(text)) {
return CompletableFuture.completedFuture(cache.get(cacheKey).get(text));
}
final CompletableFuture<String> future = plugin.createRequest(text, requester, formatFor, false, requestTimeout);
return orTimeoutAsync(future, requestTimeout).thenApply(formatted -> {
cache.computeIfAbsent(requester.getUniqueId(), uuid -> ExpiringMap.builder()
cache.computeIfAbsent(cacheKey, key -> ExpiringMap.builder()
.expiration(cacheExpiry, TimeUnit.MILLISECONDS)
.build())
.put(text, formatted);
return formatted;
}).exceptionally(e -> {
}).exceptionallyCompose(e -> {
if (!requester.isConnected()) {
return text;
return CompletableFuture.completedFuture(text);
}

if (times > 0) {
return formatPlaceholders(text, requester, formatFor, times - 1);
}

if (checkLastError()) {
return text;
return CompletableFuture.completedFuture(text);
}

// Handle failed to format exceptions
if (e instanceof CompletionException || Arrays.stream(e.getSuppressed())
.anyMatch(TimeoutException.class::isInstance)) {
final Throwable cause = e instanceof CompletionException ? e.getCause() : e;
if (cause instanceof TimeoutException) {
plugin.log(Level.WARNING, ("Timed out formatting placeholders for %s after %sms." +
"Is PAPIProxyBridge up-to-date and installed on all backend servers?")
.formatted(requester.getUsername(), getRequestTimeout()));
} else {
plugin.log(Level.WARNING, "Failed to format placeholders for %s".formatted(requester.getUsername()), e);
}

return text;
return CompletableFuture.completedFuture(text);
});
}

Expand Down Expand Up @@ -261,37 +273,42 @@ private CompletableFuture<Component> formatComponentPlaceholders(@NotNull String
if (!requester.isConnected()) {
return CompletableFuture.completedFuture(Component.text(text));
}
if (cacheExpiry > 0 && componentCache.containsKey(formatFor) && componentCache.get(formatFor).containsKey(text)) {
return CompletableFuture.completedFuture(componentCache.get(formatFor).get(text));
final CacheKey cacheKey = new CacheKey(requester.getUniqueId(), formatFor);
if (cacheExpiry > 0 && componentCache.containsKey(cacheKey) && componentCache.get(cacheKey).containsKey(text)) {
return CompletableFuture.completedFuture(componentCache.get(cacheKey).get(text));
}
final CompletableFuture<String> future = plugin.createRequest(text, requester, formatFor, true, requestTimeout);
return orTimeoutAsync(future, requestTimeout).thenApply(formatted -> {
final Component deserialized = GsonComponentSerializer.gson().deserializeOr(formatted, Component.text(formatted));
componentCache.computeIfAbsent(requester.getUniqueId(), uuid -> ExpiringMap.builder()
componentCache.computeIfAbsent(cacheKey, key -> ExpiringMap.builder()
.expiration(cacheExpiry, TimeUnit.MILLISECONDS)
.build())
.put(text, deserialized);
return deserialized;
}).exceptionally(e -> {
}).exceptionallyCompose(e -> {
if (!requester.isConnected()) {
return Component.text(text);
return CompletableFuture.completedFuture(Component.text(text));
}

if (times > 0) {
return formatComponentPlaceholders(text, requester, formatFor, times - 1);
}

if (checkLastError()) {
return Component.text(text);
return CompletableFuture.completedFuture(Component.text(text));
}

// Handle failed to format exceptions
if (e instanceof CompletionException || Arrays.stream(e.getSuppressed())
.anyMatch(TimeoutException.class::isInstance)) {
final Throwable cause = e instanceof CompletionException ? e.getCause() : e;
if (cause instanceof TimeoutException) {
plugin.log(Level.WARNING, ("Timed out formatting placeholders for %s after %sms." +
"Is PAPIProxyBridge up-to-date and installed on all backend servers?")
.formatted(requester.getUsername(), getRequestTimeout()));
} else {
plugin.log(Level.WARNING, "Failed to format placeholders for %s".formatted(requester.getUsername()), e);
}

return Component.text(text);
return CompletableFuture.completedFuture(Component.text(text));
});
}

Expand Down
Loading
Loading