world: publish decorated region batches
This commit is contained in:
parent
d8b5d0f79b
commit
c4be38acdd
10 changed files with 471 additions and 25 deletions
194
internal/world/region_generator.go
Normal file
194
internal/world/region_generator.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"regionio/internal/worldgen"
|
||||
)
|
||||
|
||||
// vanillaTerrainCache stores immutable, undecorated terrain snapshots shared
|
||||
// by overlapping region requests. A region generator must still clone these
|
||||
// chunks before mutable feature replay, but neighboring cache misses no longer
|
||||
// rerun the expensive density/carver stage for the same coordinates.
|
||||
type vanillaTerrainCache struct {
|
||||
mu sync.Mutex
|
||||
chunks map[[2]int32]*Chunk
|
||||
loads map[[2]int32]*terrainLoad
|
||||
max int
|
||||
}
|
||||
|
||||
type terrainLoad struct {
|
||||
done chan struct{}
|
||||
chunk *Chunk
|
||||
}
|
||||
|
||||
func newVanillaTerrainCache(max int) *vanillaTerrainCache {
|
||||
return &vanillaTerrainCache{
|
||||
chunks: make(map[[2]int32]*Chunk),
|
||||
loads: make(map[[2]int32]*terrainLoad),
|
||||
max: max,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *vanillaTerrainCache) get(key [2]int32, build func() *Chunk) *Chunk {
|
||||
c.mu.Lock()
|
||||
if chunk := c.chunks[key]; chunk != nil {
|
||||
c.mu.Unlock()
|
||||
return chunk
|
||||
}
|
||||
if load := c.loads[key]; load != nil {
|
||||
c.mu.Unlock()
|
||||
<-load.done
|
||||
return load.chunk
|
||||
}
|
||||
load := &terrainLoad{done: make(chan struct{})}
|
||||
c.loads[key] = load
|
||||
c.mu.Unlock()
|
||||
|
||||
chunk := build()
|
||||
c.mu.Lock()
|
||||
if existing := c.chunks[key]; existing != nil {
|
||||
load.chunk = existing
|
||||
delete(c.loads, key)
|
||||
close(load.done)
|
||||
c.mu.Unlock()
|
||||
return existing
|
||||
}
|
||||
if len(c.chunks) >= c.max {
|
||||
// The cache is an optimization only. Evict one arbitrary old entry when
|
||||
// full; correctness never depends on retaining a particular chunk.
|
||||
for oldKey := range c.chunks {
|
||||
delete(c.chunks, oldKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
c.chunks[key] = chunk
|
||||
load.chunk = chunk
|
||||
delete(c.loads, key)
|
||||
close(load.done)
|
||||
c.mu.Unlock()
|
||||
return chunk
|
||||
}
|
||||
|
||||
func terrainClone(chunk *Chunk) *Chunk {
|
||||
clone, _ := chunk.snapshot()
|
||||
return clone
|
||||
}
|
||||
|
||||
// NewVanillaRegionGenerator builds a target from a mutable five-by-five base
|
||||
// neighborhood. Vanilla feature placement for a center chunk can inspect and
|
||||
// write into adjacent chunks; the radius-two base supplies the complete source
|
||||
// biome neighborhood needed by the nine source centers around that target.
|
||||
//
|
||||
// This generator is intentionally separate from NewVanillaGenerator while its
|
||||
// full decoration parity is being measured. It uses the vanilla-compatible
|
||||
// Xoroshiro feature RNG and region ore replay, then applies the remaining
|
||||
// non-ore decoration to the target.
|
||||
func NewVanillaRegionGenerator(seed int64) Generator {
|
||||
od, fluidPicker, veins, carver := vanillaGeneratorInputs(seed)
|
||||
return vanillaRegionGeneratorFromInputs(seed, od, fluidPicker, veins, carver, newVanillaTerrainCache(256))
|
||||
}
|
||||
|
||||
// NewVanillaRegionBatchGenerator builds one complete 3x3 target batch from a
|
||||
// shared 7x7 base terrain neighborhood. Each target receives private clones of
|
||||
// its 5x5 mutable decoration region, so cross-chunk feature writes cannot leak
|
||||
// into the neighboring target's generation.
|
||||
func NewVanillaRegionBatchGenerator(seed int64) BatchGenerator {
|
||||
od, fluidPicker, veins, carver := vanillaGeneratorInputs(seed)
|
||||
return vanillaRegionBatchGeneratorFromInputs(seed, od, fluidPicker, veins, carver, newVanillaTerrainCache(256))
|
||||
}
|
||||
|
||||
// NewVanillaRegionGenerators returns the region-faithful single and batch
|
||||
// generators sharing one immutable worldgen input set.
|
||||
func NewVanillaRegionGenerators(seed int64) (Generator, BatchGenerator) {
|
||||
od, fluidPicker, veins, carver := vanillaGeneratorInputs(seed)
|
||||
terrain := newVanillaTerrainCache(256)
|
||||
return vanillaRegionGeneratorFromInputs(seed, od, fluidPicker, veins, carver, terrain),
|
||||
vanillaRegionBatchGeneratorFromInputs(seed, od, fluidPicker, veins, carver, terrain)
|
||||
}
|
||||
|
||||
func vanillaRegionGeneratorFromInputs(seed int64, od *worldgen.OverworldDensity, fluidPicker worldgen.FluidPicker, veins *worldgen.OreVeinifier, carver *worldgen.Carver, terrain *vanillaTerrainCache) Generator {
|
||||
return func(targetX, targetZ int32) *Chunk {
|
||||
chunks := make([]*Chunk, 0, 25)
|
||||
for cx := targetX - 2; cx <= targetX+2; cx++ {
|
||||
for cz := targetZ - 2; cz <= targetZ+2; cz++ {
|
||||
key := [2]int32{cx, cz}
|
||||
base := terrain.get(key, func() *Chunk {
|
||||
return generateVanillaWithoutDecoration(od, fluidPicker, veins, carver, seed, cx, cz)
|
||||
})
|
||||
chunks = append(chunks, terrainClone(base))
|
||||
}
|
||||
}
|
||||
region, err := newDecorationRegion(chunks)
|
||||
if err != nil {
|
||||
panic("world: creating decoration region: " + err.Error())
|
||||
}
|
||||
if err := region.replayScheduledOres(seed, targetX, targetZ); err != nil {
|
||||
panic("world: replaying region ores: " + err.Error())
|
||||
}
|
||||
target := region.chunks[[2]int32{targetX, targetZ}]
|
||||
decorateGeneratedNonOre(target, od, seed)
|
||||
return target
|
||||
}
|
||||
}
|
||||
|
||||
func vanillaRegionBatchGeneratorFromInputs(seed int64, od *worldgen.OverworldDensity, fluidPicker worldgen.FluidPicker, veins *worldgen.OreVeinifier, carver *worldgen.Carver, terrain *vanillaTerrainCache) BatchGenerator {
|
||||
return func(targetX, targetZ int32) (map[[2]int32]*Chunk, error) {
|
||||
base := make(map[[2]int32]*Chunk, 49)
|
||||
for cx := targetX - 3; cx <= targetX+3; cx++ {
|
||||
for cz := targetZ - 3; cz <= targetZ+3; cz++ {
|
||||
key := [2]int32{cx, cz}
|
||||
base[key] = terrain.get(key, func() *Chunk {
|
||||
return generateVanillaWithoutDecoration(od, fluidPicker, veins, carver, seed, cx, cz)
|
||||
})
|
||||
}
|
||||
}
|
||||
batch := make(map[[2]int32]*Chunk, 9)
|
||||
for cx := targetX - 1; cx <= targetX+1; cx++ {
|
||||
for cz := targetZ - 1; cz <= targetZ+1; cz++ {
|
||||
chunks := make([]*Chunk, 0, 25)
|
||||
for sx := cx - 2; sx <= cx+2; sx++ {
|
||||
for sz := cz - 2; sz <= cz+2; sz++ {
|
||||
baseChunk := base[[2]int32{sx, sz}]
|
||||
clone, _ := baseChunk.snapshot()
|
||||
chunks = append(chunks, clone)
|
||||
}
|
||||
}
|
||||
region, err := newDecorationRegion(chunks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := region.replayScheduledOres(seed, cx, cz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := region.chunks[[2]int32{cx, cz}]
|
||||
decorateGeneratedNonOre(target, od, seed)
|
||||
batch[[2]int32{cx, cz}] = target
|
||||
}
|
||||
}
|
||||
return batch, nil
|
||||
}
|
||||
}
|
||||
|
||||
func decorateGeneratedNonOre(c *Chunk, od *worldgen.OverworldDensity, seed int64) {
|
||||
var surfTop [16][16]int
|
||||
var grass [16][16]bool
|
||||
var biomeName [16][16]string
|
||||
baseX, baseZ := int(c.X)*16, int(c.Z)*16
|
||||
for x := 0; x < 16; x++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
surfTop[x][z], grass[x][z] = classifyColumnAtSurface(c, x, z)
|
||||
biomeName[x][z] = BiomeNameAt(od, baseX+x, baseZ+z)
|
||||
}
|
||||
}
|
||||
r := newChunkRand(c.X, c.Z, seed)
|
||||
decorateNonOre(c, od, c.X, c.Z, seed, &surfTop, &grass, &biomeName, &r)
|
||||
}
|
||||
|
||||
func classifyColumnAtSurface(c *Chunk, x, z int) (top int, grass bool) {
|
||||
var column [WorldHeight]uint16
|
||||
for i := 0; i < WorldHeight; i++ {
|
||||
column[i] = c.GetBlock(x, MinY+i, z)
|
||||
}
|
||||
return classifyColumn(&column)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue