Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f729998
Cache regex/lookup tables, drop player snapshot copies, improve sampl…
R00tB33rMan May 7, 2026
f548609
Introduce second getOnlinePlayers() method that does not take a snaps…
WouterGritter May 7, 2026
84f2698
Optimize .replace() chains
WouterGritter May 7, 2026
c6a721a
Don't throw on badly configured placeholder
WouterGritter May 7, 2026
036f225
Only compute variables when needed
WouterGritter May 7, 2026
9da3dac
Unnecessary early aborts (parseVariables is fast enough)
WouterGritter May 7, 2026
56c6962
Update javadoc
WouterGritter May 7, 2026
cc7eed6
Optimize parseVariables slightly
WouterGritter May 7, 2026
b393868
Address issue tray
R00tB33rMan May 8, 2026
ec50806
Re-shift location
R00tB33rMan May 8, 2026
4723c42
Re-insert for matching
R00tB33rMan May 8, 2026
22e6e8a
Resolve multiple issues with conversions when modifying sections
R00tB33rMan May 8, 2026
d1a6680
It's better to straight void this as it fixes the migration issue, an…
R00tB33rMan May 8, 2026
d7ddfcf
Fix small regressions
WouterGritter May 9, 2026
a3e1016
Fix small regression in ParsingUtils#parseVariables
WouterGritter May 9, 2026
e52d727
Slight performance improvement for checking whether a ConnectedPlayer…
WouterGritter May 9, 2026
82816af
Partial Fisher–Yates shuffle for faster player list sampling (only us…
WouterGritter May 9, 2026
0782ddd
Checkstyle issues ({ / } characters in javadoc)
WouterGritter May 9, 2026
ff40907
Snapshot players in PlayerIdentifier.Result so consumers see a stable…
WouterGritter May 9, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.velocitypowered.api.util.Ordered;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -309,6 +308,25 @@ public boolean isSupported() {
*/
private static final int SNAPSHOT_BIT = 30;

/**
* A map linking each user-facing version name (e.g. {@code "1.20.4"}) to its
* {@link ProtocolVersion}.
*/
private static final ImmutableMap<@NotNull String, @NotNull ProtocolVersion> NAME_TO_PROTOCOL_CONSTANT;

static {
Map<String, ProtocolVersion> byName = new HashMap<>();
for (ProtocolVersion version : values()) {
for (String name : version.names) {
if (byName.put(name, version) != null) {
throw new IllegalStateException("Multiple versions mapped to '" + name + "' found!");
}
}
}

NAME_TO_PROTOCOL_CONSTANT = ImmutableMap.copyOf(byName);
}

/**
* The protocol version number used by the Minecraft network protocol.
*/
Expand Down Expand Up @@ -488,10 +506,7 @@ public static ProtocolVersion getProtocolVersion(int protocol) {
* @return the protocol version
*/
public static ProtocolVersion getVersionByName(String version) {
return Arrays.stream(ProtocolVersion.values())
.filter(protocolVersion -> Arrays.asList(protocolVersion.names).contains(version))
.findFirst()
.orElse(ProtocolVersion.MINECRAFT_1_7_2);
return NAME_TO_PROTOCOL_CONSTANT.getOrDefault(version, ProtocolVersion.MINECRAFT_1_7_2);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.UUID;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.UnmodifiableView;

/**
* Provides an interface to a Minecraft server proxy.
Expand Down Expand Up @@ -80,9 +81,20 @@ public interface ProxyServer extends Audience {
* of all players online.
*
* @return the players online on this proxy
* @deprecated use {@link #getOnlinePlayers()} instead (faster, doesn't make copy)
*/
@Deprecated
Collection<? extends Player> getAllPlayers();

/**
* Retrieves all players currently connected to this proxy. This is an unmodifiable view of the live
* collection of online players.
*
* @return the players online on this proxy
*/
@UnmodifiableView
Collection<? extends Player> getOnlinePlayers();

/**
* Returns the number of players currently connected to this proxy.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.velocityctd.proxy.cluster.local;

import com.google.common.collect.Collections2;
import com.velocityctd.proxy.cluster.VelocityClusterPlayer;
import com.velocityctd.proxy.cluster.VelocityClusterPlayerService;
import com.velocitypowered.api.proxy.player.PlayerSettings;
Expand Down Expand Up @@ -53,17 +54,14 @@ public int getPlayersOnServerCount(String serverName) {

@Override
public Collection<VelocityClusterPlayer> getAllPlayers() {
return this.server.getAllPlayers().stream()
.<VelocityClusterPlayer>map(this::toLocalPlayer)
.toList();
return Collections2.transform(this.server.getOnlinePlayers(), this::toLocalPlayer);
}

@Override
public Collection<VelocityClusterPlayer> getPlayersOnServer(String serverName) {
return this.server.getServer(serverName)
.map(rs -> rs.getPlayersConnected().stream()
.<VelocityClusterPlayer>map(this::toLocalPlayer)
.toList())
.<Collection<VelocityClusterPlayer>>map(
rs -> Collections2.transform(rs.getPlayersConnected(), this::toLocalPlayer))
.orElse(List.of());
}

Expand All @@ -72,7 +70,7 @@ public Collection<VelocityClusterPlayer> getPlayersOnProxy(String proxyId) {
if (!this.server.getProxyId().equalsIgnoreCase(proxyId)) {
return List.of();
}
return getAllPlayers();
return this.getAllPlayers();
}

@Override
Expand Down Expand Up @@ -109,7 +107,7 @@ public void onPlayerSettingsChange(ConnectedPlayer player, PlayerSettings settin

@Override
public Collection<String> getPlayerNames() {
return this.server.getAllPlayers().stream()
return this.server.getOnlinePlayers().stream()
.map(ConnectedPlayer::getUsername)
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
Expand Down Expand Up @@ -86,7 +87,10 @@ public String name() {
}

private static Result success(Type type, Collection<VelocityClusterPlayer> players, @Nullable String name) {
return new Result(type, true, players, name);
// Snapshot to insulate consumers from lazy/live views (e.g. Collections2.transform on the
// online-players map): guarantees stable size and iteration regardless of joins/leaves
// happening between calls to players().
return new Result(type, true, List.copyOf(players), name);
}

private static Result failure(Type type, @Nullable String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public List<String> aliases() {

@Override
public BrigadierCommand build() {
String label = label();
LiteralCommandNode<CommandSource> listQueues = BrigadierCommand.literalArgumentBuilder("listqueues")
.requires(source -> source.getPermissionValue("velocity.queue.admin.listqueues") == Tristate.TRUE)
.executes(this::listQueues)
Expand Down Expand Up @@ -173,14 +174,14 @@ public BrigadierCommand build() {
return new BrigadierCommand(
commands.stream()
.reduce(
BrigadierCommand.literalArgumentBuilder("queueadmin")
BrigadierCommand.literalArgumentBuilder(label)
.executes(ctx -> {
CommandSource source = ctx.getSource();
String availableCommands = commands.stream()
.filter(e -> e.getRequirement().test(source))
.map(LiteralCommandNode::getName)
.collect(Collectors.joining("|"));
String commandText = "/queueadmin <%s>".formatted(availableCommands);
String commandText = "/%s <%s>".formatted(label, availableCommands);
source.sendMessage(Component.text(commandText, NamedTextColor.RED));
return 0;
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,23 +125,6 @@ public static List<ConfigurationMigration> createCtdMigrations() {
List.of("joinqueue", "queue")
),

// [command-aliases]
migration(
"What commands should have aliases for simpler execution that\n"
+ " do not already have a more advanced function or implementation.",
"command-aliases.hub",
List.of("lobby", "return")
),

// [proxy-command-aliases]
migration(
"Proxy command aliases create new commands that execute other commands when invoked.\n"
+ " This is similar to Bukkit's commands.yml functionality.\n"
+ " Adding multiple aliases executes multiple commands.",
"proxy-command-aliases.examplealias",
List.of("velocity help")
),

// [advanced]
migration(
"Enables the execution of illegal characters in chat and only allows\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public PlayerDepotService(@NotNull VelocityRedis redis) {

@Override
public void teardown() {
for (ConnectedPlayer player : this.server.getAllPlayers()) {
for (ConnectedPlayer player : this.server.getOnlinePlayers()) {
this.depot.remove(player.getUniqueId());
}

Expand Down Expand Up @@ -312,7 +312,7 @@ private void syncPlayerEntries() {
return;
}

for (ConnectedPlayer player : this.server.getAllPlayers()) {
for (ConnectedPlayer player : this.server.getOnlinePlayers()) {
if (this.depot.contains(player.getUniqueId())) {
continue;
}
Expand Down
87 changes: 87 additions & 0 deletions proxy/src/main/java/com/velocityctd/proxy/util/ParsingUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2026 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.velocityctd.proxy.util;

import java.util.function.Function;

public class ParsingUtils {

/**
* Replaces variables of the form <code>{name}</code> in the input string with values produced
* by the given mapper.
*
* <p>Each variable is delimited by a literal <code>{</code> and <code>}</code>. The mapper is
* called with the inner name only (no braces). If it returns a non-null value, that value is
* substituted in place; if it returns {@code null}, the original <code>{name}</code> is
* written back unchanged so unknown placeholders pass through intact. Text outside variables
* is passed through unchanged. Nesting is not supported: a <code>{</code> inside a variable is
* treated as part of the name. If the input ends while a variable is still open (no matching
* <code>}</code>), the opening <code>{</code> and any partial content are written back
* unchanged.
*
* @param input the string to process
* @param variableMapper function mapping a variable name (without braces) to its replacement,
* or {@code null} to leave the <code>{name}</code> literal in the output
* @return the input with all known variables substituted
*/
public static String parseVariables(String input, Function<String, String> variableMapper) {
StringBuilder out = new StringBuilder(input.length());
StringBuilder variable = new StringBuilder();
boolean inVariable = false;
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (!inVariable) {
if (c == '{') {
// start reading variable
inVariable = true;
} else {
// pass-through output string
out.append(c);
}
} else {
if (c == '}') {
// write variable value
String value = variableMapper.apply(variable.toString());
if (value == null) {
// pass-through unknown variables as-is
out.append('{');
out.append(variable);
out.append('}');
} else {
// write variable value
out.append(value);
}

variable.setLength(0);
inVariable = false;
} else {
// pass-through variable name
variable.append(c);
}
}
}

if (inVariable) {
// unclosed '{', pass through as-is
out.append('{');
out.append(variable);
}

return out.toString();
}
}
4 changes: 3 additions & 1 deletion proxy/src/main/java/com/velocitypowered/proxy/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ static class VelocityMetrics {

private static final Logger LOGGER = LogManager.getLogger(Metrics.class);

private static final Pattern MAJOR_VERSION_DIGITS = Pattern.compile("\\d+");

static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics metricsConfig) {
Metrics metrics = new Metrics(LOGGER, 30992, metricsConfig.isEnabled());

Expand Down Expand Up @@ -144,7 +146,7 @@ static void startMetrics(VelocityServer server, VelocityConfiguration.Metrics me
// of course, it really wouldn't be all that simple if they didn't add a quirk, now
// would it valid strings for the major may potentially include values such as -ea to
// denote a pre-release
Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
Matcher versionMatcher = MAJOR_VERSION_DIGITS.matcher(majorVersion);
if (versionMatcher.find()) {
majorVersion = versionMatcher.group(0);
}
Expand Down
23 changes: 20 additions & 3 deletions proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;

/**
* Implementation of {@link ProxyServer}.
Expand Down Expand Up @@ -666,7 +667,7 @@ public boolean reloadConfiguration() throws IOException {
}

if (!this.getConfiguration().getServerLinks().isEmpty()) {
for (ConnectedPlayer player : this.getAllPlayers()) {
for (ConnectedPlayer player : this.getOnlinePlayers()) {
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21)) {
try {
if (player.getProtocolState() == ProtocolState.CONFIGURATION || player.getProtocolState() == ProtocolState.PLAY) {
Expand Down Expand Up @@ -1170,7 +1171,7 @@ public Optional<ConnectedPlayer> getPlayer(UUID uuid) {
public Collection<ConnectedPlayer> matchPlayer(String partialName) {
Objects.requireNonNull(partialName);

return getAllPlayers().stream().filter(p -> p.getUsername()
return getOnlinePlayers().stream().filter(p -> p.getUsername()
.regionMatches(true, 0, partialName, 0, partialName.length()))
.collect(Collectors.toList());
}
Expand All @@ -1189,6 +1190,22 @@ public Collection<ConnectedPlayer> getAllPlayers() {
return ImmutableList.copyOf(connectionsByUuid.values());
}

@Override
public @UnmodifiableView Collection<ConnectedPlayer> getOnlinePlayers() {
return Collections.unmodifiableCollection(connectionsByUuid.values());
}

/**
* Returns whether the given player is currently registered as online on this proxy. Uses an
* O(1) UUID lookup against the underlying connection map.
*
* @param player the player to check
* @return {@code true} if the same player instance is registered under its UUID
*/
public boolean isPlayerOnline(ConnectedPlayer player) {
return connectionsByUuid.get(player.getUniqueId()) == player;
}

@Override
public int getPlayerCount() {
return clusterPlayerService.getTotalPlayerCount();
Expand Down Expand Up @@ -1272,7 +1289,7 @@ public InetSocketAddress getBoundAddress() {

@Override
public @NonNull Iterable<? extends Audience> audiences() {
Collection<ConnectedPlayer> connectedPlayers = getAllPlayers();
Collection<ConnectedPlayer> connectedPlayers = getOnlinePlayers();

Collection<Audience> audiences = new ArrayList<>(connectedPlayers.size() + 1);
audiences.add(console);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ private void processPlayerList(ByteBufDataInput in) {
out.writeUTF("ALL");

StringJoiner joiner = new StringJoiner(", ");
for (ConnectedPlayer online : proxy.getAllPlayers()) {
for (ConnectedPlayer online : proxy.getOnlinePlayers()) {
joiner.add(online.getUsername());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class ClientSettingsWrapper implements PlayerSettings {
@Override
public Locale getLocale() {
if (locale == null) {
locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-"));
locale = Locale.forLanguageTag(settings.getLocale().replace('_', '-'));
}

return locale;
Expand Down
Loading