1.13 migration, aims & objectives
#1
Present architecture (BLOCKTYPE, NIBBLETYPE)

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.
However, this is an excellent format for custom blocks.

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.
Reply
Thanks given by:
#2
Is this understanding correct, in comparison to the old proposal, that the differences are:

PalettedBlockArea is to be forgotten, and the existing structures should be extended to use a global palette, which is mostly static and only partially dynamic.

The changes appear to be less fundimental to the server, but also much less incremental, so this will basically have to be done in one go.

Personally, this proposal appears sensible and if you want to go ahead with it and can complete it you should.

One small point, is that I really don't like adding comparison operators to the Block struct that actually compare BlockType. It would be much clearer in use to to Block.Type() == xyz
Reply
Thanks given by:
#3
I'm not entirely sure on PalettedBlockArea. The aim of this class is to have one implementation of 1.13 memory saving palettes that can be re-used in BlockArea, chunk generation, chunk storage. For the latter two though, which are comparatively short-lived but tend to be access-heavy, we'd want to prioritise speed (so no additional palette lookup) over memory savings. So the class may just end up subsumed into ChunkData for chunk storage.
Reply
Thanks given by:
#4
About migration, it could be split into chunks storing our Block struct, with a translation <-> BLOCKTYPE/NIBBLETYPE, followed later by the huge change making GetBlock/SetBlock take Block structs.
Reply
Thanks given by:
#5
Does anybody else have a comment about this - happy to approve @tigerw's vision for the future?
Reply
Thanks given by:
#6
Sounds like a decent proposal to me. If you have a clear vision of the changes necessary from here on, and are willing to put in work, I say go for it.
Reply
Thanks given by:
#7
So we have the autogenerated stuff in Registries/BlockStates.h .cpp

So now we'll have to create the struct with the comparison operator and replace any operation taking a BLOCKTYPE, NIBBLETYPE with that struct - right?

Any coversion of (BLOCKTYPE, NIBBLETYPE) is done in the code of the palette folder in Protocol. I'm gonna give this a go today.
Reply
Thanks given by: tigerw
#8
There's already a BlockState struct in BlockState.h.
Reply
Thanks given by:
#9
(03-09-2021, 10:14 PM)tigerw Wrote: There's already a BlockState struct in BlockState.h.

Yeah - i saw that. I'm workign slowly forwand to replace everything. I'm stuck at unity_35 at this pointBig Grin
Reply
Thanks given by:




Users browsing this thread: 1 Guest(s)