Changing a shelf's contents through anything other than direct player interaction leaves clients rendering the old contents until the chunk is resent. We hit it twice on 26.2: a plugin pulling items out of a shelf through the Bukkit inventory API, and inserting items from console with /item replace block (so the vanilla command path desyncs too).
Reproduce:
- Place an oak_shelf and put an item in it so it renders
- From console: minecraft:item replace block container.0 with minecraft:air
- Server NBT is updated, but the client keeps rendering the item until the chunk reloads
Same result through the API: ((Shelf) block.getState()).getInventory().setItem(0, null).
Cause as far as we can tell: ShelfBlockEntity setItem paths only call setChanged(), while the sendBlockUpdated broadcast only happens in ShelfBlock's player interaction code. Chiseled bookshelves have the sibling problem, their slot occupied blockstate properties never move on API edits.
CraftJukebox.update() already handles exactly this class of problem for jukeboxes, so a similar resync on shelf mutations seems like the natural fix. The workaround plugins use today is a fresh getState().update(true, false) after every mutation, which costs a state creation per edit.
Found while working on CraftBook's shelf support (EngineHub/CraftBook#1376). Written with AI assistance.
Changing a shelf's contents through anything other than direct player interaction leaves clients rendering the old contents until the chunk is resent. We hit it twice on 26.2: a plugin pulling items out of a shelf through the Bukkit inventory API, and inserting items from console with /item replace block (so the vanilla command path desyncs too).
Reproduce:
Same result through the API: ((Shelf) block.getState()).getInventory().setItem(0, null).
Cause as far as we can tell: ShelfBlockEntity setItem paths only call setChanged(), while the sendBlockUpdated broadcast only happens in ShelfBlock's player interaction code. Chiseled bookshelves have the sibling problem, their slot occupied blockstate properties never move on API edits.
CraftJukebox.update() already handles exactly this class of problem for jukeboxes, so a similar resync on shelf mutations seems like the natural fix. The workaround plugins use today is a fresh getState().update(true, false) after every mutation, which costs a state creation per edit.
Found while working on CraftBook's shelf support (EngineHub/CraftBook#1376). Written with AI assistance.