09-14-2020, 04:54 AM
Present architecture (BLOCKTYPE, NIBBLETYPE)
(If we're lucky we might have an E_META_XXX mask.)
Fast, but not type safe. There may be any number of metadata changes between MC 1.x and 1.y which silently broke our bitwise ops which we just don't know about. Doesn't support >256 block types.
Next steps
Need something that's fast and type safe. If Mojang changes some property in 1.17 say, the person who's updating to the new blocks needs to see a compilation failure for every use of a property that was changed or no longer exists, as well for not setting a property that was added.
Building on the recently added BlockState/BlockTypeRegistry/...
These classes maintain a dynamic string mapping, akin to the Vanilla { "minecraft:block_name" : { "property1" : "value1" }}.
This isn't the best format for Vanilla blocks:
Plan going forward
A new Block struct (similar to cItem) for Vanilla. BlockState & BlockTypeRegistry for plugins.
Block structure storage format
16 bit short, in a typed Block structure. Consumes no more than BLOCKTYPE + NIBBLETYPE.
Vanilla block handling
Lua script generated namespaces that can create a Block and query the properties of one. Baked in at compile time.
Custom blocks, extensibility, plugins
This isn't yet done, but could look something like this:
Use a global dynamic registry, xoft's BlockState/BlockTypeRegistry classes. Plugins can register blocks with a set of string properties, attach their custom BlockHandler, and set BlockInfo properties (IsSolid, IsSnowable, etc.).
For each combination of properties, the plugin receives a Block struct (so, basically an ID that comes after the Vanilla ones). Now this custom block can be used like any vanilla block, passed to SetBlock, returned from GetBlock. When native code needs information about this custom ID, it queries the global dynamic registry.
Protocol
Since the set of vanilla blocks are fixed, mapping Block (ID) to protocol ID is a switch statement. Custom blocks probably need some negotiation with the client, the plugin can be responsible for returning the right protocol ID.
Serialisation
Storage to Anvil needs mapping to and from namespaced strings, "minecraft:xx" et al. A bijective mapping in WorldStorage\NamespaceSerializer.cpp (BlockType <-> std::string) as exists for Statistics should do the trick. Again, unknown IDs are delegated to the global registry.
Chunk storage
Just a normal palette storing our 16 bit shorts. No special handling for custom blocks since this is just storage. Ideally in 1.16's Chunk Data format, so sending chunks need minimal processing.
Chunk.SetBlock(Pos, E_BLOCK_PISTON, 0x10); auto BlockType = Chunk.GetBlock(Pos); if (BlockType == E_BLOCK_PISTON) { auto IsSticky = Chunk.GetMeta(Pos) & 0x08; }
(If we're lucky we might have an E_META_XXX mask.)
Fast, but not type safe. There may be any number of metadata changes between MC 1.x and 1.y which silently broke our bitwise ops which we just don't know about. Doesn't support >256 block types.
Next steps
Need something that's fast and type safe. If Mojang changes some property in 1.17 say, the person who's updating to the new blocks needs to see a compilation failure for every use of a property that was changed or no longer exists, as well for not setting a property that was added.
⁂
Building on the recently added BlockState/BlockTypeRegistry/...
These classes maintain a dynamic string mapping, akin to the Vanilla { "minecraft:block_name" : { "property1" : "value1" }}.
This isn't the best format for Vanilla blocks:
- The set of Vanilla blocks are fixed per version. There is no need to parse all definitions from a file on every startup when the compiler can do the work.
- Mojang have changed block names, properties before. For example, Silverfish Egg, Monster Block, Infested Block. Using strings gives no indication that anything is amiss until something goes awry at runtime.
- Especially since so many of our users are on low powered devices, we need to be careful to avoid too many dynamic string allocations. Anecdote, but 1.16 runs at 1FPS on a flat world for 20 seconds after joining singleplayer. Pre-flattening 1.12.2 runs at 40. I'm unsure if allocations are to blame, but Pi Zero users should still have good performance after this migration.
Plan going forward
A new Block struct (similar to cItem) for Vanilla. BlockState & BlockTypeRegistry for plugins.
Block structure storage format
struct Block { // Gets the type of this block BlockType Type() const; // Comparison operators // ... unsigned short ID; };
16 bit short, in a typed Block structure. Consumes no more than BLOCKTYPE + NIBBLETYPE.
Vanilla block handling
Chunk.SetBlock( Pos, // Call the autogenerated Piston(...) function in the autogenerated Blocks namespace // Creates a Block struct representing a Piston with the given properties set // Can also do Blocks::Piston() to create a default Piston, using the default Mojang defined in their registry.json Blocks::Piston(eBlockFace::BLOCK_FACE_XM, /* sticky */ true, /* extended */ false) ); auto Block = Chunk.GetBlock(Pos); if (Block == BlockType::Piston) // Invokes comparison operator between Block and BlockType, converts a Block to its type automatically { // Call the autogenerated "Sticky" accessor in the Blocks::Piston autogenerated namespace // The accessor looks at Block (i.e. its ID) and returns if it's sticky. auto IsSticky = Blocks::Piston::Sticky(Block); }
Lua script generated namespaces that can create a Block and query the properties of one. Baked in at compile time.
Custom blocks, extensibility, plugins
This isn't yet done, but could look something like this:
Use a global dynamic registry, xoft's BlockState/BlockTypeRegistry classes. Plugins can register blocks with a set of string properties, attach their custom BlockHandler, and set BlockInfo properties (IsSolid, IsSnowable, etc.).
For each combination of properties, the plugin receives a Block struct (so, basically an ID that comes after the Vanilla ones). Now this custom block can be used like any vanilla block, passed to SetBlock, returned from GetBlock. When native code needs information about this custom ID, it queries the global dynamic registry.
Protocol
Since the set of vanilla blocks are fixed, mapping Block (ID) to protocol ID is a switch statement. Custom blocks probably need some negotiation with the client, the plugin can be responsible for returning the right protocol ID.
Serialisation
Storage to Anvil needs mapping to and from namespaced strings, "minecraft:xx" et al. A bijective mapping in WorldStorage\NamespaceSerializer.cpp (BlockType <-> std::string) as exists for Statistics should do the trick. Again, unknown IDs are delegated to the global registry.
Chunk storage
Just a normal palette storing our 16 bit shorts. No special handling for custom blocks since this is just storage. Ideally in 1.16's Chunk Data format, so sending chunks need minimal processing.