server.liquid
Constants #
const max_cells_per_tick = 4096
max_cells_per_tick caps how many pending cells the manager drains in a single tick so a large flood can never stall the actor thread. Cells left over stay queued and are processed on later ticks - the flow just spreads a bit slower under heavy load instead of blocking the whole server. 4096 is generous: a flat pool spreading 7 cells in every direction is well under this.
const source_depth = 8
Water depth model, adapted from dragonfly and PocketMine.
depth runs 1..8 internally: 8 is a source, 1..7 are flowing levels where a higher number is a fuller (taller) cell, and a falling cell behaves like a full column. Each horizontal step loses spread_decay levels, so a source reaches at most 7 cells away before the flow is exhausted.
Bedrock encodes this in the liquid_depth block state as liquid_depth = 8 - depth, with +8 added when the cell is falling. A source is minecraft:water (still); anything flowing is minecraft:flowing_water.
const max_flow_depth = 7
const spread_decay = 1
const min_adjacent_sources = 2
fn new_falling #
fn new_falling() WaterState
new_falling is a falling column - it appears full but spreads like flowing.
fn new_flowing #
fn new_flowing(depth int) WaterState
new_flowing builds a flowing cell at the given depth.
fn new_manager #
fn new_manager(host Host) &LiquidManager
new_manager builds a LiquidManager bound to host and precomputes the water id table (all source, flowing and falling states) once.
fn new_source #
fn new_source() WaterState
new_source is the full still water source.
interface BlockSink #
interface BlockSink {
mut:
set_block_id(id int, x int, y int, z int)
}
BlockSink writes a block by network id and broadcasts the change to viewers. Every liquid write routes through this so a spread looks like a normal edit.
interface BlockSource #
interface BlockSource {
mut:
get_block(x int, y int, z int) int
}
BlockSource reads block network ids from a world by absolute coordinates. Hub satisfies it structurally; tests use an in-memory grid.
interface Host #
interface Host {
BlockSource
BlockSink
}
Host is what the LiquidManager needs from its world: read and write blocks. Hub satisfies it via get_block/set_block_id, so this module never imports session and stays unit-testable against a fake grid.
struct LiquidManager #
struct LiquidManager {
mut:
host Host
pending map[string]Pos
// water_states maps every known water network id to its resolved cell so the
// manager can tell water apart from other blocks when it reads the world.
water_states map[int]WaterState
air_id int
}
LiquidManager owns the set of block positions that still need a liquid update and processes them on the actor thread each tick. It holds no world of its own - the Host it is handed reads and writes blocks.
fn (LiquidManager) place_source #
fn (mut m LiquidManager) place_source(x int, y int, z int)
place_source sets a water source at the position and queues it for spreading.
fn (LiquidManager) enqueue #
fn (mut m LiquidManager) enqueue(x int, y int, z int)
enqueue marks a cell as needing a liquid update on a later tick.
fn (LiquidManager) on_block_changed #
fn (mut m LiquidManager) on_block_changed(x int, y int, z int)
on_block_changed re-evaluates a cell and its neighbours after a block edit - placing water starts its flow, breaking a block frees a path for adjacent water to spread into. Only water cells actually do anything when processed.
fn (LiquidManager) pending_count #
fn (m &LiquidManager) pending_count() int
pending_count is the number of cells still queued. Used by tests.
fn (LiquidManager) tick #
fn (mut m LiquidManager) tick()
tick drains up to max_cells_per_tick queued cells and processes each. Cells a processed cell touches (neighbours it fills, or itself when it changes) are re-queued for the next tick, so the flow advances one ring per tick like vanilla. Runs on the actor thread from TickJob.
struct WaterState #
struct WaterState {
pub:
depth int
falling bool
}
WaterState is a resolved water cell: its internal depth and whether it is falling. It knows how to encode itself into a Bedrock network id.
fn (WaterState) is_source #
fn (w WaterState) is_source() bool
is_source reports whether this cell is a full still source.
fn (WaterState) network_id #
fn (w WaterState) network_id() int
network_id resolves this water cell to its Bedrock runtime id. Source cells use minecraft:water, flowing/falling cells use minecraft:flowing_water.