Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add several codecs for existing capabilities. Add NbtListCollector. #1035

Closed
wants to merge 12 commits into from

Conversation

robotgryphon
Copy link
Contributor

This is a best-effort PR to add Codec support to the existing capabilities provided by NeoForge. It also adds a NBT List collector for usage with Java stream APIs.

@neoforged-pr-publishing
Copy link

  • Publish PR to GitHub Packages

Copy link
Contributor

@ChampionAsh5357 ChampionAsh5357 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basic comments and questions.

Codec.INT.fieldOf("energy").forGetter(x -> x.energy)
).apply(i, EnergyStorage::new));

public static final StreamCodec<FriendlyByteBuf, EnergyStorage> STREAM_CODEC = StreamCodec.composite(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I think this should be two stream codecs: one for sending only the energy component and the rest for all the information. Most do not generally need to increase the other parameters on the client after the initial construction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't most codec-based syncing just replace the object entirely on the client? Dealing with deltas is annoying. :D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but you shouldn't send more information than necessary. For example, you could just sync a single int representing the current energy in 90% of cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you just need to sync a single int, then use ByteBufCodecs and do it yourself?

Comment on lines 23 to 25
public static Codec<ItemStackHandler> codec(HolderLookup.Provider provider) {
return new ItemStackHandlerCodec(provider);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again with only a codec and no stream codec? I can understand the reason why, but we shouldn't assume somebody will not need to synchronize themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can wrap the existing (complicated) codec into a Stream codec if needed, but I can work on an implementation in a bit.

Copy link
Member

@Technici4n Technici4n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to reimplement the NBT methods in terms of the codec. 😉

@@ -173,4 +181,49 @@ protected void validateSlotIndex(int slot) {
protected void onLoad() {}

protected void onContentsChanged(int slot) {}

public record ItemStackHandlerCodec(HolderLookup.Provider provider) implements Codec<ItemStackHandler> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing a HolderLookup.Provider like this is incorrect because it can change. It should be retrieved using RegistryOps if you really need it, but I think that the downstream codecs should already handle it?

@robotgryphon
Copy link
Contributor Author

It would be nice to reimplement the NBT methods in terms of the codec. 😉

Having done this in a couple of my mods before, it actually ends up becoming a messy hybrid of the two approaches. It's far easier to deprecate NBT if possible, and tell people to use the codecs directly instead. (ie MC 21, remove INBTSerializable)

@ApexModder ApexModder added enhancement New (or improvement to existing) feature or request 1.20.6 Targeted at Minecraft 1.20.6 labels May 30, 2024
Copy link
Contributor

@ChampionAsh5357 ChampionAsh5357 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you are. Generally looks good.

stream.accept(decoder::process);
});

return Pair.of(finalList, input);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned value of T should be the results that could not be decoded.

Comment on lines +39 to +41
ops.getList(map.get("items")).ifSuccess(stream -> {
stream.accept(decoder::process);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the failure should be reported here rather than consumed into the ether.

Codec.INT.fieldOf("energy").forGetter(x -> x.energy)
).apply(i, EnergyStorage::new));

public static final StreamCodec<FriendlyByteBuf, EnergyStorage> STREAM_CODEC = StreamCodec.composite(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but you shouldn't send more information than necessary. For example, you could just sync a single int representing the current energy in 90% of cases.

* @return
*/
@Deprecated
public static Codec<ItemStackHandler> nbtCompatibleCodec(HolderLookup.Provider provider) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the only intended usecase for this to be serialized to NBT? If it's serialized to JSON, we may want to put some safeguards in place to prevent crashes during number list conversions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be useful to convert old NBT data into JSON for backups or something. Why guard it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue occurs when converting JSON number lists to NBT. Basically, the conversion that NBTOps uses checks for the lowest number of bytes needed to represent a number. So, if your first number starts with 8, the ops will create a byte list tag. However, if your numbers are not all represented by bytes in this example, it will crash. For example, converting the list [8, -1, 32914] will crash. So, there needs to be an extra safeguard when transforming between the two.

Copy link
Member

@Technici4n Technici4n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that having builtin codecs for the capability templates is a good idea.

However, I think that adding all this codec code to support the old serialization format is not worth it. Given that 1.21 is around the corner, shouldn't we instead change the data format to match modern expectations?

By the way, we might want to wait to see how the transfer rework affects these base implementations before updating them.

I also don't like the new provided codecs. They seem too specific to me.

* @param indexKey A key used for serializing list index information.
* @param <E> Type of elements in list.
*/
public record IndexedNonNullListCodec<E>(MapCodec<E> elementCodec, E defaultValue, Predicate<E> skipPredicate, String indexKey) implements Codec<NonNullList<E>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is oddly specific to the old serialization pattern. Shouldn't we be moving towards always serializing entries?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're willing to break old-NBT support, this could be simplified quite a bit to no longer require a MapCodec. I believe newer codecs for stuff like inventories (a prime usage of NNL) simply encode the data as a group alongside the index, rather than inlining.

*
* @return A non-null list codec for item stacks.
*/
public static Codec<NonNullList<ItemStack>> optionalItems() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item-specific code shouldn't be in this more generic class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was requested by @ChampionAsh5357 (#1035 (comment)) and I'd agree - a lot of usages of NNL is storage for items, why not provide a simple utility for that?

*
* Usage: {@code Stream.doStuff().collect(NbtListCollector.toNbtList())}
*/
public class NbtListCollector implements Collector<Tag, ListTag, ListTag> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be more broadly applicable as a static method to build an NbtList from a list of tags? (I am surprised that this doesn't already exist, but couldn't find it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm interpreting badly, but there's a difference between doing a stream collection of Tags into an NbtList (what this does) and turning a standard collection of Tags into an NbtList (non-streaming)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After all, a list of Tag being put into a ListTag is simple code; a foreach will get you there no problem?

@robotgryphon
Copy link
Contributor Author

Superceded by #1163, which adds codecs for INBTSerializable. Seeing as that is a primary usecase and stream codecs can wrap that, this can be re-visited after the transfer reworks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.20.6 Targeted at Minecraft 1.20.6 enhancement New (or improvement to existing) feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants