Skip to content

Instantly share code, notes, and snippets.

@SpaceManiac
Last active November 13, 2017 17:35
Show Gist options
  • Save SpaceManiac/5ee244ffa6d510e942b3 to your computer and use it in GitHub Desktop.
Save SpaceManiac/5ee244ffa6d510e942b3 to your computer and use it in GitHub Desktop.
Chunk Send Event / Send Chunk Update API rough sketch & notes
Chunk Send Event / Send Chunk Update API rough sketch & notes
<Wolvereness> SpaceManiac: 1/3 of the problem is addresssing where to hook this, 1/3 of the problem
is addressing how to have a mutable block of memory with sensible API, 1/3 of the problem is
address performance concerns like bulk and compression
JIRA Ticket: BUKKIT-5642 Chunk Send Event and Methods
Vaguely related tickets:
BUKKIT-4114: Fast mass block update API
BUKKIT-4802: World.refreshChunk mades mobs unattackable
BUKKIT-4183: update blocks within a chunk clientside
API SIDE
========
Event: ChunkTransmitEvent? ChunkSendEvent? similar name
PlayerChunkSendEvent?
Should it be a PlayerEvent or a ChunkEvent? (I'm leaning Chunk, but Player may make more sense)
Bulk event? Would really just be a list of individual events... depends whether advantage of only
emitting one event for bulk sends outweights the API complexity & need to listen for two events.
Otherwise would just emit many individual events.
Final state: Chunk, Player, maybe ChunkSnapshot
Mutable state: Cancelled, probably MutableChunkSnapshot
Representation: essentially a MutableChunkSnapshot, like ChunkSnapshot but mutable
MutableChunkSnapshot could be assumed to not include max height or temp/rain but include biome
maybe a name more descriptive of its purpose for chunk transmission distinct from ChunkSnapshot
ChunkSendData, SendableChunk (kind of like this one), MutableChunk (confusing?)
Separate from event: would want a way to send a chunk updates to players, e.g.
void Player#sendChunkUpdate(MutableChunkSnapshot)
void Player#sendBulkChunkUpdate(List<MutableChunkSnapshot>)
Maybe boolean to indicate whether it was actually sent (fail if it is out of player's view range)
Matching methods on world? could filter based on what is in each player's actual view range
Bulk update methods could complain if MutableChunkSnapshots are not all continuous/full chunk
Could either buffer sendChunkUpdates (more work) or trust plugin devs to use sendBulk.
For creating MutableChunkSnapshots: parallel to existing ChunkSnapshot getters in Chunk (except no
flags) and World (for getting empty ones - but now they're editable and can be filled if desired)
Problem: most devs are used to dealing with Blocks and that API but it'd be massively shenanigans
to make parallel Block interface/implementation that corresponds to MutableChunkSnapshots instead
of real live chunks in the world. Counterpoint: It isn't too bad to give devs low-level access in
some cases, see chunk generation which similarly uses raw data arrays as backing.
Problem: not very future-proof. TypeId and data are magic numbery and like to disappear any day
now. So it might be wise to hold out on a released API until the replacement is known (in concrete
terms - how it looks in the protocol, etc. - not just abstractly)
Internal representation of MutableChunkSnapshot will be similar to normal Chunks in that it keeps
track of which chunk sections exist & are non-empty (needs to be &'d with send mask before actually
being sent)
API SPECIFICS
=============
ChunkSnapshot:
int getX();
int getZ();
String getWorldName();
int getBlockTypeId(int x, int y, int z);
int getBlockData(int x, int y, int z);
int getBlockSkyLight(int x, int y, int z);
int getBlockEmittedLight(int x, int y, int z);
int getHighestBlockYAt(int x, int z);
Biome getBiome(int x, int z);
double getRawBiomeTemperature(int x, int z);
double getRawBiomeRainfall(int x, int z);
long getCaptureFullTime();
boolean isSectionEmpty(int sy);
MutableChunkSnapshot: (or other name)
similar to ChunkSnapshot:
int getX();
int getZ();
String getWorldName();
int getBlockTypeId(int x, int y, int z);
int getBlockData(int x, int y, int z);
int getBlockSkyLight(int x, int y, int z);
int getBlockEmittedLight(int x, int y, int z);
Biome getBiome(int x, int z);
setters for data:
void setBlockTypeId...
void setBlockData...
void setBlockSkyLight...
void setBlockEmittedLight...
void setBiome...
for accessing/modifying the send mask:
bool containsSection(int sy)
void setContainsSection(int sy, bool contained)
for changing whether it's an update/patch or a full chunk:
bool isContinuous()
!isUpdate, isFullChunk, isFullColumn
void setContinuous(bool)
PROTOCOL REFERENCE
==================
Chunk Packet:
X, Z, continuous flag, section mask, extra data mask, data [compressed]
data: types / metadata / blocklight / skylight (conditional) / extra / biomes
skylight included in data only if world's environment is NORMAL - inferred by client
continuous=true implies existing data should be thrown away (non-included sections are air)
while continuous=false implies existing data should stay (such sections are unchanged)
Bulk Chunk Packet:
num entries, compressed size, skylight flag, data [compressed], entry array
each entry: x, z, mask of sections, mask of extra data
data: concatenation of data portion of Chunk Packet for each entry (skylight is from )
continuous is assumed to be true
note that skylight is specified by the server in this case
Extra data & extra data mask are implied by block IDs greater than 255. Bukkit/CB don't support
this anywhere else.
WHERE TO HOOK
=============
Essentially: in-between determining a specific chunk needs to go to a specific player, and the
process of turning the internal chunk representation into the packet's format (& compression
happening)
The processing to turn the internal reps into a MutableChunkSnapshot to send to events could be
skipped if there are no handlers registered for the event, or could be delayed until
getMutableChunkSnapshot() is called on the event for the first time (in case every event handler
doesn't care about this chunk). To this end the event could also provide a normal ChunkSnapshot
(also lazily fetched?) if only read access is desired in order to curb performance issues. In
general lazy processing seems like a good idea here.
In specific NMS/CB terms:
CraftBukkit already includes PacketPlayOutMapChunkBulk to improve compression efficiency
PacketPlayOutMapChunk is here:
https://github.com/Bukkit/mc-dev/blob/master/net/minecraft/server/PacketPlayOutMapChunk.java
continuous flag is named inflatedBuffer right now, probably not very descriptive name, not sure if
worth changing
ChunkPacket: Right now it takes a Chunk, the skylight flag, and the section mask, used in:
PlayerChunk:88: player is known, easy to pass down call stack & eventually event
PlayerChunk:167: sendAll'd, used in multi-block-change context, not full chunk send, could either
loop or just not include this case in the event since it's more of a low-level detail - though it
does send some data that plugins might want to filter so maybe it's worth turning the sendAll call
into a loop. MutableChunkSnapshot should have the facilities to handle non-full-chunks.
ChunkBulk: used only at nms.EntityPlayer line 220 to perform bulk chunk send, constructed &
immediately sent. Could simply be modified between being constructed & sent (event emitted for each
chunk included). Might be easier to do modification before the construction of the packet. In that
case it'd need to be changed to not pull from the live chunk data but from the set of
MutableChunkSnapshot. Bulk is not compressed until later during its actual transmission, an
existing optimization by CraftBukkit.
Good place to hook *might* be PacketPlayOutMapChunk#a(Chunk, boolean, int) which does the actual
conversion of the chunk into the data array used in the packets. The player involved in the
transaction would have to get passed in there somehow. Both packets call this function no matter
where they are called from, reducing surface area of CB changes.
Note: found usages are limited to classes already in CB since I haven't yet figured out how to get
IntelliJ to show sources from mc-dev
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment