1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2026-01-06 15:41:50 +00:00

Implement JavaRegistry(Provider) for game tests, get basic test for component hashing working

This commit is contained in:
Eclipse
2025-10-24 00:23:50 +00:00
parent a942ac53b1
commit c60c367ed1
12 changed files with 461 additions and 52 deletions

View File

@@ -40,7 +40,10 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.HashOps;
import org.geysermc.geyser.gametest.registries.GameTestJavaRegistryProvider;
import org.geysermc.geyser.item.hashing.DataComponentHashers;
import org.geysermc.mcprotocollib.protocol.codec.MinecraftTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
@@ -48,39 +51,42 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GeyserComponentHashTestInstance<T> extends GameTestInstance {
public class GeyserComponentHashTestInstance extends GameTestInstance {
private static final MapCodec<TypedDataComponent<?>> TYPED_COMPONENT_CODEC = DataComponentType.PERSISTENT_CODEC
.dispatchMap("component", TypedDataComponent::type, GeyserComponentHashTestInstance::typedComponentCodec);
public static final MapCodec<GeyserComponentHashTestInstance<?>> CODEC = TYPED_COMPONENT_CODEC.xmap(GeyserComponentHashTestInstance::new,
instance -> instance.testValue);
private static final MapCodec<List<TypedDataComponent<?>>> TYPED_COMPONENT_CODEC = DataComponentType.PERSISTENT_CODEC
.dispatchMap("component", list -> list.getFirst().type(), GeyserComponentHashTestInstance::typedComponentListCodec);
public static final MapCodec<GeyserComponentHashTestInstance> CODEC = TYPED_COMPONENT_CODEC.xmap(GeyserComponentHashTestInstance::new,
instance -> instance.testCases);
private final TypedDataComponent<T> testValue;
private final List<TypedDataComponent<?>> testCases;
public GeyserComponentHashTestInstance(TypedDataComponent<T> testValue) {
public GeyserComponentHashTestInstance(List<TypedDataComponent<?>> testCases) {
// TODO use default vanilla test environment
super(new TestData<>(Holder.direct(new TestEnvironmentDefinition.AllOf(List.of())),
ResourceLocation.withDefaultNamespace("empty"), 1, 1, true));
this.testValue = testValue;
this.testCases = testCases;
}
@Override
public void run(@NotNull GameTestHelper helper) {
// Encode vanilla component to buffer
RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), helper.getLevel().registryAccess());
TypedDataComponent.STREAM_CODEC.encode(buffer, testValue);
// Read with MCPL
int id = MinecraftTypes.readVarInt(buffer);
DataComponent<?, ?> mcplComponent = DataComponentTypes.from(id).readDataComponent(buffer);
// Hash both and compare
RegistryOps<HashCode> ops = RegistryOps.create(HashOps.CRC32C_INSTANCE, helper.getLevel().registryAccess());
int expected = testValue.encodeValue(ops).getOrThrow().asInt();
//int geyser = DataComponentHashers.hash(session, mcplComponent).asInt();
int geyser = 0;
helper.assertValueEqual(expected, geyser, Component.literal("Hash for component " + testValue));
for (TypedDataComponent<?> testCase : testCases) {
// Encode vanilla component to buffer
RegistryFriendlyByteBuf buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), helper.getLevel().registryAccess());
TypedDataComponent.STREAM_CODEC.encode(buffer, testCase);
// Read with MCPL
int id = MinecraftTypes.readVarInt(buffer);
DataComponent<?, ?> mcplComponent = DataComponentTypes.from(id).readDataComponent(buffer);
// Hash both and compare
int expected = testCase.encodeValue(ops).getOrThrow().asInt();
GameTestJavaRegistryProvider registries = new GameTestJavaRegistryProvider(helper.getLevel().registryAccess());
int geyser = DataComponentHashers.hash(registries, mcplComponent).asInt();
helper.assertValueEqual(expected, geyser, Component.literal("Hash for component " + testCase));
}
// Succeed if nothing was thrown
helper.succeed();
@@ -97,7 +103,17 @@ public class GeyserComponentHashTestInstance<T> extends GameTestInstance {
return Component.literal("Geyser Data Component Hash Test");
}
private static <T> MapCodec<TypedDataComponent<T>> typedComponentCodec(DataComponentType<T> component) {
return component.codecOrThrow().fieldOf("value").xmap(value -> new TypedDataComponent<>(component, value), TypedDataComponent::value);
// Generics are NOT friendly!!!
@SuppressWarnings({"rawtypes", "unchecked"})
private static MapCodec<List<TypedDataComponent<?>>> typedComponentListCodec(DataComponentType component) {
return ExtraCodecs.compactListCodec(component.codecOrThrow()).fieldOf("value")
.xmap(
values -> ((List<?>) values).stream()
.map(testCase -> new TypedDataComponent(component, testCase))
.map(testCase -> (TypedDataComponent<?>) testCase)
.toList(),
typedComponents -> ((List<?>) typedComponents).stream()
.map(testCase -> ((TypedDataComponent<?>) testCase).value())
.toList());
}
}

View File

@@ -0,0 +1,234 @@
package org.geysermc.geyser.gametest.registries;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.bytes.ByteList;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
// Stolen from mappings-gen: is there a better way to do this?
public class CloudburstNbtOps implements DynamicOps<Object> {
public static final CloudburstNbtOps INSTANCE = new CloudburstNbtOps();
@Override
public Object empty() {
return null;
}
@Override
public Object emptyMap() {
return NbtMap.EMPTY;
}
@Override
public Object emptyList() {
return NbtList.EMPTY;
}
@Override
public <U> U convertTo(DynamicOps<U> outOps, Object input) {
if (input == empty()) {
return outOps.empty();
}
NbtType<?> type = NbtType.byClass(input.getClass());
return switch (type.getEnum()) {
case END -> outOps.empty();
case BYTE -> outOps.createByte((Byte) input);
case SHORT -> outOps.createShort((Short) input);
case INT -> outOps.createInt((Integer) input);
case LONG -> outOps.createLong((Long) input);
case FLOAT -> outOps.createFloat((Float) input);
case DOUBLE -> outOps.createDouble((Double) input);
case BYTE_ARRAY -> outOps.createByteList(ByteBuffer.wrap((byte[]) input));
case STRING -> outOps.createString((String) input);
case LIST -> this.convertList(outOps, input);
case COMPOUND -> this.convertMap(outOps, input);
case INT_ARRAY -> outOps.createIntList(Arrays.stream((int[]) input));
case LONG_ARRAY -> outOps.createLongList(Arrays.stream((long[]) input));
};
}
@Override
public DataResult<Number> getNumberValue(Object input) {
if (input instanceof Number) {
return DataResult.success((Number) input);
}
return DataResult.error(() -> "Input is not a number: " + input);
}
@Override
public Number getNumberValue(Object input, Number defaultValue) {
return input instanceof Number ? (Number) input : defaultValue;
}
@Override
public Object createNumeric(Number i) {
return i;
}
@Override
public DataResult<String> getStringValue(Object input) {
if (input instanceof String) {
return DataResult.success((String) input);
}
return DataResult.error(() -> "Input is not a string: " + input);
}
@Override
public Object createString(String value) {
return value;
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public DataResult<Object> mergeToList(Object list, Object value) {
if (list == empty()) {
NbtType<?> type = NbtType.byClass(value.getClass());
return DataResult.success(new NbtList(type, value));
}
if (list instanceof NbtList<?> nbtList) {
List listBuilder = new ArrayList<>(nbtList);
listBuilder.add(value);
return DataResult.success(new NbtList(nbtList.getType(), listBuilder));
}
return DataResult.error(() -> "mergeToList was not called with a list: " + list);
}
@Override
public DataResult<Object> mergeToList(Object list, List<Object> values) {
if (list == empty()) {
if (values.isEmpty()) {
return DataResult.success(emptyList());
}
NbtType<?> type = NbtType.byClass(values.get(0).getClass());
return DataResult.success(new NbtList(type, values));
}
if (list instanceof NbtList<?> nbtList) {
if (values.isEmpty()) {
return DataResult.success(nbtList);
}
if (nbtList.isEmpty()) {
return DataResult.success(new NbtList(NbtType.byClass(values.get(0).getClass()), values));
}
List listBuilder = new ArrayList<>(nbtList);
listBuilder.addAll(values);
return DataResult.success(new NbtList(nbtList.getType(), listBuilder));
}
return DataResult.error(() -> "mergeToList was not called with a list: " + list);
}
@Override
public DataResult<Object> mergeToMap(Object map, Object key, Object value) {
if (!(map instanceof NbtMap) && map != null) {
return DataResult.error(() -> "mergeToMap called with not a map: " + map, map);
} else if (!(key instanceof String)) {
return DataResult.error(() -> "key is not a string: " + key, map);
} else {
NbtMapBuilder builder;
if (map instanceof NbtMap nbtMap) {
builder = nbtMap.toBuilder();
} else {
builder = NbtMap.builder();
}
builder.put((String) key, value);
return DataResult.success(builder.build());
}
}
@Override
public DataResult<Stream<Pair<Object, Object>>> getMapValues(Object input) {
if (input instanceof NbtMap nbt) {
return DataResult.success(nbt.entrySet().stream().map(entry -> Pair.of(entry.getKey(), entry.getValue())));
} else {
return DataResult.error(() -> "Input was not NbtMap");
}
}
@Override
public Object createMap(Stream<Pair<Object, Object>> map) {
NbtMapBuilder builder = NbtMap.builder();
map.forEach(pair -> builder.put((String) pair.getFirst(), pair.getSecond()));
return builder.build();
}
@Override
@SuppressWarnings("rawtypes")
public DataResult<Stream<Object>> getStream(Object input) {
if (input instanceof NbtList<?> list) {
return DataResult.success((Stream<Object>) list.stream());
}
if (input instanceof int[] ints) {
return DataResult.success(Arrays.stream(ints).mapToObj(Integer::valueOf));
}
if (input instanceof long[] longs) {
return DataResult.success(Arrays.stream(longs).mapToObj(Long::valueOf));
}
if (input instanceof byte[] bytes) {
ByteList byteList = new ByteArrayList(bytes);
return DataResult.success((Stream) byteList.stream());
}
return DataResult.error(() -> "Was not a list");
}
@Override
public DataResult<IntStream> getIntStream(Object input) {
if (input instanceof int[] ints) {
return DataResult.success(Arrays.stream(ints));
} else {
return DynamicOps.super.getIntStream(input);
}
}
@Override
public Object createIntList(IntStream input) {
return input.toArray();
}
@Override
public DataResult<LongStream> getLongStream(Object input) {
if (input instanceof long[] longs) {
return DataResult.success(Arrays.stream(longs));
} else {
return DynamicOps.super.getLongStream(input);
}
}
@Override
public Object createLongList(LongStream input) {
return input.toArray();
}
@Override
public Object createList(Stream<Object> input) {
final List<?> list = input.toList();
if (list.isEmpty()) {
return emptyList();
}
NbtType<?> type = NbtType.byClass(list.getFirst().getClass());
return new NbtList(type, list);
}
@Override
public Object remove(Object input, String key) {
if (input instanceof NbtMap map) {
NbtMapBuilder builder = map.toBuilder();
builder.remove(key);
return builder.build();
} else {
return input;
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.gametest.registries;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DynamicOps;
import net.kyori.adventure.key.Key;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.cloudburstmc.nbt.NbtMap;
import org.geysermc.geyser.session.cache.RegistryCache;
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
import org.geysermc.geyser.session.cache.registry.RegistryEntryData;
import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.ToIntFunction;
public class GameTestJavaRegistry<T> implements JavaRegistry<T> {
private final List<RegistryEntryData<T>> entries;
public GameTestJavaRegistry(RegistryAccess registries, JavaRegistryKey<T> registryKey) {
Registry<?> registry = registries.lookupOrThrow(geyserKeyToMojangKey(registryKey));
entries = convertRegistryData(registryKey, registries, registry);
}
@Override
public List<RegistryEntryData<T>> entries() {
return entries;
}
private static <Mojang, Geyser> List<RegistryEntryData<Geyser>> convertRegistryData(JavaRegistryKey<Geyser> registryKey, RegistryAccess registries, Registry<Mojang> registry) {
DynamicOps<Object> nbtOps = registries.createSerializationContext(CloudburstNbtOps.INSTANCE);
Codec<Mojang> codec = getSyncedRegistryData(registry.key()).elementCodec();
//noinspection unchecked
RegistryCache.RegistryReader<Geyser> reader = (RegistryCache.RegistryReader<Geyser>) RegistryCache.READERS.get(registryKey);
ToIntFunction<Key> keyIdFunction = key -> registry.getId(registry.getValue(keyToResourceLocation(key)));
List<RegistryEntryData<Geyser>> entries = new ArrayList<>();
for (Mojang entry : registry) {
int id = registry.getIdOrThrow(entry);
Key key = resourceLocationToKey(Objects.requireNonNull(registry.getKey(entry)));
NbtMap encoded = (NbtMap) codec.encodeStart(nbtOps, entry).getOrThrow();
Geyser mapped = reader.read(new RegistryEntryContext(new RegistryEntry(key, encoded), keyIdFunction, Optional.empty()));
entries.add(new RegistryEntryData<>(id, key, mapped));
}
return List.copyOf(entries);
}
private static <T> RegistryDataLoader.RegistryData<T> getSyncedRegistryData(ResourceKey<? extends Registry<T>> registry) {
//noinspection unchecked
return RegistryDataLoader.SYNCHRONIZED_REGISTRIES.stream()
.filter(data -> data.key() == registry)
.map(data -> (RegistryDataLoader.RegistryData<T>) data)
.findFirst()
.orElseThrow(() -> new IllegalStateException(registry + " is not a network synced registry"));
}
private static ResourceKey<? extends Registry<?>> geyserKeyToMojangKey(JavaRegistryKey<?> key) {
return ResourceKey.createRegistryKey(keyToResourceLocation(key.registryKey()));
}
private static ResourceLocation keyToResourceLocation(Key key) {
return ResourceLocation.fromNamespaceAndPath(key.namespace(), key.value());
}
private static Key resourceLocationToKey(ResourceLocation location) {
//noinspection PatternValidation
return Key.key(location.getNamespace(), location.getPath());
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.gametest.registries;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.core.RegistryAccess;
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
import org.geysermc.geyser.session.cache.registry.JavaRegistryKey;
import org.geysermc.geyser.session.cache.registry.JavaRegistryProvider;
import java.util.Map;
public class GameTestJavaRegistryProvider implements JavaRegistryProvider {
private final RegistryAccess registries;
private final Map<JavaRegistryKey<?>, GameTestJavaRegistry<?>> registryCache = new Object2ObjectOpenHashMap<>();
public GameTestJavaRegistryProvider(RegistryAccess registries) {
this.registries = registries;
}
@Override
public <T> JavaRegistry<T> registry(JavaRegistryKey<T> registryKey) {
//noinspection unchecked
return (JavaRegistry<T>) registryCache.computeIfAbsent(registryKey, key -> new GameTestJavaRegistry<>(registries, key));
}
}

View File

@@ -0,0 +1,20 @@
{
"type": "geyser:component_hash",
"component": "minecraft:custom_data",
"value": [
{
"hello": "g'day",
"nice?": false,
"coolness": 100,
"geyser": {
"is": "very cool"
},
"a list": [
["in a list"]
]
},
{
"example_key": "second test case!"
}
]
}

View File

@@ -1,15 +0,0 @@
{
"type": "geyser:component_hash",
"component": "minecraft:custom_data",
"value": {
"hello": "g'day",
"nice?": false,
"coolness": 100,
"geyser": {
"is": "very cool"
},
"a list": [
["in a list"]
]
}
}

View File

@@ -1,7 +1,7 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.geysermc.geyser.platform.gametest.mixin",
"package": "org.geysermc.geyser.gametest.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [],
"server": [],

View File

@@ -58,7 +58,7 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit
this.setServer(server);
onGeyserEnable();
});
} else {
} else if (!GeyserFabricPlatform.isGameTestServer()) {
ClientLifecycleEvents.CLIENT_STOPPING.register(($)-> {
onGeyserShutdown();
});

View File

@@ -701,7 +701,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
runIfNonNull(erosionUnixListener, UnixSocketClientListener::close);
ResourcePackLoader.clear();
CodeOfConductManager.getInstance().save();
if (platformType != InternalPlatformType.GAMETEST) {
CodeOfConductManager.getInstance().save();
}
this.setEnabled(false);
}

View File

@@ -42,6 +42,7 @@ import org.geysermc.geyser.item.components.Rarity;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.session.cache.registry.JavaRegistryProvider;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
@@ -307,9 +308,9 @@ public class DataComponentHashers {
return hasher;
}
public static <T> HashCode hash(GeyserSession session, DataComponentType<T> component, T value) {
public static <T> HashCode hash(JavaRegistryProvider registries, DataComponentType<T> component, T value) {
try {
return hasher(component).hash(value, new MinecraftHashEncoder(session.getRegistryCache()));
return hasher(component).hash(value, new MinecraftHashEncoder(registries));
} catch (Exception exception) {
GeyserImpl.getInstance().getLogger().error("Failed to hash item data component " + component.getKey() + " with value " + value + "!");
GeyserImpl.getInstance().getLogger().error("This is a Geyser bug, please report this!");
@@ -317,8 +318,8 @@ public class DataComponentHashers {
}
}
public static <V, T extends DataComponentType<V>> HashCode hash(GeyserSession session, DataComponent<V, T> component) {
return hash(session, component.getType(), component.getValue());
public static <V, T extends DataComponentType<V>> HashCode hash(JavaRegistryProvider registries, DataComponent<V, T> component) {
return hash(registries, component.getType(), component.getValue());
}
public static HashedStack hashStack(GeyserSession session, ItemStack stack) {
@@ -339,7 +340,7 @@ public class DataComponentHashers {
} else if (component.getValue().getValue() == null) {
removals.add(component.getKey());
} else {
hashedAdditions.put(component.getKey(), hash(session, (DataComponentType) component.getKey(), component.getValue().getValue()).asInt());
hashedAdditions.put(component.getKey(), hash(session.getRegistryCache(), (DataComponentType) component.getKey(), component.getValue().getValue()).asInt());
}
}
return new HashedStack(stack.getId(), stack.getAmount(), hashedAdditions, removals);
@@ -540,7 +541,7 @@ public class DataComponentHashers {
}
private static <T> void testHash(GeyserSession session, DataComponentType<T> component, T value, int expected) {
int got = hash(session, component, value).asInt();
int got = hash(session.getRegistryCache(), component, value).asInt();
System.out.println("Testing hashing component " + component.getKey() + ", expected " + expected + ", got " + got + " " + (got == expected ? "PASS" : "ERROR"));
}
}

View File

@@ -186,7 +186,7 @@ public final class RegistryCache implements JavaRegistryProvider {
entry = new RegistryEntry(entry.getId(), localRegistry.get(entry.getId()));
}
RegistryEntryContext context = new RegistryEntryContext(entry, entryIdMap, Optional.of(session));
RegistryEntryContext context = new RegistryEntryContext(entry, key -> entryIdMap.getOrDefault(key, -1), Optional.of(session));
// This is what Geyser wants to keep as a value for this registry.
T cacheEntry = reader.read(context);
if (cacheEntry == null) {

View File

@@ -25,8 +25,8 @@
package org.geysermc.geyser.session.cache.registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.Optional;
import java.util.function.ToIntFunction;
import net.kyori.adventure.key.Key;
import org.cloudburstmc.nbt.NbtMap;
@@ -38,16 +38,16 @@ import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry;
* Used to store context around a single registry entry when reading said entry's NBT.
*
* @param entry the registry entry being read.
* @param keyIdMap a map for each of the resource location's in the registry and their respective network IDs.
* @param keyIdFunction a function that turns a resource location in the registry to its respective network ID.
* @param session the Geyser session. Only empty during testing.
*/
public record RegistryEntryContext(RegistryEntry entry, Object2IntMap<Key> keyIdMap, Optional<GeyserSession> session) {
public record RegistryEntryContext(RegistryEntry entry, ToIntFunction<Key> keyIdFunction, Optional<GeyserSession> session) {
// TODO: not a fan of this. With JavaRegistryKey#key now being a thing, I'd rather have that always used, so that registry readers won't have to worry
// about using the right method. This would require pre-populating all data-driven registries with default (probably null) values before actually decoding the data from the registy packet.
// This could also be helpful in the feature when a data-driven registry reader needs to use an element from another data-driven registry
public int getNetworkId(Key registryKey) {
return keyIdMap.getOrDefault(registryKey, -1);
return keyIdFunction.applyAsInt(registryKey);
}
public Key id() {