Okay, so I am trying to rewrite this using async
and I guess I'll just have to improvise when I want to cross compile and can't use _Concurrency
But I have another issue. A Chunk
now owns a Task
which generates it, but I want to express the following properties of my C++ coroutine:
- Chunk tasks should be generated on the same actor, and the task being resumed should always be the one belonging to a chunk closest to the camera
- Chunks generate in stages and depend on each other, because structures like trees can generate across chunks. A given chunk as its final stage of generation awaits its neighbors until before said stage so that it can know the list of blocks which spill over from those chunks — but doesn't await until the end since that would make the neighbors try to resolve their own neighbors and so on, causing an infinite chain of awaiting. Entering that final stage should only be possible for the primary chunk being generated.
Code so far
public func generate() async {
var shuffled = self.position.x ^ self.position.z << 1 ^ Int64(bitPattern: self.world.seed) << 2
// The RNG will do nothing if the seed is 0, take care not to accidentally shuffle one.
for i in Int64(0)... { if shuffled != 0 { break }
// Just to be safe and avoid taking *1 entire second* to type check this expression, split into steps.
let step1 = self.position.x * i
let step2 = self.position.z * i
let step3 = Int64(bitPattern: self.world.seed)
shuffled = step1 ^ step2 << 1 ^ step3 << 2 ^ i << 3
}
var rng = Xoshiro256StarStar(from: UInt64(bitPattern: Int64(shuffled)))
for ix in 0..<Self.side {
for iz in 0..<Self.side {
let fix = Float(ix)
let fiz = Float(iz)
let fx = Float(self.position.x)
let fz = Float(self.position.z)
let posX = fix + Float(Self.side) * fx
let posZ = fiz + Float(Self.side) * fz
func octave(_ frequency: Float, _ amplitude: Float) -> Float {
//perlin(posX * frequency, posZ * frequency, 0, Int32(this->world->seed())) * amplitude
}
func octaved(base frequency: Float) -> Float {
octave(frequency, 0) + octave(frequency * 2, 0.5) + octave(frequency * 4, 0.25)
};
let continentalness = octaved(base: 0.005)
let erosion = octaved(base: 0.01)
let peaks = octaved(base: 0.05)
// Terrain
let base: Float = switch continentalness {
case ..<0.3: continentalness.normalized(from: -1...0.3, to: 50...100)
case 0.3..<0.4: continentalness.normalized(from: 0.3...0.4, to: 100...150)
case _: 150
}
// MARK: - Height pass -------------------------------------------------------------------------------------
let height = base
for iy in 0..<Self.height {
let fiy = Float(iy)
if fiy <= height { self[ix, iy, iz] = Stone.shared }
if fiy > height && fiy < 90.0 { self[ix, iy, iz] = Water.shared }
await Task.yield()
}
// MARK: - Layer pass --------------------------------------------------------------------------------------
let maxThickness = 3 // TODO(!): This should be variable slightly but still determined by seed.
for (currentThickness, iy) in (0..<Self.height).reversed().enumerated() where currentThickness <= maxThickness {
if self[ix, iy, iz].tag == .stone {
self[ix, iy, iz] =
base < 92
? Sand.shared
: currentThickness == 0 ? Grass.shared : Dirt.shared
}
await Task.yield()
}
// TODO(!): This is a duplicate loop but it was accidentally left in
// the C++ version so for now keep it to get identical foliage and tree generation.
for iy in (0..<Self.height).reversed() {
if
iy + 1 < Self.height
&& self[ix, iy, iz].tag == .grass
&& self[ix, iy + 1, iz].tag == .air
&& Float.random(in: 1...100, using: &rng) < 25
{
self[ix, iy + 1, iz] = TallGrass.shared
break
}
await Task.yield()
}
// TODO(!): This could probably be a `where` loop.
for iy in (0..<Self.height).reversed() {
if
iy + 1 < Self.height
&& self[ix, iy, iz].tag == .grass
&& self[ix, iy + 1, iz].tag == .air
&& Float.random(in: 0..<100, using: &rng) < 25
{
self[ix, iy + 1, iz] = TallGrass.shared
break
} else if
iy + 1 < Self.height
&& self[ix, iy, iz].tag == .grass
&& self[ix, iy + 1, iz].tag == .air
&& Float.random(in: 0..<100, using: &rng) < 1
{
self[ix, iy + 1, iz] = Rose.shared
break
}
await Task.yield()
}
}
}
// Tree pass
for ix in 0..<Self.side {
mainLoop:
for iz in 0..<Self.side {
if Float.random(in: 0..<100, using: &rng) > 1 { continue }
let height = Int.random(in: 4...7, using: &rng)
var start: Int? = nil
for iy in (0..<Self.height).reversed() {
if (self[ix, iy, iz].tag == .grass) { start = iy + 1; break }
if (!self[ix, iy, iz].softGeneration) { continue mainLoop }
}
if let start { // TODO(!): This isn't easy to translate into Swift, ignore for now.
//for (Int iy = *start; iy < *start + height + 1 and iy < chunkHeight; iy += 1) {
// if (iy < *start + height) self.safeSoftSetBlockAt(ix, iy, iz, Log.shared)
// if iy > start + height - 4 {
// let radius = iy > start + height - 2 ? 2 : 3
// for (Int tix = -radius + 1; tix < radius; tix += 1) {
// for (Int tiz = -radius + 1; tiz < radius; tiz += 1) {
// this.safeSpillingSoftSetBlockAt(ix + tix, iy, iz + tiz, Leaves.shared)
// }
// }
// }
//}
}
await Task.yield()
}
}
// COMPLETION STAGE - This is the point where generating further creates a dependency on our neighbors.
// We can now inform chunks awaiting on us that we are ready to be referenced for structure generation.
self.stage = .completion
await Task.yield()
let positions = [
Position(x: self.position.x - 1, z: self.position.z - 1),
Position(x: self.position.x - 1, z: self.position.z ),
Position(x: self.position.x - 1, z: self.position.z + 1),
Position(x: self.position.x, z: self.position.z - 1),
Position(x: self.position.x, z: self.position.z + 1),
Position(x: self.position.x + 1, z: self.position.z - 1),
Position(x: self.position.x + 1, z: self.position.z ),
Position(x: self.position.x + 1, z: self.position.z + 1)
]
for neighbor in positions.map { self.world.demandChunkAt($0) } {
while (neighbor.stage == .terrain) {
await Task.yield() // ??????????
}
// for block in neighbor.spill where block.position == self.position
for (position, x, y, z, block) in neighbor.spill where position == self.position {
self.safeSoftSetBlockAt(x, y, z, block)
}
}
await self.relight()
await self.remesh()
// GENERATION END - The chunk is now fully generated and ready for use.
self.stage = .generated
// Remesh neighbors
self.world.remeshNeighbors(self.position)
}
Source code of the Swift version TeamPuzel/BlockGameSwift - BlockGameSwift - Gitea: Git with a cup of tea