Skip to content

Make ^ support nbt (properly) - #2734

Open
QuImUfu wants to merge 2 commits into
EngineHub:masterfrom
QuImUfu:nbt-aware-partial-patterns
Open

Make ^ support nbt (properly)#2734
QuImUfu wants to merge 2 commits into
EngineHub:masterfrom
QuImUfu:nbt-aware-partial-patterns

Conversation

@QuImUfu

@QuImUfu QuImUfu commented Mar 14, 2025

Copy link
Copy Markdown

My implementation of #2733,
It adds ^{=nbt} support, integrated in current ^ support to allow replacing/keeping arbitrary parts of a block.

Additionally, adds {nbt} support for merging NBT tags into existing ones.

Examples:

//replace chest ^barrel will keep orientation and NBT intact and just replace the block type, keeping e.g. contents as-is.
//replace chest ^[facing=north] will make all chest face north, keeping their NBT intact.
//replace chest,barrel ^{=LootTable:"minecraft:chests/simple_dungeon"} will make both, chests and barrels contain dungeon loot, while removing any potential other NBT.
//set ^{=} deletes nbt from selection. (does not work for LootTable tags due to a bug in vanilla MC I can't link here or find again because mojangs bug tracker is completely broken)
//replace decorated_pot ^{LootTable:"minecraft:chests/simple_dungeon"} will make pots contain dungeon loot while keeping their pattern.
...

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from a572827 to 9c974cf Compare March 15, 2025 08:34
@QuImUfu
QuImUfu marked this pull request as ready for review March 15, 2025 08:53
@QuImUfu
QuImUfu requested a review from a team as a code owner March 15, 2025 08:53

@me4502 me4502 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this. I've left a comment around API breakages. I haven't reviewed the actual parser itself as the diff is broken currently by the rename.

Also as this is adding behaviour, this should be targeting master, not 7.3.x.

import java.util.stream.Stream;


public class PartiallyApplyingPatternParser extends InputParser<Pattern> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Renaming this class would be an API break, so it'd need to retain its original name, or duplicate it and deprecate the old one.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

What is the rule for API breaks/changes? Am I allowed to change behaviour in a potentially breaking way (this is) and still maintain the same name/API? Or do I need to provide a fully backwards-compatible implementation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I created a deprecated legacy version, that should behave like before. Do I need to do the same for the two Patterns that I changed?
Is there some documentation on what needs to stay API compatible?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Typically as long as it's binary compatible and works vaguely as expected it should be fine. Slight behaviour changes can happen IMO.

@Override
public BaseBlock applyBlock(BlockVector3 position) {
BlockState oldBlock = getExtent().getBlock(position);
BaseBlock oldBlock = getExtent().getFullBlock(position);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you tested how well this performs? This fundamentally means the pattern is doing a lot more work per-block.

Also, have you tested how this behaves when set to another block with a block entity of a different type, with conflicting data? Eg, change a chest with items to a jukebox and make sure it doesn't explode?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It does a lot more (in some cases unavoidable) work. I did not do large scale testing where performance issues would become apparent. I will think about how we can avoid incurring a performance penalty (if there is one, I'll test that) for blocks that can't have NBT or when NBT is overwritten anyway.

If it breaks something, it can already break with the vanilla /data command. But it and the data command, in fact, are able to break a lot of (as far as I have tested only non-vanilla) blocks, and AFAICT, there is not much we could do about it. We could maintain a gigantic database of sanitizers, one for every block with tile entity in every mod, plead to mod devs to sanitize their NBT properly, or we could prevent the game from flushing to disk until after the command finished, to prevent game-cashing NBT from corrupting worlds, at the expense of possibly prohibitive RAM use.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I wasn't able to detect any performance impact, which honestly surprised me. Not sure if my testing was flawed.
The performance impact seems, at least on my PC, to be completely drowned by IO for huge edits, and be undetectably small for smaller ones.
If you manage to find a scenario in which my changes have a significant performance impact, I'd be happy to take another look.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The case where this would likely have the most impact is when the selection has a large amount of complex NBT data in it, as the change basically leads to reading that data from the world.

Although tbh AFAICT this change here is mostly a bugfix anyway, so if it's performant it might be fine to PR separately to version/7.4.x

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I tried fitting as many barrels with items in the loaded chunks as my heap size allows (16GiB) to avoid the IO bottlenecks I experienced in my previous testing, and replacing them with and without the changes in this PR. And while the time till the replaced ... blocks message didn't meaningfully change, the time till the game became responsive again did significantly increase with these changes (between 30% and 50%).

I see a similar slowdown merging NBT vs. setting NBT. So it seems that reading NBT for each block indeed can cause a significant difference, that will likely increase with more complex NBT (e.g. complex items in the barrels).

I will try to include an optimisation that makes the respective patterns skip reading & writing NBT if that is unnecessary. This should at least allow users experiencing performance issues to just specify {=} and get the previous performance back, at the cost of the previous behaviour.

@QuImUfu
QuImUfu changed the base branch from version/7.3.x to master March 16, 2025 20:06
@octylFractal

octylFractal commented Mar 16, 2025

Copy link
Copy Markdown
Member

I have concerns about the exact syntax here, I don't think we should allow ^{} to be deletion / require a prefix comma -- this is inconsistent with the existing function.

As it stands, ^ is purely overwrite/additive. ^foo means "set block type to foo keeping all applicable state entries", ^[x=y] means "set the block state to the previous state with x set to y". I think it should remain that way for NBT as well, which would imply that ^{} is essentially a no-op and should be illegal syntax, as ^[] is today. If we want to add a variant that clears existing state, I think it should use a different prefix, or perhaps an extended one such as ^= ("equals" to imply removal of unwritten state).

@QuImUfu

QuImUfu commented Mar 16, 2025

Copy link
Copy Markdown
Author

While I understand you concern, I have some concerns with merge only for ^:

The current syntax allows for simply including {} to sidestep any NBT handling and (if implemented correctly, I probably will have to fix that) prevent any performance penalty.

If we do merge only, we need to add a different way to have ^[..=..] and ^.. keep (with related performance penalty) or drop NBT.

I'd suggest following to make it more consistent, without introducing a new prefix:
Changing {,..} to just {..} and {..} to {=..} or ={..} (the second being slightly confusing in combination with other overrides) and keeping merging as default this way. But that'd still include new syntax for ^ only applicable to NBT (we could also add ^[=..] but that would be a mostly useless feature just for consistencies’ sake).

If you have a different idea on how that could be implemented more consistently, I'm very open to suggestions.

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from 9c974cf to 4f58081 Compare March 21, 2025 12:51
@QuImUfu

QuImUfu commented Mar 21, 2025

Copy link
Copy Markdown
Author

I have changed it to merge by default and set with {=...}.

@QuImUfu
QuImUfu requested review from me4502 and octylFractal April 5, 2025 07:15
@madebyisaacr

Copy link
Copy Markdown

Does this PR also preserve sign and hanging sign text? If so, it will close #2546

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from 4f58081 to e2f2e19 Compare July 31, 2026 14:08
@QuImUfu

QuImUfu commented Jul 31, 2026

Copy link
Copy Markdown
Author

Does this PR also preserve sign and hanging sign text? If so, it will close #2546

Yes. Along with preserving chest/barrel contents, pot contents ...

Updated the PR to current master.

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from e2f2e19 to 5f39ada Compare August 3, 2026 22:04
@QuImUfu

QuImUfu commented Aug 3, 2026

Copy link
Copy Markdown
Author

Updated the MR to fix an issue with the command mentioned in #2998

I am not quite confident in the changes in StringUtils.parseListInQuotes, because I am unsure of the original intent of that method.

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from a382de1 to cda2816 Compare August 4, 2026 05:37

@me4502 me4502 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry it's been so long – I missed the initial changes you made after the reviews, and then with the uptick in MC update frequency/etc I haven't had a chance to look at the PR backlog outside of ones I was actively tracking.

I've replied to the comments, otherwise I'll let @octylFractal review the actual parser behaviour here. I'm a little apprehensive about the complexity of this parser at this point

@Override
public BaseBlock applyBlock(BlockVector3 position) {
BlockState oldBlock = getExtent().getBlock(position);
BaseBlock oldBlock = getExtent().getFullBlock(position);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The case where this would likely have the most impact is when the selection has a large amount of complex NBT data in it, as the change basically leads to reading that data from the world.

Although tbh AFAICT this change here is mostly a bugfix anyway, so if it's performant it might be fine to PR separately to version/7.4.x

import java.util.stream.Stream;


public class PartiallyApplyingPatternParser extends InputParser<Pattern> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Typically as long as it's binary compatible and works vaguely as expected it should be fine. Slight behaviour changes can happen IMO.

@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch 2 times, most recently from 68cef48 to cce709b Compare August 4, 2026 21:54
@QuImUfu
QuImUfu force-pushed the nbt-aware-partial-patterns branch from cce709b to a7abc4f Compare August 4, 2026 21:56
@QuImUfu

QuImUfu commented Aug 4, 2026

Copy link
Copy Markdown
Author

I added a Test to increase my confidence in the implementation of the PartiallyApplyingPatternParser, however, I had to add a couple of public getters to the Patterns for validation, and I am not sure if that is an acceptable in this project. If it isn't, I am open to any suggestions on how to do it better.

Also, can I add a component test spanning the entire parsing logic? I am unsure if the Parsers interact correctly for nested/mixed Patterns.
Or are only Unit Tests permitted?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants