Last active
November 13, 2017 17:35
-
-
Save SpaceManiac/5ee244ffa6d510e942b3 to your computer and use it in GitHub Desktop.
Chunk Send Event / Send Chunk Update API rough sketch & notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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