IMG.BANNERISO 100 f/2.8 1/125s | 50mm Lens WB 5600K | EV +0.3 AF-S RAW
// Blocks with block entities
Translation may be out of date
04 Block Entities
1 What Are Block Entities
Blocks store data using a limited set of predefined BlockStates, render models through default rendering behavior, and execute logic via update and scheduling systems.
However, this approach has limitations and cannot directly support certain block behaviors required for game interactions.
Block entities solve this problem. They give ordinary blocks three key capabilities:
Store data using NBT
Custom rendering behavior
Update every tick
1.1 Block Entity Contents
Block entities vary by block type, but a basic block entity typically contains:
type
world (the world it exists in)
pos (position)
removed (whether it has been removed)
cachedState (cached state of the corresponding block)
1.2 Block Entities and Blocks
Block entities are bound to blocks in the world. Each block position can only have one block entity instance.
Only blocks that are specifically declared can have block entities attached.
Block entities are added and removed alongside their blocks. However, through techniques like update suppression, you can retain a block entity even after its block is removed.
2 Notable Blocks with Block Entities
2.1 Hopper
In Minecraft, the hopper is a block entity that handles automated item transfer.
2.1.1 Hopper Block Entity Data
A hopper block entity stores the following information:
inventory storage space (5 slots)
transferCooldown cooldown (default is -1)
lastTickTime world time of the last tick
facing direction
2.1.2 Hopper Block Entity Functions
Transfer items to target container
Extract items from container
Attempt to pick up item entities
2.1.3 Hopper Block Entity Workflow
Every game tick, the hopper executes these steps:
// IMG_LOAD
Now, let's look at the detailed logic for pulling and outputting.
When the hopper is ready to operate, it first attempts to output to the container it's pointing at. Then it tries to extract items from above. Finally, if either operation succeeds, it sets the cooldown and syncs its data.
// IMG_LOAD
Let's look at output and pull separately.
First, output...
The output operation uses the insert() method, which transfers items from the hopper to the target container and returns a boolean indicating success. If the transfer succeeds, the hopper enters cooldown.
Basic output logic:
Check if the target container is available
If the target container is full, output fails.
If the target container has available slots, attempt to transfer items.
Attempt to output items
// IMG_LOAD
When calling transfer() to move items, it returns an item stack:
An empty return value means the items were fully transferred, so output succeeds.
A non-empty return value means the items could not be fully transferred, so output fails.
// IMG_LOAD
That covers output. Now let's look at pulling.
The pull logic is similar to output, but in the opposite direction:
During output: the hopper transfer()s items to the target container.
During pull: a containertransfer()s items to the hopper.
Since we'll cover transfer() in detail next, we won't repeat it here.
// IMG_LOAD
transfer() is the core method that handles item transfer in hoppers. It takes the following parameters:
from: item source (can be a hopper or other container)
to: transfer target (can be a container or another hopper)
itemStack: the item stack to transfer
slot: target slot in the target container
Here's how it works:
// IMG_LOAD
That covers the basic hopper mechanism. This foundation will help with the chapters ahead.
2.1.4 Moving Piston
Another important block is moving_piston, commonly known as B36.
2.1.5 Moving Piston Block Entity Data
A moving piston block entity stores the following information:
pushedBlock the block being pushed
facing direction
extending whether the piston is extending (determines animation direction)
whether this is the piston head retracting
2.1.6 Moving Piston Block Entity Functions
Handle piston pushing entities
Update animation progress
If progress>=1, replace MOVING_PISTON with the block stored in pushedBlock
Render animation on the client
2.1.7 Moving Piston Block Entity Workflow
For details, see .
Decrease cooldown (cd)
If cd is still greater than 0, the hopper skips all pull and output operations and stops processing.
If cd reaches zero, the hopper begins attempting output and pull operations.
Execute item transfer logic
First attempt output (place items into target container)
Then attempt pull (extract items from container above)
If either succeeds, the hopper resets the cooldown and syncs its data.
Iterate through the hopper's item slots, attempting to transfer items to the target container one by one.
If any transfer succeeds, return "success"; otherwise return "failure".
Check the target slot
If the slot is empty, place itemStack directly into it and clear itemStack.
If the slot already has items of the same type with remaining space, merge the items and clear itemStack.
Determine if the transfer succeeded
After transfer, if itemStack is empty, the transfer succeeded; otherwise it failed.
Special timing for hopper-to-hopper transfers
If the target is another hopper, the target hopper receives 8gt of cooldown.
If the target hopper to has already ticked before the source hopper from, the target hopper to only receives 7gt of cooldown.
Note that this only occurs when the target hopper is empty.
Additionally, even if from ticks after to and applies 8gt of cooldown, to will immediately subtract 1 when it ticks next, leaving only 7gt of cooldown.
source
progress current movement progress
savedWorldTime world time when logic was last executed
ADVANCED
3 Timing Between Block EntitiesADVANCED
Block entity ticking is managed by two lists in LevelChunk:
pendingBlockEntityTickers: block entities added during the current tick cycle.
blockEntityTickers: block entities scheduled for ticking.
Both lists are ArrayLists, so traversal order depends on insertion order. Here's the process:
tickBlockEntities() first appends pendingBlockEntityTickers to blockEntityTickers, then clears pendingBlockEntityTickers.
When iterating through blockEntityTickers, removed entries (isRemoved() returns true) are deleted; others have their method called.
New block entities are added via addBlockEntityTicker(). If ticking is in progress (tickingBlockEntities == true), they go to pendingBlockEntityTickers; otherwise to blockEntityTickers.
When unloading, block entities in the same chunk are stored in a Map<BlockPos, BlockEntity>. On reload, the map is iterated and each entity is added to blockEntityTickers via addBlockEntityTicker(), so execution order follows BlockPos.hashCode() order.
Across chunks, block entity order depends on which chunk loads first.
TL;DR:
Before unloading, block entity tick order matches placement order. After reloading, order within a chunk depends on position hash; across chunks, it depends on chunk load order.
Here's the relevant code (with some irrelevant content removed):
java
50 LINES||
// Used to temporarily store block entities dynamically added during the tickBlockEntities processprivate final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList
// SYNTAX_HIGHLIGHT
();
// List of block entities to be ticked
protected final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList();