up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 08:33:57 +08:00
commit 26ed99fda6
845 changed files with 75419 additions and 0 deletions
+541
View File
@@ -0,0 +1,541 @@
package world
import (
"fmt"
"maps"
"math"
"math/bits"
"slices"
"sort"
"sync"
"github.com/brentp/intintmap"
"github.com/df-mc/dragonfly/server/world/chunk"
"github.com/segmentio/fasthash/fnv1"
)
// DefaultBlockRegistry is the default (vanilla) block registry used by Dragonfly when no custom registry is provided.
//
// The registry is populated during package init (block states + block implementations) and is finalized on first use
// by `server.Config.New()`, `world.Config.New()`, or `mcdb.Config.Open()`. Callers that need custom blocks should
// create a new registry using `NewBlockRegistry()` and pass it through config instead of mutating this value.
var DefaultBlockRegistry = &BasicBlockRegistry{
blockProperties: make(map[string]map[string]any),
stateRuntimeIDs: make(map[stateHash]uint32),
customBlocks: make(map[string]CustomBlock),
}
// BlockRegistry converts between runtime IDs and block states/implementations.
//
// A BlockRegistry has a build/finalize lifecycle:
// - During setup, blocks and block states may be registered using RegisterBlockState/RegisterBlock.
// - After calling Finalize, the registry becomes immutable and is ready for use in chunk encoding/decoding and
// network serialization. RegisterBlock/RegisterBlockState will panic after finalization.
//
// The interface is split because the chunk package cannot import world.Block.
type BlockRegistry interface {
chunk.BlockRegistry
// BlockByRuntimeID looks up a Block by runtime ID. If the runtime ID is unknown/out of range, ok is false.
BlockByRuntimeID(rid uint32) (Block, bool)
// BlockByRuntimeIDOrAir looks up a Block by runtime ID. If not found, an air block is returned.
BlockByRuntimeIDOrAir(rid uint32) Block
// BlockRuntimeID looks up the runtime ID of a previously registered Block.
BlockRuntimeID(block Block) (rid uint32)
// RegisterBlock registers a Block implementation for a previously registered block state.
RegisterBlock(block Block)
// RegisterBlockState registers a block state that blocks may encode to.
RegisterBlockState(blockState BlockState)
// CustomBlocks returns custom blocks registered in this registry, keyed by identifier.
CustomBlocks() map[string]CustomBlock
// BlockByName looks up a Block by full identifier and properties.
BlockByName(name string, properties map[string]any) (Block, bool)
// Blocks returns all blocks registered in the registry, indexed by runtime ID.
Blocks() []Block
// Air returns the air block registered in the registry.
Air() Block
// Finalize finalizes the registry, building derived lookup tables required for runtime usage. Finalize is
// idempotent.
Finalize()
// BitSize returns the number of bits used by BlockHash (depends on the number of registered blocks).
BitSize() int
// BlockHash returns a unique identifier of the block including the block states. The hash is internal to Dragonfly
// and is used for fast map lookups; it does not need to match any in-game identifiers.
BlockHash(b Block) uint64
// RuntimeIDToHash resolves a runtime ID to its network block hash.
RuntimeIDToHash(runtimeID uint32) (hash uint32, ok bool)
}
const (
blockFlagNBT uint16 = 1 << iota
blockFlagRandomTick
blockFlagLiquid
blockFlagLiquidDisplacing
)
// blockInfo is a packed set of per-runtime-ID properties used in hot paths (lighting, ticking, fluid checks, etc.).
//
// Layout (uint16):
// - bits 0..3: boolean flags (blockFlag*).
// - bits 8..11: 4-bit light emission level (0-15).
// - bits 12..15: 4-bit light filtering level (0-15).
//
// Values are computed during BlockRegistry.Finalize() and stored in BasicBlockRegistry.blockInfos, indexed by runtime ID.
type blockInfo uint16
func (b *blockInfo) set(flag uint16) {
*b |= blockInfo(flag)
}
func (b blockInfo) get(flag uint16) bool {
return uint16(b)&flag != 0
}
func (b *blockInfo) setLight(light uint8) {
// Overwrite the 4-bit light emission field.
*b &^= blockInfo(0xF) << 8
*b |= blockInfo(light&0xF) << 8
}
func (b *blockInfo) setLightFilter(light uint8) {
// Overwrite the 4-bit light filtering field.
*b &^= blockInfo(0xF) << 12
*b |= blockInfo(light&0xF) << 12
}
func (b blockInfo) getLight() uint8 {
return uint8((b >> 8) & 0xF)
}
func (b blockInfo) getLightFilter() uint8 {
return uint8((b >> 12) & 0xF)
}
// BasicBlockRegistry is the default BlockRegistry implementation used by Dragonfly.
type BasicBlockRegistry struct {
mu sync.Mutex
finalized bool
bitSize int
hashes *intintmap.Map
networkhashToRids map[uint32]uint32
ridsToNetworkhash []uint32
// stateRuntimeIDs holds a map for looking up the runtime ID of a block by the stateHash it produces.
stateRuntimeIDs map[stateHash]uint32
blockProperties map[string]map[string]any
// blocks holds a list of all registered Blocks indexed by their runtime ID. Blocks that were not explicitly
// registered are of the type unknownBlock.
blocks []Block
// customBlocks maps a custom block's identifier to the custom block.
customBlocks map[string]CustomBlock
blockInfos []blockInfo
airRID uint32
}
func (br *BasicBlockRegistry) BitSize() int {
if !br.finalized {
panic("BlockRegistry.BitSize called on non finalized BlockRegistry")
}
return br.bitSize
}
func (br *BasicBlockRegistry) BlockCount() int {
if !br.finalized {
panic("BlockRegistry.BlockCount called on non finalized BlockRegistry")
}
return len(br.blockInfos)
}
func (br *BasicBlockRegistry) RandomTickBlock(rid uint32) bool {
if !br.finalized {
panic("BlockRegistry.RandomTickBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].get(blockFlagRandomTick)
}
func (br *BasicBlockRegistry) FilteringBlock(rid uint32) uint8 {
if !br.finalized {
panic("BlockRegistry.FilteringBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].getLightFilter()
}
func (br *BasicBlockRegistry) LightBlock(rid uint32) uint8 {
if !br.finalized {
panic("BlockRegistry.LightBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].getLight()
}
func (br *BasicBlockRegistry) NBTBlock(rid uint32) bool {
if !br.finalized {
panic("BlockRegistry.NBTBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].get(blockFlagNBT)
}
func (br *BasicBlockRegistry) LiquidDisplacingBlock(rid uint32) bool {
if !br.finalized {
panic("BlockRegistry.LiquidDisplacingBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].get(blockFlagLiquidDisplacing)
}
func (br *BasicBlockRegistry) LiquidBlock(rid uint32) bool {
if !br.finalized {
panic("BlockRegistry.LiquidBlock called on non finalized BlockRegistry")
}
return br.blockInfos[rid].get(blockFlagLiquid)
}
func (br *BasicBlockRegistry) Blocks() []Block {
if !br.finalized {
panic("BlockRegistry.Blocks called on non finalized BlockRegistry")
}
return slices.Clone(br.blocks)
}
func (br *BasicBlockRegistry) HashToRuntimeID(hash uint32) (rid uint32, ok bool) {
if !br.finalized {
panic("BlockRegistry.HashToRuntimeID called on non finalized BlockRegistry")
}
rid, ok = br.networkhashToRids[hash]
return
}
func (br *BasicBlockRegistry) RuntimeIDToHash(runtimeID uint32) (hash uint32, ok bool) {
if !br.finalized {
panic("BlockRegistry.RuntimeIDToHash called on non finalized BlockRegistry")
}
if runtimeID >= uint32(len(br.ridsToNetworkhash)) {
return 0, false
}
return br.ridsToNetworkhash[runtimeID], true
}
// Clone returns an independent copy of the registry. If the source registry is finalized, the clone is also finalized.
// If the source is not finalized, the clone remains mutable.
func (br *BasicBlockRegistry) Clone() *BasicBlockRegistry {
br.mu.Lock()
defer br.mu.Unlock()
br2 := &BasicBlockRegistry{
blockProperties: make(map[string]map[string]any, len(br.blockProperties)),
stateRuntimeIDs: make(map[stateHash]uint32, len(br.stateRuntimeIDs)),
customBlocks: make(map[string]CustomBlock, len(br.customBlocks)),
finalized: br.finalized,
bitSize: br.bitSize,
airRID: br.airRID,
}
for k, v := range br.blockProperties {
br2.blockProperties[k] = maps.Clone(v)
}
maps.Copy(br2.stateRuntimeIDs, br.stateRuntimeIDs)
maps.Copy(br2.customBlocks, br.customBlocks)
br2.blocks = make([]Block, len(br.blocks))
copy(br2.blocks, br.blocks)
br2.blockInfos = append([]blockInfo(nil), br.blockInfos...)
if br.finalized {
br2.hashes = intintmap.New(len(br.blocks), 0.999)
for rid, b := range br2.blocks {
if _, hash := b.Hash(); hash == math.MaxUint64 {
continue
}
br2.hashes.Put(int64(br2.BlockHash(b)), int64(rid))
}
br2.networkhashToRids = make(map[uint32]uint32, len(br.networkhashToRids))
maps.Copy(br2.networkhashToRids, br.networkhashToRids)
br2.ridsToNetworkhash = append([]uint32(nil), br.ridsToNetworkhash...)
}
return br2
}
// NewBlockRegistry returns a mutable registry seeded with all vanilla block states and block implementations.
// Callers may RegisterBlockState/RegisterBlock and must call Finalize() before using the registry for world/chunk
// serialization. The returned registry is independent from DefaultBlockRegistry.
func NewBlockRegistry() BlockRegistry {
// Clone() produces a fully populated registry. We then mark it mutable again so that additional block states and
// blocks can be registered before finalizing.
br := DefaultBlockRegistry.Clone()
br.finalized = false
br.bitSize = 0
br.hashes = nil
br.networkhashToRids = nil
br.ridsToNetworkhash = nil
br.blockInfos = nil
return br
}
// RegisterBlock registers the Block passed. The EncodeBlock method will be used to encode and decode the
// block passed. RegisterBlock panics if the block properties returned were not valid, existing properties.
func (br *BasicBlockRegistry) RegisterBlock(b Block) {
br.mu.Lock()
defer br.mu.Unlock()
if br.finalized {
panic("BlockRegistry.RegisterBlock called on finalized BlockRegistry")
}
name, properties := b.EncodeBlock()
if _, ok := b.(CustomBlock); ok {
br.registerBlockStateLocked(BlockState{Name: name, Properties: properties})
}
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
if !ok {
// We assume all blocks must have all their states registered beforehand. Vanilla blocks will have
// this done through registering of all states present in the block_states.nbt file.
panic(fmt.Sprintf("block state returned is not registered (%v {%#v})", name, properties))
}
if _, ok := br.blocks[rid].(unknownBlock); !ok {
panic(fmt.Sprintf("block with name and properties %v {%#v} already registered", name, properties))
}
br.blocks[rid] = b
if c, ok := b.(CustomBlock); ok {
if _, ok := br.customBlocks[name]; !ok {
br.customBlocks[name] = c
}
}
}
// RegisterBlockState registers a BlockState to the registry. The function panics if the properties the
// BlockState holds are invalid or if the BlockState was already registered.
func (br *BasicBlockRegistry) RegisterBlockState(s BlockState) {
br.mu.Lock()
defer br.mu.Unlock()
br.registerBlockStateLocked(s)
}
// registerBlockStateLocked is the implementation of RegisterBlockState. br.mu must be held by the caller.
func (br *BasicBlockRegistry) registerBlockStateLocked(s BlockState) {
if br.finalized {
panic("BlockRegistry.RegisterBlockState called on finalized BlockRegistry")
}
h := stateHash{name: s.Name, properties: hashProperties(s.Properties)}
if _, ok := br.stateRuntimeIDs[h]; ok {
panic(fmt.Sprintf("cannot register the same state twice (%+v)", s))
}
if _, ok := br.blockProperties[s.Name]; !ok {
br.blockProperties[s.Name] = s.Properties
}
rid := uint32(len(br.blocks))
br.blocks = append(br.blocks, unknownBlock{BlockState: s})
br.stateRuntimeIDs[h] = rid
}
func (br *BasicBlockRegistry) Finalize() {
br.mu.Lock()
defer br.mu.Unlock()
if br.finalized {
// Finalize is intentionally idempotent so code can defensively call it when a registry is provided through
// config. RegisterBlock/RegisterBlockState still panic after finalization.
return
}
br.bitSize = bits.Len64(uint64(len(br.blocks)))
sort.SliceStable(br.blocks, func(i, j int) bool {
var nameOne string
if b1, ok := br.blocks[i].(unknownBlock); ok {
nameOne = b1.Name
} else {
nameOne, _ = br.blocks[i].EncodeBlock()
}
var nameTwo string
if b2, ok := br.blocks[j].(unknownBlock); ok {
nameTwo = b2.Name
} else {
nameTwo, _ = br.blocks[j].EncodeBlock()
}
return fnv1.HashString64(nameOne) < fnv1.HashString64(nameTwo)
})
br.blockInfos = make([]blockInfo, len(br.blocks))
br.hashes = intintmap.New(len(br.blocks), 0.999)
br.networkhashToRids = make(map[uint32]uint32, len(br.blocks))
br.ridsToNetworkhash = make([]uint32, len(br.blocks))
br.stateRuntimeIDs = make(map[stateHash]uint32, len(br.blocks))
networkHashScratch := make([]byte, 0, 0xff)
foundAir := false
for idx, b := range br.blocks {
rid := uint32(idx)
name, properties := b.EncodeBlock()
h := stateHash{name: name, properties: hashProperties(properties)}
if name == "minecraft:air" {
br.airRID = rid
foundAir = true
}
if _, ok := br.stateRuntimeIDs[h]; ok {
panic(fmt.Sprintf("cannot register the same state twice (%s %+v)", name, properties))
}
br.stateRuntimeIDs[h] = rid
var info blockInfo
// Default to fully opaque. Blocks that implement lightDiffuser may override this (e.g., air -> 0, leaves -> 1-14).
info.setLightFilter(15)
if diffuser, ok := b.(lightDiffuser); ok {
info.setLightFilter(diffuser.LightDiffusionLevel())
}
if emitter, ok := b.(lightEmitter); ok {
info.setLight(emitter.LightEmissionLevel())
}
if _, ok := b.(NBTer); ok {
info.set(blockFlagNBT)
}
if _, ok := b.(RandomTicker); ok {
info.set(blockFlagRandomTick)
}
if _, ok := b.(Liquid); ok {
info.set(blockFlagLiquid)
}
if _, ok := b.(LiquidDisplacer); ok {
info.set(blockFlagLiquidDisplacing)
}
br.blockInfos[rid] = info
if _, hash := b.Hash(); hash != math.MaxUint64 {
h := int64(br.BlockHash(b))
if other, ok := br.hashes.Get(h); ok {
panic(fmt.Sprintf("block %#v with hash %v already registered by %#v", b, h, br.blocks[other]))
}
br.hashes.Put(h, int64(rid))
}
var netHash uint32
netHash, networkHashScratch = networkBlockHash(name, properties, networkHashScratch)
if other, ok := br.networkhashToRids[netHash]; ok {
otherName, otherProperties := br.blocks[other].EncodeBlock()
panic(fmt.Sprintf("network block hash collision for (%s %+v) and (%s %+v)", name, properties, otherName, otherProperties))
}
br.networkhashToRids[netHash] = rid
br.ridsToNetworkhash[rid] = netHash
}
if !foundAir {
panic("BlockRegistry.Finalize: no minecraft:air block state registered")
}
br.finalized = true
}
// AirRuntimeID returns the runtime ID of the air block.
func (br *BasicBlockRegistry) AirRuntimeID() uint32 {
if !br.finalized {
panic("BlockRegistry.AirRuntimeID called on non finalized BlockRegistry")
}
return br.airRID
}
// RuntimeIDToState returns the name and state properties of a block by its runtime ID.
func (br *BasicBlockRegistry) RuntimeIDToState(runtimeID uint32) (name string, properties map[string]any, found bool) {
if !br.finalized {
panic("BlockRegistry.RuntimeIDToState called on non finalized BlockRegistry")
}
if runtimeID >= uint32(len(br.blocks)) {
return "", nil, false
}
name, properties = br.blocks[runtimeID].EncodeBlock()
return name, properties, true
}
// StateToRuntimeID returns the runtime ID of a block by its name and state properties.
func (br *BasicBlockRegistry) StateToRuntimeID(name string, properties map[string]any) (runtimeID uint32, found bool) {
if !br.finalized {
panic("BlockRegistry.StateToRuntimeID called on non finalized BlockRegistry")
}
if rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]; ok {
return rid, true
}
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(br.blockProperties[name])}]
return rid, ok
}
// BlockHash returns a unique identifier of the block including the block states. This function is used internally
// to convert a block to a single integer which can be used in map lookups. The hash produced therefore does not
// need to match anything in the game, but it must be unique among all registered blocks.
// The tool in `/cmd/blockhash` may be used to automatically generate block hashes of blocks in a package.
func (br *BasicBlockRegistry) BlockHash(b Block) uint64 {
base, hash := b.Hash()
return base | (hash << uint64(br.bitSize))
}
// BlockRuntimeID attempts to return a runtime ID of a block previously registered using RegisterBlock().
// If the runtime ID cannot be found because the Block wasn't registered, BlockRuntimeID will panic.
func (br *BasicBlockRegistry) BlockRuntimeID(b Block) uint32 {
if !br.finalized {
panic("BlockRegistry.BlockRuntimeID called on non finalized BlockRegistry")
}
if b == nil {
return br.airRID
}
if _, hash := b.Hash(); hash != math.MaxUint64 {
// b is not an unknownBlock.
if rid, ok := br.hashes.Get(int64(br.BlockHash(b))); ok {
return uint32(rid)
}
panic(fmt.Sprintf("cannot find block by non-0 hash of block %#v", b))
}
return br.slowBlockRuntimeID(b)
}
func (br *BasicBlockRegistry) BlockByRuntimeIDOrAir(rid uint32) Block {
bl, _ := br.BlockByRuntimeID(rid)
return bl
}
// slowBlockRuntimeID finds the runtime ID of a Block by hashing the properties produced by calling the
// Block.EncodeBlock method and looking it up in the stateRuntimeIDs map.
func (br *BasicBlockRegistry) slowBlockRuntimeID(b Block) uint32 {
name, properties := b.EncodeBlock()
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
if !ok {
panic(fmt.Sprintf("cannot find block by (name + properties): %#v", b))
}
return rid
}
// BlockByRuntimeID attempts to return a Block by its runtime ID. If not found, the bool returned is
// false. If found, the block is non-nil and the bool true.
func (br *BasicBlockRegistry) BlockByRuntimeID(rid uint32) (Block, bool) {
if !br.finalized {
panic("BlockRegistry.BlockByRuntimeID called on non finalized BlockRegistry")
}
if rid >= uint32(len(br.blocks)) {
return br.Air(), false
}
return br.blocks[rid], true
}
// BlockByName attempts to return a Block by its name and properties. If not found, the bool returned is
// false.
func (br *BasicBlockRegistry) BlockByName(name string, properties map[string]any) (Block, bool) {
if !br.finalized {
panic("BlockRegistry.BlockByName called on non finalized BlockRegistry")
}
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
if !ok {
return nil, false
}
return br.blocks[rid], true
}
// CustomBlocks returns a map of all custom blocks registered with their names as keys.
func (br *BasicBlockRegistry) CustomBlocks() map[string]CustomBlock {
return maps.Clone(br.customBlocks)
}
// Air returns an air block.
func (br *BasicBlockRegistry) Air() Block {
if !br.finalized {
panic("BlockRegistry.Air called on non finalized BlockRegistry")
}
if br.airRID >= uint32(len(br.blocks)) {
// This should never happen for a valid registry (Finalize enforces air exists).
panic("BlockRegistry.Air: air runtime ID out of range")
}
return br.blocks[br.airRID]
}