Skip to content

Commit

Permalink
Implement key-based registry caching and velocity proxy per-server ca…
Browse files Browse the repository at this point in the history
…ching

Bungee isn't finished yet
  • Loading branch information
booky10 committed Aug 6, 2024
1 parent c2cdafb commit 26c30f4
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public InternalPacketListener(PacketListenerPriority priority) {
@Override
public void onPacketSend(PacketSendEvent event) {
User user = event.getUser();
boolean proxy = PacketEvents.getAPI().getInjector().isProxy();
if (event.getPacketType() == PacketType.Login.Server.LOGIN_SUCCESS) {
Object channel = event.getChannel();
//Process outgoing login success packet
Expand All @@ -74,6 +73,7 @@ public void onPacketSend(PacketSendEvent event) {

// Switch the user's connection state to new state, but the variable event.getConnectionState() remains LOGIN
// We switch user state immediately to remain in sync with vanilla, allowing you to encode packets immediately
boolean proxy = PacketEvents.getAPI().getInjector().isProxy();
if (proxy ? event.getUser().getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_2)
: event.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2)) {
user.setEncoderState(ConnectionState.CONFIGURATION);
Expand All @@ -88,11 +88,11 @@ else if (event.getPacketType() == PacketType.Configuration.Server.REGISTRY_DATA)

if (packet.getElements() != null) { // 1.20.2 to 1.20.5
SynchronizedRegistriesHandler.handleRegistry(user, packet.getServerVersion().toClientVersion(),
packet.getRegistryKey(), packet.getElements(), !proxy);
packet.getRegistryKey(), packet.getElements());
}
if (packet.getRegistryData() != null) { // since 1.20.5
SynchronizedRegistriesHandler.handleLegacyRegistries(user, packet.getServerVersion()
.toClientVersion(), packet.getRegistryData(), proxy);
.toClientVersion(), packet.getRegistryData());
}
}

Expand All @@ -103,7 +103,7 @@ else if (event.getPacketType() == PacketType.Play.Server.JOIN_GAME) {

if (joinGame.getDimensionCodec() != null) { // 1.16 to 1.20.1
SynchronizedRegistriesHandler.handleLegacyRegistries(user, joinGame.getServerVersion().toClientVersion(),
joinGame.getDimensionCodec(), proxy);
joinGame.getDimensionCodec());
}

user.setDimensionType(joinGame.getDimensionType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

package com.github.retrooper.packetevents.manager.server;

import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import org.jetbrains.annotations.Nullable;

public interface ServerManager {
/**
* Get the server version.
Expand All @@ -34,4 +38,20 @@ public interface ServerManager {
default SystemOS getOS() {
return SystemOS.getOS();
}

/**
* Gets a platform-specific network-synchronized-registries cache key.
* <p>
* This tells packetevents, if a registry should be cached or read again. On
* backend servers with global registries, this may be a constant value. On
* proxy servers with per-server registries, this may be a value which depends
* on the current server the {@link User} is on.
*
* @param user the {@link User} for which the registry gets read
* @param version the version the packet is for
* @return some value or null for no caching at all
*/
default @Nullable Object getRegistryCacheKey(User user, ClientVersion version) {
return null; // no caching
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,72 +55,67 @@

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@ApiStatus.Internal
public final class SynchronizedRegistriesHandler {

private static final boolean FORCE_PER_USER_REGISTRIES = Boolean.getBoolean("packetevents.force-per-user-registries");
private static final Map<ResourceLocation, RegistryEntry<?>> REGISTRY_KEYS = Stream.of(
new RegistryEntry<>(Biomes.getRegistry(), Biomes.class, Biome::decode),
new RegistryEntry<>(ChatTypes.getRegistry(), ChatTypes.class, ChatType::decode),
new RegistryEntry<>(TrimPatterns.getRegistry(), TrimPatterns.class, TrimPattern::decode),
new RegistryEntry<>(TrimMaterials.getRegistry(), TrimMaterials.class, TrimMaterial::decode),
new RegistryEntry<>(WolfVariants.getRegistry(), WolfVariant.class, WolfVariant::decode),
new RegistryEntry<>(PaintingVariants.getRegistry(), PaintingVariants.class, PaintingVariant::decode),
new RegistryEntry<>(DimensionTypes.getRegistry(), DimensionTypes.class, DimensionType::decode),
new RegistryEntry<>(DamageTypes.getRegistry(), DamageTypes.class, DamageType::decode),
new RegistryEntry<>(BannerPatterns.getRegistry(), BannerPatterns.class, BannerPattern::decode),
new RegistryEntry<>(EnchantmentTypes.getRegistry(), EnchantmentTypes.class, EnchantmentType::decode),
new RegistryEntry<>(JukeboxSongs.getRegistry(), IJukeboxSong.class, IJukeboxSong::decode)
new RegistryEntry<>(Biomes.getRegistry(), Biome::decode),
new RegistryEntry<>(ChatTypes.getRegistry(), ChatType::decode),
new RegistryEntry<>(TrimPatterns.getRegistry(), TrimPattern::decode),
new RegistryEntry<>(TrimMaterials.getRegistry(), TrimMaterial::decode),
new RegistryEntry<>(WolfVariants.getRegistry(), WolfVariant::decode),
new RegistryEntry<>(PaintingVariants.getRegistry(), PaintingVariant::decode),
new RegistryEntry<>(DimensionTypes.getRegistry(), DimensionType::decode),
new RegistryEntry<>(DamageTypes.getRegistry(), DamageType::decode),
new RegistryEntry<>(BannerPatterns.getRegistry(), BannerPattern::decode),
new RegistryEntry<>(EnchantmentTypes.getRegistry(), EnchantmentType::decode),
new RegistryEntry<>(JukeboxSongs.getRegistry(), IJukeboxSong::decode)
).collect(Collectors.toMap(RegistryEntry::getRegistryKey, Function.identity()));

private SynchronizedRegistriesHandler() {
}

public static void handleRegistry(
User user, ClientVersion version,
ResourceLocation registryName,
List<RegistryElement> elements
) {
Object cacheKey = PacketEvents.getAPI().getServerManager().getRegistryCacheKey(user, version);
handleRegistry(user, version, registryName, elements, cacheKey);
}

public static void handleRegistry(
User user, ClientVersion version,
ResourceLocation registryName,
List<RegistryElement> elements,
boolean caching
Object cacheKey
) {
RegistryEntry<?> registry = REGISTRY_KEYS.get(registryName);
if (registry != null) {
Supplier<SimpleRegistry<?>> syncedRegistrySupplier = () -> registry.createFromElements(elements, version);
if (!FORCE_PER_USER_REGISTRIES && caching) {
// Cache the synchronized registries within each VersionedRegistry.
SimpleRegistry<?> syncedRegistry;
if (registry.baseRegistry instanceof VersionedRegistry) {
VersionedRegistry<?> versionedRegistry = (VersionedRegistry<?>) registry.baseRegistry;

//Did we already cache these? If so, skip...
if (versionedRegistry.getSynchronizedRegistry() != null) return;

syncedRegistry = syncedRegistrySupplier.get();
versionedRegistry.synchronizeRegistry(syncedRegistry);
} else {
syncedRegistry = syncedRegistrySupplier.get();
}

// Dimension type registry is the one exception that we always store for each user.
if (registryName.equals(DimensionTypes.getRegistry().getRegistryKey())) {
user.putUserRegistry(syncedRegistry);
}
}
else {
// If we opt not to cache, we shall store the registries within each user.
user.putUserRegistry(syncedRegistrySupplier.get());
}
RegistryEntry<?> registryData = REGISTRY_KEYS.get(registryName);
if (registryData == null) {
return;
}
SimpleRegistry<?> syncedRegistry;
if (FORCE_PER_USER_REGISTRIES || cacheKey == null) {
syncedRegistry = registryData.createFromElements(elements, version); // no caching
} else {
syncedRegistry = registryData.computeSyncedRegistry(cacheKey, () ->
registryData.createFromElements(elements, version));
}
user.putUserRegistry(syncedRegistry);
}

public static void handleLegacyRegistries(
User user, ClientVersion version,
NBTCompound registryData,
boolean cache
NBTCompound registryData
) {
Object cacheKey = PacketEvents.getAPI().getServerManager().getRegistryCacheKey(user, version);
for (NBT tag : registryData.getTags().values()) {
NBTCompound compound = (NBTCompound) tag;
// extract registry name
Expand All @@ -131,7 +126,7 @@ public static void handleLegacyRegistries(
compound.getCompoundListTagOrThrow("value");
// store registry elements
handleRegistry(user, version, registryName,
RegistryElement.convertNbt(nbtElements), cache);
RegistryElement.convertNbt(nbtElements), cacheKey);
}
}

Expand All @@ -144,19 +139,28 @@ private interface NbtEntryDecoder<T> {
private static final class RegistryEntry<T extends MappedEntity & CopyableEntity<T>> {

private final IRegistry<T> baseRegistry;
private final Class<?> registryContainerClass;
private final NbtEntryDecoder<T> decoder;

// each registry may have a synchronized registry cache, for convenience and enhanced performance
//
// the key to this cache depends on the platform - it may be a constant value for bukkit servers
// or some backend server related value for proxy servers
private final Map<Object, SimpleRegistry<T>> syncedRegistries = new ConcurrentHashMap<>(2);

public RegistryEntry(
IRegistry<T> baseRegistry,
Class<?> registryContainerClass,
NbtEntryDecoder<T> decoder
) {
this.baseRegistry = baseRegistry;
this.registryContainerClass = registryContainerClass;
this.decoder = decoder;
}

@SuppressWarnings("unchecked")
public SimpleRegistry<T> computeSyncedRegistry(Object key, Supplier<SimpleRegistry<?>> registry) {
return this.syncedRegistries.computeIfAbsent(key,
$ -> (SimpleRegistry<T>) registry.get());
}

private void handleElement(
SimpleRegistry<T> registry,
RegistryElement element,
Expand Down Expand Up @@ -206,10 +210,5 @@ public SimpleRegistry<T> createFromElements(List<RegistryElement> elements, Clie
public ResourceLocation getRegistryKey() {
return this.baseRegistry.getRegistryKey();
}

@ApiStatus.Internal
public Class<?> getRegistryContainerClass() {
return registryContainerClass;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ public final class VersionedRegistry<T extends MappedEntity> implements IRegistr
private final Map<String, T> typeMap = new HashMap<>();
private final Map<Byte, Map<Integer, T>> typeIdMap = new HashMap<>();

//Each version registry may have a synchronized registry cache, for convenience and enhanced performance.
@Nullable
private SimpleRegistry<T> synchedRegistry = null;

public VersionedRegistry(String registry, String mappingsPath) {
this(new ResourceLocation(registry), mappingsPath);
}
Expand All @@ -59,45 +55,25 @@ public <Z extends T> Z define(String name, Function<TypesBuilderData, Z> builder
return instance;
}

@ApiStatus.Internal
public void synchronizeRegistry(SimpleRegistry<?> registry) {
this.synchedRegistry = (SimpleRegistry<T>) registry;
}

@ApiStatus.Internal
@Nullable
public SimpleRegistry<T> getSynchronizedRegistry() {
return synchedRegistry;
}

@ApiStatus.Internal
public void unloadMappings() {
this.typesBuilder.unloadFileMappings();
}

@Override
public @Nullable T getByName(String name) {
if (synchedRegistry != null) {
return synchedRegistry.getByName(name);
}
return this.typeMap.get(name);
}

@Override
public @Nullable T getById(ClientVersion version, int id) {
if (synchedRegistry != null) {
return synchedRegistry.getById(version, id);
}
int index = this.typesBuilder.getDataIndex(version);
Map<Integer, T> idMap = this.typeIdMap.get((byte) index);
return idMap.get(id);
}

@Override
public int getId(MappedEntity entity, ClientVersion version) {
if (synchedRegistry != null) {
return synchedRegistry.getId(entity, version);
}
return entity.getId(version);
}

Expand All @@ -106,17 +82,11 @@ public int getId(MappedEntity entity, ClientVersion version) {
*/
@Override
public Collection<T> getEntries() {
if (synchedRegistry != null) {
return synchedRegistry.getEntries();
}
return Collections.unmodifiableCollection(this.typeMap.values());
}

@Override
public ResourceLocation getRegistryKey() {
if (synchedRegistry != null) {
return synchedRegistry.getRegistryKey();
}
return this.registryKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.github.retrooper.packetevents.netty.NettyManager;
import com.github.retrooper.packetevents.protocol.ProtocolVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.settings.PacketEventsSettings;
import com.github.retrooper.packetevents.util.LogManager;
Expand Down Expand Up @@ -88,6 +89,11 @@ public ServerVersion getVersion() {
}
return VERSION;
}

@Override
public Object getRegistryCacheKey(User user, ClientVersion version) {
return version; // global registries, only depends on packet version
}
};

private final PlayerManagerAbstract playerManager = new PlayerManagerAbstract() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerManager;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.PEVersion;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
Expand Down Expand Up @@ -64,4 +66,9 @@ public ServerVersion getVersion() {
}
return serverVersion;
}

@Override
public Object getRegistryCacheKey(User user, ClientVersion version) {
return version; // global registries, only depends on packet version
}
}
Loading

0 comments on commit 26c30f4

Please sign in to comment.