pile

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 24 Imported by: 0

README

Pile

A compact, deterministic single-file world format and world.Provider for dragonfly servers, built for lobbies, minigame maps, skyblock islands and structures.

One .pile file per dimension. On the benchmark map, 5–20× smaller than the vanilla leveldb format (141× on dedup-friendly flat worlds), loading in a single sequential read.

go get github.com/oriumgames/pile

Why

  • Zero garbage. A save is a canonical full rewrite (atomic temp + rename): edit a lobby on a live server, save, and the file is byte-identical to a fresh conversion of the same content. Identical content ⇒ identical bytes, so a file hash is a map version.
  • Small. World-global block palette, content-hash deduplication of repeated sections (empty sections cost one bit), byte-aligned indices under zstd.
  • Safe. xxHash64 integrity, no-panic decoding of hostile files, crash-safe append mode with torn-write recovery, automatic block-state upgrades across Minecraft versions, unknown states preserved through load/save.
  • Fast. Parallel encode/decode, direct (layout-asserted unsafe) access to dragonfly's chunk internals: no per-block work on either path.

Quick start

p, err := pile.Open("maps/lobby")
w := world.Config{Provider: p}.New()
// ...
defer p.Close() // saves

Options: pile.ReadOnly(), pile.Compression(...), pile.AppendMode(), pile.Skip(pile.SkipEntities|...), pile.FilterEntity(...), pile.FilterBlockEntity(...), pile.FilterColumn(...), pile.LoadSkip(...), pile.CacheColumns(n), pile.FastSaves(), pile.StoreLight(), pile.Registry(...), pile.WithSpawnStore(...), pile.MaxDecodedBytes(n). Player data never lives in pile files.

pile.MaxDecodedBytes(n) is the one to reach for if you open worlds you did not write. The format's own ceilings are set at what it can represent rather than at what a server wants to spend, so a legal file of about a kilobyte decodes into a gigabyte; this caps it. A world refused under the cap fails with format.ErrDecodeBudget, which does not wrap format.ErrCorrupt — the file is too big for your limit, not broken, so do not quarantine it as though it were.

If the worlds come from strangers, open them like this:

p, err := pile.Open(dir,
    pile.ReadOnly(),                  // nothing is written back
    pile.MaxDecodedBytes(256<<20),    // whatever your box can spare
    pile.CacheColumns(0),             // no cache: one column at a time
)

The ceiling charges everything a decode produces — columns, section storages, and the block entities, entities and scheduled updates inside them. What it does not bound is wall-clock time: a small file can legally cost seconds of CPU, so do not decode foreign worlds on a request path or unbounded in parallel.

LoadSkip is not a bound. It drops categories after the file is decoded, so it removes nothing from the peak — use it to keep content out of your runtime, not to keep it out of memory.

And the integrity hashes detect corruption, not tampering: xxHash64 is keyless, so anyone who can author a file can make its checksums agree. A file that verifies is well-formed, never trustworthy.

Converting to and from dragonfly's leveldb format is API too, not only a CLI command — which matters when your server registers blocks the pile binary cannot know about, since the conversion has to happen where that registry is:

n, err := pile.ImportMCDB("./mcdb-world", "./maps/lobby", pile.Registry(myRegistry))
n, err := pile.ExportMCDB("./maps/lobby", "./mcdb-world")

Two file modes

solid (default) indexed (pile.AppendMode())
for lobbies, minigames, islands, anything read-mostly large or save-heavy worlds
save deterministic full rewrite, zero garbage append + checkpoint; auto-compaction on close
memory whole world directory + palettes; columns decoded on demand (optional LRU)
durability atomic rename footer checkpoints, torn-write recovery, per-chunk checksums

Convert between modes with pile mode.

Templates & instances

The minigame primitive: one decoded base world, any number of throwaway copy-on-write instances.

tmpl, _ := pile.OpenTemplate("maps/bedwars")
inst := tmpl.Instance()                       // in-memory world.Provider, COW
w := world.Config{Provider: inst}.New()
// ... play a round; blocks break, beds explode ...
inst.Close()                                  // everything evaporates; base pristine
// or: inst.SaveAs("maps/edited")             // keep it

pile.NewMemory() is the same machinery with no base (generated arenas).

Structures

Same format, first-class API:

s, _ := pile.LoadStructure("structures/spawn.pile")
tx.BuildStructure(pos, s)                     // s implements world.Structure
s.PasteInto(p, world.Overworld, pos)          // fast path; carries entities + block-entity NBT
s2, _ := pile.ExtractStructure(p, world.Overworld, lo, hi)
s3 := s2.Rotate(1)                            // 90° clockwise, block states included
lib, _ := pile.LoadStructureLibrary("structures/") // name → structure

Building worlds in code

b := pile.NewBuilder(nil, cube.Range{-64, 319})
b.Fill(lo, hi, block.Stone{})
p := b.Provider()          // in-memory world
_ = b.Save("maps/arena")   // or straight to disk

Self-describing maps

Settings and arbitrary world/chunk metadata travel inside the file. The metadata is an opaque blob: spawn points, regions, NPC positions, whatever shape your game wants, in whatever encoding you already use.

p.UserData() / p.SetUserData(b)
p.ChunkUserData(pos, dim) / p.SetChunkUserData(pos, dim, b)

pile does not look inside it, which is also why pile move refuses a world carrying any unless you pass --keep-user-data: it can translate every block and entity, and it cannot find a coordinate in a blob it does not understand.

Snapshots for versions and grief rollback: p.Snapshot("clean"), p.Rollback("clean"), p.Snapshots(). Autosave: stop := p.AutoSave(5*time.Minute).

CLI

go install github.com/oriumgames/pile/cmd/pile@latest installs: convert (mcdb ⇄ pile), inspect, verify, stats, check, blocks, hash, edit, render, compact, mode, upgrade, prune, move, extract, paste, origin, diff, patch/apply, export/import, snapshot/snapshots/rollback/unsnapshot, version. Every command that decodes chunk content takes --max-decoded n, the CLI's pile.MaxDecodedBytes. See cmd/pile/readme.md.

Notes

  • Pin your dragonfly version: the codec uses layout-asserted unsafe access to chunk internals and panics loudly at startup (instead of corrupting data) if a dragonfly upgrade changes those layouts.
  • For an enormous constantly-mutating survival world, dragonfly's own mcdb remains the better fit; providers are per-world and coexist.

Based on ideas from hollow-cube/polar. MIT licensed; see license.md.

Documentation

Overview

Package pile provides a compact single-file world provider for dragonfly built on the pile v2 format.

Index

Constants

View Source
const (
	CompressionNone    = format.CompressionNone
	CompressionFast    = format.CompressionFast
	CompressionDefault = format.CompressionDefault
	CompressionBest    = format.CompressionBest
)

Compression levels, re-exported from the format package.

Variables

View Source
var (
	// ErrNotFound reports that no column is stored at a position. LoadColumn
	// returns it, under the name world.Provider's contract requires: it is
	// dragonfly's leveldb.ErrNotFound, and every provider signals a missing
	// chunk with it whatever it is backed by.
	ErrNotFound = leveldb.ErrNotFound
	// ErrCorrupt reports that a file is not a valid pile file. Every decode
	// failure wraps it except ErrDecodeBudget, which is the point of the two
	// being separate.
	ErrCorrupt = format.ErrCorrupt
	// ErrDecodeBudget reports that a decode was stopped by the ceiling set
	// with MaxDecodedBytes. It deliberately does not wrap ErrCorrupt: the file
	// is larger than this provider was told to decode, not broken, and a
	// caller that quarantines corrupt worlds must not quarantine this one.
	ErrDecodeBudget = format.ErrDecodeBudget
)

The errors a caller branches on, re-exported so that branching on them does not mean importing the codec and dragonfly's leveldb package alongside this one. They are the same values, so errors.Is against either name works.

View Source
var ErrReadOnly = errors.New("pile: provider is read-only")

ErrReadOnly is returned by the mutating operations that can report an error at all — Save, SetChunkUserData, Snapshot, DeleteSnapshot and Rollback — when the provider was opened with ReadOnly. The mutators with no error to return (StoreColumn, SaveSettings, SetUserData) are silent no-ops instead, as ReadOnly's own documentation says; IsReadOnly is how a caller tells the two situations apart before it relies on a write.

View Source
var ErrUnmovableUserData = errors.New("pile: world carries user data, which pile cannot translate")

ErrUnmovableUserData is returned by MoveWorld when the world carries application metadata and MoveOptions.KeepUserData is false.

User data is opaque to pile: whatever coordinates it holds -- spawn points, regions, NPC positions -- cannot be found, let alone translated. Moving the blocks and leaving them behind is a silent failure, and the coordinates go on resolving to whatever now occupies their old positions, so it is one that surfaces long after the move as a map that is subtly wrong rather than as an error. The move refuses instead, and KeepUserData is how a caller says it will re-anchor the data itself.

View Source
var ErrWouldClip = errors.New("pile: move would clip content outside the world's vertical range")

ErrWouldClip is returned by MoveWorld when the offset would push content outside the dimension's vertical range and MoveOptions.Clip is false.

Functions

func DimPath added in v0.2.0

func DimPath(dir string, dim world.Dimension) string

DimPath returns the file path of a dimension inside a world directory: overworld.pile, nether.pile, end.pile (dim<id>.pile for custom dimensions).

func ExportMCDB added in v0.2.0

func ExportMCDB(src, dst string, opts ...Option) (int, error)

ExportMCDB converts a pile world into a freshly created leveldb world and returns the number of columns converted.

dst must not already hold an mcdb world, so the result contains only live keys rather than whatever was there plus this world on top.

Options are the provider options the *source* is opened with. A caller converting a world it did not write should pass MaxDecodedBytes: this walks every column of every dimension, so it is exactly the operation a hostile file makes expensive.

func FileMode added in v0.2.0

func FileMode(path string) (uint8, error)

FileMode reads the mode byte of a pile file header: format.ModeSolid or format.ModeIndexed.

func ImportMCDB added in v0.2.0

func ImportMCDB(src, dst string, opts ...Option) (int, error)

ImportMCDB converts a dragonfly leveldb world into a fresh pile world and returns the number of columns converted.

The output is garbage-free by construction: only live columns are visited, and a pile save is a canonical full rewrite, so the result is byte-identical to any other conversion of the same content. That is the property worth converting for -- an mcdb world accumulates dead keys that a copy preserves.

dst must not already hold a pile world. Refusing rather than merging is deliberate: a conversion into an existing world would mix two worlds' columns and leave no way to tell which came from where.

Options are the provider options the destination is opened with, so a caller can choose compression, append mode or a block registry. Reading the source takes none: it is a leveldb world and its cost is bounded by its own files.

func IsMCDB added in v0.2.0

func IsMCDB(dir string) bool

IsMCDB reports whether dir holds a dragonfly leveldb world.

func IsPile added in v0.2.0

func IsPile(dir string) bool

IsPile reports whether dir holds a pile world.

func MCDBBlockStates added in v0.2.1

func MCDBBlockStates(dir string) ([]format.BlockState, error)

MCDBBlockStates returns every distinct block state stored in a Bedrock world, read straight from the sub-chunk palettes.

No registry is involved and none is needed: a palette entry carries the identifier and the properties, which is the whole of a state. That is what makes this usable on a world holding blocks from a behaviour pack, where resolving anything against a vanilla registry is exactly what fails.

The database is opened READ-ONLY. goleveldb's default is read-write and rewrites the manifest and journal on open even when nothing is stored, which is a change to somebody's world that reading it must not make.

func RegisterBlockState added in v0.2.1

func RegisterBlockState(reg world.BlockRegistry, s format.BlockState) (bool, error)

RegisterBlockState registers a state on reg unless it is already there, and reports whether it added one. It is RegisterMCDBStates for a caller that got its states from somewhere else -- a pile world's own palette, say.

func RegisterMCDBStates added in v0.2.1

func RegisterMCDBStates(dir string, reg world.BlockRegistry) (int, error)

RegisterMCDBStates registers, on reg, every block state in the Bedrock world at dir that reg does not already know, and returns how many it added.

This is what makes a world holding custom blocks convertible without the behaviour pack. dragonfly's chunk decoder resolves each palette entry against the registry and fails outright on one it cannot find -- "cannot get runtime ID of block state" -- so a single block from a pack stops the conversion at whichever chunk happens to hold it.

What is registered is the bare state, through RegisterBlockState, which is dragonfly's own placeholder for a block nothing implements. That is enough to decode, and it is all that is wanted here: pile stores a palette entry as its identifier and properties, so the file that comes out carries the real cubecraft:portal_side and not a substitute. A server that registers the block properly later resolves it from the same file.

It is not a way to make the block behave. A placeholder has no model, no collision and no behaviour; nothing short of implementing the pack gives it those. It is a way to move the world.

The registry must not be finalized yet, since registration is what Finalize closes off.

func WorldBlockStates added in v0.2.1

func WorldBlockStates(dir string, opts ...format.ReadOption) ([]format.BlockState, error)

WorldBlockStates returns every block state in a pile world's palettes, across every dimension file, deduplicated and sorted.

No registry is involved: pile stores a palette entry as its identifier and properties, so a state nothing can resolve is still one the file states plainly. That is what makes this usable on a converted world holding blocks from a behaviour pack.

func WorldBounds added in v0.2.0

func WorldBounds(p *Provider, dim world.Dimension) (lo, hi cube.Pos, ok bool)

WorldBounds computes the chunk-granular bounding box [lo, hi] of all non-empty sections of a dimension. ok is false when the dimension holds no blocks.

Types

type Builder added in v0.2.0

type Builder struct {
	// contains filtered or unexported fields
}

Builder creates worlds programmatically without a running server: void or flat arenas, generated lobbies, island templates. Build the content, then hand it over with Provider (in-memory world) or Save (write to disk).

A Builder is single-goroutine; after Provider or Save it hands ownership of its chunks over and resets to empty.

func NewBuilder added in v0.2.0

func NewBuilder(reg world.BlockRegistry, rng cube.Range) *Builder

NewBuilder creates a Builder for worlds with the given vertical range. A nil registry uses world.DefaultBlockRegistry.

func (*Builder) AddBlockEntity added in v0.2.0

func (b *Builder) AddBlockEntity(pos cube.Pos, data map[string]any)

AddBlockEntity attaches block entity NBT to a position.

func (*Builder) AddEntity added in v0.2.0

func (b *Builder) AddEntity(data map[string]any)

AddEntity adds an entity from NBT data. The data must contain a "Pos" list; a "UniqueID" is assigned sequentially when absent.

func (*Builder) Fill added in v0.2.0

func (b *Builder) Fill(lo, hi cube.Pos, bl world.Block)

Fill places a block in every position of the box [lo, hi] (inclusive). The chunk lookup is hoisted per column, so large fills stay cheap.

func (*Builder) FillBiome added in v0.2.0

func (b *Builder) FillBiome(lo, hi cube.Pos, bio world.Biome)

FillBiome sets the biome in every position of the box [lo, hi].

func (*Builder) Provider added in v0.2.0

func (b *Builder) Provider(opts ...Option) *Provider

Provider hands the built world over as an in-memory provider. The Builder resets to empty; the chunks are transferred without copying.

func (*Builder) Save added in v0.2.0

func (b *Builder) Save(dir string, opts ...Option) error

Save builds and writes the world to a directory in one step.

func (*Builder) SetBlock added in v0.2.0

func (b *Builder) SetBlock(pos cube.Pos, bl world.Block)

SetBlock places a block. Positions outside the vertical range are ignored.

func (*Builder) SetUserData added in v0.2.0

func (b *Builder) SetUserData(data []byte)

SetUserData sets the world's application metadata blob.

func (*Builder) Settings added in v0.2.0

func (b *Builder) Settings(s *world.Settings)

Settings sets the world settings.

type DimFile added in v0.2.0

type DimFile struct {
	Dim world.Dimension
	// Indexed reports the file's mode, preserved when writing back.
	Indexed bool
	// StoreLight reports whether the source file stored baked light,
	// preserved when writing back.
	StoreLight bool
	Columns    []format.Column
}

DimFile is one dimension file of a world, loaded for offline tooling.

type MoveOptions added in v0.2.0

type MoveOptions struct {
	// Offset is the block translation applied to everything in the world.
	Offset cube.Pos
	// Clip permits cutting content that lands outside the vertical range.
	// Without it, a move that would lose anything fails with ErrWouldClip.
	Clip bool
	// DryRun computes the report without writing anything.
	DryRun bool
	// Backup copies the current files into snapshots/pre-move before writing.
	Backup bool
	// KeepUserData permits moving a world that carries application metadata,
	// which pile copies through untranslated. Without it such a move fails
	// with ErrUnmovableUserData.
	KeepUserData bool
	// Registry used for block resolution; nil uses world.DefaultBlockRegistry.
	Registry world.BlockRegistry
	// MaxDecoded bounds the live decoded state the world's files may produce,
	// in bytes, as MaxDecodedBytes does for a provider. 0 is the format's own
	// ceiling.
	MaxDecoded int64
}

MoveOptions configures MoveWorld.

type MoveReport added in v0.2.0

type MoveReport struct {
	Offset cube.Pos
	// Chunks is the number of columns translated across all dimensions.
	Chunks int
	// FastPath is true when the offset was chunk-aligned horizontally with no
	// vertical component, so blocks were re-keyed without being rewritten.
	FastPath bool

	// Clipped content counts (non-air blocks, and entries dropped).
	ClippedBlocks        int
	ClippedBlockEntities int
	ClippedEntities      int
	ClippedTicks         int
}

MoveReport describes what a move did (or, for a dry run, would do).

func MoveWorld added in v0.2.0

func MoveWorld(dir string, opt MoveOptions) (*MoveReport, error)

MoveWorld translates a pile world on disk: all blocks, biomes, entities, block entities, scheduled ticks and the spawn position. Files keep their mode (solid or indexed) and are replaced atomically. The move is all-or-nothing across dimensions.

What it does not translate is user data, world-level or per-chunk, because the format stores it as an opaque blob and has no way to find a coordinate inside one. A world carrying any is refused unless MoveOptions.KeepUserData says the caller will re-anchor it: see ErrUnmovableUserData.

func (*MoveReport) ClippedTotal added in v0.2.0

func (r *MoveReport) ClippedTotal() int

ClippedTotal sums all clipped content.

type Option added in v0.2.0

type Option func(*config)

Option configures a Provider.

func AppendMode added in v0.2.0

func AppendMode() Option

AppendMode opens the world as indexed/append files instead of solid ones: stores append immediately, saves are cheap checkpoints, columns are decoded on demand and memory stays at directory-plus-palette level. Use for large or frequently saved worlds. Garbage accumulates between compactions; Close compacts automatically past a garbage threshold. Solid files cannot be opened in append mode (convert with `pile mode` first) and vice versa.

func CacheColumns added in v0.2.0

func CacheColumns(n int) Option

CacheColumns keeps up to n decoded columns per dimension in an LRU cache (append mode only), with Morton-neighbour readahead. 0 disables caching.

func Compression added in v0.2.0

func Compression(level format.CompressionLevel) Option

Compression sets the zstd level used for saves. Default: CompressionBest.

func FastSaves added in v0.2.0

func FastSaves() Option

FastSaves compresses saves with multiple threads. Saves get faster; solid files are no longer byte-deterministic across runs, which weakens the "identical content = identical file" property.

func FilterBlockEntity added in v0.2.0

func FilterBlockEntity(f func(chunk.BlockEntity) bool) Option

FilterBlockEntity keeps only block entities for which f returns true when storing.

func FilterColumn added in v0.2.0

func FilterColumn(f func(world.ChunkPos, world.Dimension) bool) Option

FilterColumn refuses entire columns when storing: columns for which f returns false are silently not persisted.

func FilterEntity added in v0.2.0

func FilterEntity(f func(chunk.Entity) bool) Option

FilterEntity keeps only entities for which f returns true when storing.

func LoadSkip added in v0.2.0

func LoadSkip(m SkipMask) Option

LoadSkip drops data categories when columns are loaded, even if present in the file. Useful for template worlds whose entities are spawned by code.

It is not a bound and must not be used as one. The file is decoded in full first: the categories are dropped from the column LoadColumn hands back, with every entity already built, and in solid mode the provider goes on holding them for its lifetime. It removes nothing from the peak cost of opening a hostile world. MaxDecodedBytes is the dial for that, "Loading a file somebody sent you", says what it does and does not cover.

func MaxDecodedBytes added in v0.2.0

func MaxDecodedBytes(n int64) Option

MaxDecodedBytes bounds the live decoded state a world file may produce, in bytes. 0 (the default) is the format's own ceiling, which is where §8 of the specification sets it: near what the format can represent, not near what a deployment wants to spend. A legal 1,161-byte file decodes into 1.12 GiB, and the ceiling above it is four times higher again.

Set it when the worlds being opened are not worlds you wrote. A value above the format's ceiling is clamped down to it, so this can tighten the limit and cannot loosen it.

A file refused under this ceiling is not a corrupt file. The error satisfies errors.Is(err, format.ErrDecodeBudget) and deliberately does not satisfy errors.Is(err, format.ErrCorrupt): it says the file is larger than this provider was told to decode, not that anything is wrong with it.

In append mode the limit is per open world file rather than per column read: it covers the directory the file keeps resident plus one decoded column.

What it charges is decoded columns and decoded section storages. It charges nothing for entities, block entities or scheduled updates, and one legal column may hold 1,048,576 of each: a 4,764-byte file of two such columns decodes into 774 MB under a 64 KiB ceiling, and no setting refuses it that does not also refuse a three-column world. That gap is measured and recorded bound; a caller opening worlds it did not write needs a bound outside the process as well as this one.

func ReadOnly added in v0.2.0

func ReadOnly() Option

ReadOnly opens the provider in read-only mode: all mutating operations are no-ops and the files are never written.

func Registry added in v0.2.0

func Registry(reg world.BlockRegistry) Option

Registry sets the block registry used for encoding and decoding. It is finalized on open. Default: world.DefaultBlockRegistry.

func Skip added in v0.2.0

func Skip(m SkipMask) Option

Skip drops whole data categories when columns are stored. Dropped data never reaches the file, so output stays deterministic regardless of the filtered content.

func StoreLight added in v0.2.0

func StoreLight() Option

StoreLight stores baked light data in saved files. Never needed for correctness (dragonfly recalculates light on chunk load regardless); useful only for consumers that skip that recalculation.

func WithSpawnStore added in v0.2.0

func WithSpawnStore(s SpawnStore) Option

WithSpawnStore plugs an external player spawn store into the provider.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider implements world.Provider backed by pile files, one per dimension (overworld.pile, nether.pile, end.pile). The whole world is held in memory; saves are full deterministic rewrites (temp file + atomic rename), so a saved file never contains garbage.

func NewMemory added in v0.2.0

func NewMemory(opts ...Option) *Provider

NewMemory creates a provider with no backing files: a pure in-memory world. Save and Close are no-ops; use SaveAs to write it to disk explicitly.

func Open added in v0.2.0

func Open(dir string, opts ...Option) (*Provider, error)

Open creates a provider for the world directory dir, loading any existing pile files in it. The directory is created on first save when absent.

func (*Provider) AutoSave added in v0.2.0

func (p *Provider) AutoSave(interval time.Duration) (stop func())

AutoSave starts a background ticker that schedules a coalesced save every interval. The returned stop function halts it; it also stops on Close.

It saves through SaveAsync, so a failed autosave is reported by the next Save or Close and not at the moment it happens, and only the most recent failure is kept. A process that autosaves and ignores Close's return value never learns that any of them failed.

func (*Provider) ChunkCount

func (p *Provider) ChunkCount(dim world.Dimension) int

ChunkCount returns the number of stored columns in a dimension, including base-provider columns not shadowed locally.

func (*Provider) ChunkUserData added in v0.2.0

func (p *Provider) ChunkUserData(pos world.ChunkPos, dim world.Dimension) []byte

ChunkUserData returns a copy of the per-chunk metadata blob, or nil.

func (*Provider) Close

func (p *Provider) Close() error

Close saves pending changes (unless read-only) and marks the provider closed. It waits for any in-flight background save to finish first. In append mode, dimension files past the garbage threshold are compacted.

A Close that fails does not close the provider: the error is returned, the state stays dirty, and Close may be retried once the underlying problem (a full disk, say) is resolved.

func (*Provider) Columns added in v0.2.0

func (p *Provider) Columns(dim world.Dimension) iter.Seq2[world.ChunkPos, *chunk.Column]

Columns iterates over copies of all stored columns of a dimension, including base-provider columns not shadowed locally.

The set of positions is fixed when iteration starts, so storing a column at a new position during iteration is safe and not observed. Each column is produced when it is reached, one at a time: iterating does not require memory for the whole dimension, and a caller that stops early pays only for what it took. In append mode that means a column overwritten during the iteration may be yielded with its new content.

func (*Provider) DeleteSnapshot added in v0.2.0

func (p *Provider) DeleteSnapshot(name string) error

DeleteSnapshot removes a snapshot.

func (*Provider) Dir added in v0.2.0

func (p *Provider) Dir() string

Dir returns the world directory.

func (*Provider) IsReadOnly

func (p *Provider) IsReadOnly() bool

IsReadOnly reports whether the provider was opened read-only.

func (*Provider) IterError added in v0.2.0

func (p *Provider) IterError() error

IterError returns and clears the first error a Columns iteration hit. An iterator cannot report errors itself, so any caller for which a short iteration would mean data loss (conversion, export, backup) must check this after iterating.

func (*Provider) LoadColumn

func (p *Provider) LoadColumn(pos world.ChunkPos, dim world.Dimension) (*chunk.Column, error)

LoadColumn returns a copy of the stored column at pos, falling back to the base provider for instances. If none exists, the error matches ErrNotFound (dragonfly's leveldb.ErrNotFound) as the world.Provider contract requires.

func (*Provider) LoadPlayerSpawnPosition

func (p *Provider) LoadPlayerSpawnPosition(id uuid.UUID) (cube.Pos, bool, error)

LoadPlayerSpawnPosition delegates to the configured SpawnStore; without one, no spawn exists. Pile files never contain player data.

func (*Provider) Rollback added in v0.2.0

func (p *Provider) Rollback(name string) error

Rollback replaces the world's current state with a snapshot. All unsaved and saved changes since the snapshot are discarded; the provider reloads from the restored files.

func (*Provider) Save

func (p *Provider) Save() error

Save synchronously writes all dirty dimensions. Unchanged dimensions are not rewritten unless world metadata changed (metadata is duplicated into every dimension file). A failure from an earlier background save is surfaced here (and cleared). Encoding and file I/O run outside the provider lock, so a save does not stall the world.

func (*Provider) SaveAs added in v0.2.0

func (p *Provider) SaveAs(dir string) error

SaveAs writes the provider's complete current state (including base-provider columns for instances) as a fresh world at dir. The provider itself is unchanged; an in-memory instance stays in memory. Works on read-only providers: it writes a copy, not the source.

func (*Provider) SaveAsync

func (p *Provider) SaveAsync()

SaveAsync schedules a background save. Multiple calls coalesce; the last scheduled save always observes the latest state. No-op when read-only.

It reports nothing, because there is nobody to report to. A background save that fails is remembered and returned by the next Save or Close, so the failure is not lost — but only the most recent one is kept, and a process that only ever calls SaveAsync never observes any of them. Anything that must know a save succeeded has to call Save.

func (*Provider) SavePlayerSpawnPosition

func (p *Provider) SavePlayerSpawnPosition(id uuid.UUID, pos cube.Pos) error

SavePlayerSpawnPosition delegates to the configured SpawnStore, if any.

func (*Provider) SaveSettings

func (p *Provider) SaveSettings(s *world.Settings)

SaveSettings stores the world settings, persisted on the next save.

func (*Provider) SetChunkUserData added in v0.2.0

func (p *Provider) SetChunkUserData(pos world.ChunkPos, dim world.Dimension, b []byte) error

SetChunkUserData stores a per-chunk metadata blob. It is a no-op if no column is stored at pos.

func (*Provider) SetUserData added in v0.1.6

func (p *Provider) SetUserData(b []byte)

SetUserData stores the world's application metadata blob.

func (*Provider) Settings

func (p *Provider) Settings() *world.Settings

Settings returns the world settings.

The pointer is the provider's own, not a copy: world.Provider's contract has dragonfly take it at startup, mutate it as the world runs and hand it back to SaveSettings at shutdown. A caller that changes settings outside that cycle must still call SaveSettings, because nothing observes a write through this pointer and the world would not be marked dirty.

func (*Provider) Snapshot added in v0.2.0

func (p *Provider) Snapshot(name string) error

Snapshot saves the current state and copies the world's files into snapshots/<name>, replacing any snapshot of the same name. Not supported on read-only or in-memory providers.

func (*Provider) Snapshots added in v0.2.0

func (p *Provider) Snapshots() ([]string, error)

Snapshots lists the world's snapshot names.

func (*Provider) StoreColumn

func (p *Provider) StoreColumn(pos world.ChunkPos, dim world.Dimension, col *chunk.Column) error

StoreColumn stores a copy of col at pos, applying the configured skip mask and filters. Read-only providers ignore the call.

func (*Provider) UserData added in v0.2.0

func (p *Provider) UserData() []byte

UserData returns a copy of the world's application metadata blob.

type SkipMask added in v0.2.0

type SkipMask uint32

SkipMask selects data categories to drop when storing columns.

const (
	// SkipEntities drops all entities.
	SkipEntities SkipMask = 1 << iota
	// SkipBlockEntities drops all block entities.
	SkipBlockEntities
	// SkipScheduledTicks drops all scheduled block updates.
	SkipScheduledTicks
	// SkipBiomes stores no biome data; loading yields biome 0 everywhere.
	SkipBiomes
	// SkipChunkUserData drops per-chunk user data.
	SkipChunkUserData
)

type SpawnStore added in v0.2.0

type SpawnStore interface {
	LoadPlayerSpawn(id uuid.UUID) (pos cube.Pos, exists bool, err error)
	SavePlayerSpawn(id uuid.UUID, pos cube.Pos) error
}

SpawnStore is an optional external store for player spawn positions. Pile keeps no player data in its files; without a SpawnStore the provider reports no spawn for every player.

type Structure added in v0.2.0

type Structure struct {
	// contains filtered or unexported fields
}

Structure is a block region stored in the pile structure format. It implements world.Structure, so it can be built with tx.BuildStructure; the PasteInto fast path additionally carries entities and block entity NBT, which the world.Structure interface cannot express.

func ExtractStructure added in v0.2.0

func ExtractStructure(p *Provider, dim world.Dimension, lo, hi cube.Pos, opts ...StructureOption) (*Structure, error)

ExtractStructure copies the block region [lo, hi] (inclusive) of a dimension from a provider into a new Structure, including block entities and entities inside the region. Options apply to the resulting structure.

func LoadStructure added in v0.2.0

func LoadStructure(path string, opts ...StructureOption) (*Structure, error)

LoadStructure reads a structure file.

func (*Structure) At added in v0.2.0

func (s *Structure) At(x, y, z int, _ func(x, y, z int) world.Block) (world.Block, world.Liquid)

At returns the block at a structure-local position, implementing world.Structure. Blocks carrying NBT (chests, signs, ...) are returned with their block entity data decoded into the block.

func (*Structure) Data added in v0.2.0

func (s *Structure) Data() *format.StructureData

Data exposes the underlying structure data. It is the Structure's own, not a copy: mutating it mutates the Structure, and the encoder requires Cells to stay exactly CellDims(Size) long, so a caller that resizes one without the other has built a value that will not save.

func (*Structure) Dimensions added in v0.2.0

func (s *Structure) Dimensions() [3]int

Dimensions returns the structure's size, implementing world.Structure.

func (*Structure) PasteInto added in v0.2.0

func (s *Structure) PasteInto(p *Provider, dim world.Dimension, at cube.Pos) error

PasteInto writes the structure into a provider's stored world at position `at` (the structure's origin offset is applied). Blocks, block entities and entities are carried; with SkipAir, air positions leave existing blocks untouched. Positions outside the dimension's range are dropped.

func (*Structure) Rotate added in v0.2.0

func (s *Structure) Rotate(quarters int) *Structure

Rotate returns a copy of the structure rotated clockwise (viewed from above) by quarters*90 degrees around the Y axis. Positions of blocks, block entities and entities rotate exactly; the paste anchor resets to zero.

Block state rotation is best effort: the common Bedrock direction properties (facing_direction, direction, weirdo_direction, pillar_axis, ground_sign_direction, minecraft:cardinal_direction and string facing properties) rotate correctly, which covers stairs, logs, signs, doors, furnaces, chests and most other rotatable blocks. Exotic properties (rail curves and similar) keep their state and may need manual fixing.

func (*Structure) Save added in v0.2.0

func (s *Structure) Save(path string) error

Save writes the structure to a file atomically.

type StructureLibrary added in v0.2.0

type StructureLibrary struct {
	// contains filtered or unexported fields
}

StructureLibrary is a set of structures loaded from a directory, looked up by file basename (without the .pile extension).

func LoadStructureLibrary added in v0.2.0

func LoadStructureLibrary(dir string, opts ...StructureOption) (*StructureLibrary, error)

LoadStructureLibrary loads every *.pile structure in a directory. Options apply to each structure. Non-structure pile files fail the load: a library directory holds structures only.

func (*StructureLibrary) Get added in v0.2.0

func (l *StructureLibrary) Get(name string) (*Structure, bool)

Get returns a structure by name.

func (*StructureLibrary) Len added in v0.2.0

func (l *StructureLibrary) Len() int

Len returns the number of structures.

func (*StructureLibrary) Names added in v0.2.0

func (l *StructureLibrary) Names() []string

Names returns all structure names, sorted.

type StructureOption added in v0.2.0

type StructureOption func(*Structure)

StructureOption configures a Structure.

func SkipAir added in v0.2.0

func SkipAir() StructureOption

SkipAir makes At return nil for air cells, so building the structure onto a world leaves existing blocks in air positions untouched. PasteInto honours it the same way.

func StructureMaxDecodedBytes added in v0.2.0

func StructureMaxDecodedBytes(n int64) StructureOption

StructureMaxDecodedBytes bounds the live decoded state LoadStructure may produce, in bytes. It is MaxDecodedBytes for structure files: 0 (the default) is the format's own ceiling, a value above it is clamped down, and a refusal reports format.ErrDecodeBudget rather than claiming the file is corrupt.

It has the same blind spot the provider's does, and it matters more here because a structure is a single object: the ceiling charges decoded cells and section storages, and charges nothing for the block entities and entities a structure carries.

func StructureRegistry added in v0.2.0

func StructureRegistry(reg world.BlockRegistry) StructureOption

StructureRegistry sets the block registry used to resolve blocks. Default: world.DefaultBlockRegistry.

type Template added in v0.2.0

type Template struct {
	// contains filtered or unexported fields
}

Template is a shared, read-only base world from which any number of independent instances can be created. The template's decoded state is shared by all instances copy-on-write: an instance only holds the columns it modified.

func OpenTemplate added in v0.2.0

func OpenTemplate(dir string, opts ...Option) (*Template, error)

OpenTemplate opens a world directory as a read-only template.

func (*Template) Close added in v0.2.0

func (t *Template) Close() error

Close closes the underlying provider.

func (*Template) Instance added in v0.2.0

func (t *Template) Instance(opts ...Option) *Provider

Instance creates an in-memory world.Provider backed by the template. Loads fall through to the template until a column is stored; stores stay in the instance. Close discards the instance; SaveAs persists it as a new world. Options configure the instance (filters, compression for SaveAs).

func (*Template) Provider added in v0.2.0

func (t *Template) Provider() *Provider

Provider returns the template's own read-only provider, for serving the template world directly.

type WorldFiles added in v0.2.0

type WorldFiles struct {
	Dims []DimFile

	Settings []byte
	UserData []byte
}

WorldFiles is a world directory loaded for offline tooling (move, diff, patch, prune). It holds every dimension's columns plus the world metadata blobs.

func LoadWorldFiles added in v0.2.0

func LoadWorldFiles(dir string, reg world.BlockRegistry, opts ...Option) (*WorldFiles, error)

LoadWorldFiles loads all dimension files of a world directory, regardless of their mode. Intended for offline tools; servers use Open.

Options are accepted so a tool reading a world it did not write can set MaxDecodedBytes; only the decode policy is read, since everything else an Option carries is about a running provider. It has no ceiling by default, which is the same position Open is in and for the same reason.

func (*WorldFiles) Backup added in v0.2.0

func (wf *WorldFiles) Backup(dir, name string) error

Backup copies the world's current dimension files into snapshots/<name>, replacing any previous backup of that name.

func (*WorldFiles) Dim added in v0.2.0

func (wf *WorldFiles) Dim(dim world.Dimension) *DimFile

Dim returns the loaded data for a dimension, or nil.

func (*WorldFiles) Write added in v0.2.0

func (wf *WorldFiles) Write(dir string, reg world.BlockRegistry) error

Write writes every dimension back into dir. All dimensions are encoded and fsynced to temporary files first and only then renamed into place, so a failure while encoding or writing any dimension leaves the whole world untouched. The renames themselves are not a single atomic transaction: a crash inside the rename phase can leave a world with some dimensions updated (each individual file is always intact and consistent).

Directories

Path Synopsis
cmd
pile command
Command pile is the tooling CLI for the pile world format: conversion to and from dragonfly's leveldb format (mcdb), inspection and verification.
Command pile is the tooling CLI for the pile world format: conversion to and from dragonfly's leveldb format (mcdb), inspection and verification.
Package format implements the Pile v2 world file format: a compact, deterministic, single-file world container designed around dragonfly's chunk types.
Package format implements the Pile v2 world file format: a compact, deterministic, single-file world container designed around dragonfly's chunk types.
internal
lru
Package lru holds the bounded least-recently-used map used by both the provider's column and metadata caches and the format package's shared zstd dictionary codecs.
Package lru holds the bounded least-recently-used map used by both the provider's column and metadata caches and the format package's shared zstd dictionary codecs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL