From 25a784be0417af724bc963d8432dc4393b793117 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 12:35:13 +0200 Subject: [PATCH] feat(core): implement per-tick world simulation orchestrator --- core/src/WorldSim.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 core/src/WorldSim.cpp diff --git a/core/src/WorldSim.cpp b/core/src/WorldSim.cpp new file mode 100644 index 0000000..89e8b9e --- /dev/null +++ b/core/src/WorldSim.cpp @@ -0,0 +1,206 @@ +#include "vf/WorldSim.h" +#include "vf/Climate.h" + +namespace vf { + +namespace { + +long long atLeastOne(long long value) { return value < 1 ? 1 : value; } + +ClimateConfig toClimateConfig(const WorldClimateSettings& settings) { + ClimateConfig config; + config.seaLevel = settings.seaLevel; + config.heightLapse = settings.heightLapse; + config.latitudeLapse = settings.latitudeLapse; + config.freezeTemperature = settings.freezeTemperature; + config.meltTemperature = settings.meltTemperature; + config.frostSpreadTemperature = settings.frostSpreadTemperature; + config.maxSnowDepth = settings.maxSnowDepth; + config.packThreshold = settings.packThreshold; + return config; +} + +uint64_t fnv1a(uint64_t hash, uint8_t byte) { + hash ^= byte; + hash *= 1099511628211ull; + return hash; +} + +} // namespace + +WorldSim::WorldSim(World& world, const Vec3i& regionOrigin, const Vec3i& regionDims, + const WorldSimConfig& config) + : world(world), + origin(regionOrigin), + dims(regionDims), + tickCounter(0), + weatherInterval(atLeastOne(config.weatherIntervalTicks)), + climateInterval(atLeastOne(config.climateIntervalTicks)), + logisticsInterval(atLeastOne(config.logisticsIntervalTicks)), + mobDt(config.mobDt), + clock(config.startTick, config.time), + sky(config.worldSeed, config.weather), + biomes(config.biome), + weatherClimate(std::make_unique(biomes, toClimateConfig(config.climate))), + crops(config.worldSeed, config.farming), + randomTick(config.worldSeed, config.randomTicksPerChunk), + scheduler(config.worldSeed, config.blockRandomTicksPerChunk), + redstone(std::make_unique(world, regionOrigin, regionDims)), + cartConfig(config.cart), + creatureConfig(config.creature), + poweredRail([](const Vec3i&) { return false; }) { + scheduler.setRandomTickHook([](World& w, const Vec3i& pos, Block block) { + if (block == Block::Dirt) tryGrowGrass(w, pos); + }); + + std::size_t volume = static_cast(dims.x) * static_cast(dims.y) * + static_cast(dims.z); + baseline.resize(volume); + clearDirty(); +} + +WorldSim::~WorldSim() = default; + +int WorldSim::localIndexOf(int gx, int gy, int gz) const { + int lx = gx - origin.x; + int ly = gy - origin.y; + int lz = gz - origin.z; + return (ly * dims.z + lz) * dims.x + lx; +} + +void WorldSim::step(long long ticks) { + for (long long i = 0; i < ticks; ++i) { + ++tickCounter; + clock.advance(1); + + if (tickCounter % weatherInterval == 0) sky.advance(weatherInterval); + + runFarmingRandomTicks(); + scheduler.tick(world); + + if (tickCounter % climateInterval == 0) runClimate(); + + redstone->tick(); + redstone->applyLamps(); + + if (tickCounter % logisticsInterval == 0) runLogistics(); + + runMinecarts(); + runCreatures(); + } + recomputeDirty(); +} + +void WorldSim::runFarmingRandomTicks() { + int skyLight = clock.skyLightLevel(); + Vec3i chunkMin{floorDiv(origin.x, chunkSize), floorDiv(origin.y, chunkSize), + floorDiv(origin.z, chunkSize)}; + Vec3i chunkMax{floorDiv(origin.x + dims.x - 1, chunkSize), + floorDiv(origin.y + dims.y - 1, chunkSize), + floorDiv(origin.z + dims.z - 1, chunkSize)}; + + std::vector cells; + for (int cx = chunkMin.x; cx <= chunkMax.x; ++cx) + for (int cy = chunkMin.y; cy <= chunkMax.y; ++cy) + for (int cz = chunkMin.z; cz <= chunkMax.z; ++cz) { + randomTick.sampleGlobal(Vec3i{cx, cy, cz}, tickCounter, cells); + for (const Vec3i& cell : cells) crops.randomTick(world, cell, skyLight); + } +} + +void WorldSim::runClimate() { + Precipitation precip = currentPrecipitation(); + std::vector changes = weatherClimate->tick(world, origin, dims, precip); + Climate::apply(world, changes); +} + +void WorldSim::runLogistics() { + for (int gx = origin.x; gx < origin.x + dims.x; ++gx) + for (int gy = origin.y; gy < origin.y + dims.y; ++gy) + for (int gz = origin.z; gz < origin.z + dims.z; ++gz) { + Vec3i pos{gx, gy, gz}; + switch (world.blockAt(gx, gy, gz)) { + case Block::Hopper: + tickHopper(world, pos, hopperStates[pos], store); + break; + case Block::Dispenser: + tickDispenser(world, pos, dispenserStates[pos], store, *redstone); + break; + case Block::Dropper: + tickDropper(world, pos, dispenserStates[pos], store, *redstone); + break; + default: + break; + } + } +} + +void WorldSim::runMinecarts() { + if (carts.empty()) return; + stepMinecarts(world, carts, cartConfig, poweredRail, store); +} + +void WorldSim::runCreatures() { + for (Creature& creature : mobs) + updateCreature(world, creature, player, hasPlayer, creatureConfig, mobDt); +} + +void WorldSim::recomputeDirty() { + dirtyList.clear(); + for (int gx = origin.x; gx < origin.x + dims.x; ++gx) + for (int gy = origin.y; gy < origin.y + dims.y; ++gy) + for (int gz = origin.z; gz < origin.z + dims.z; ++gz) { + int idx = localIndexOf(gx, gy, gz); + if (world.blockAt(gx, gy, gz) != baselineBlock(idx)) + dirtyList.push_back(Vec3i{gx, gy, gz}); + } +} + +void WorldSim::clearDirty() { + for (int gx = origin.x; gx < origin.x + dims.x; ++gx) + for (int gy = origin.y; gy < origin.y + dims.y; ++gy) + for (int gz = origin.z; gz < origin.z + dims.z; ++gz) + baseline[static_cast(localIndexOf(gx, gy, gz))] = + world.blockAt(gx, gy, gz); + dirtyList.clear(); +} + +uint64_t WorldSim::regionHash() const { + uint64_t hash = 1469598103934665603ull; + for (int gx = origin.x; gx < origin.x + dims.x; ++gx) + for (int gy = origin.y; gy < origin.y + dims.y; ++gy) + for (int gz = origin.z; gz < origin.z + dims.z; ++gz) + hash = fnv1a(hash, static_cast(world.blockAt(gx, gy, gz))); + return hash; +} + +int WorldSim::snowDepthAt(const Vec3i& pos) const { return weatherClimate->snowDepthAt(pos); } + +int WorldSim::iceAgeAt(const Vec3i& pos) const { return weatherClimate->iceAgeAt(pos); } + +float WorldSim::temperatureAt(int gx, int gy, int gz) const { + return weatherClimate->temperatureAt(gx, gy, gz); +} + +Precipitation WorldSim::currentPrecipitation() const { + Vec3i center{origin.x + dims.x / 2, origin.y + dims.y / 2, origin.z + dims.z / 2}; + float temperature = weatherClimate->temperatureAt(center.x, center.y, center.z); + return sky.precipitationAt(temperature); +} + +void WorldSim::addMinecart(const Minecart& cart) { carts.push_back(cart); } + +void WorldSim::setPoweredRailPredicate(std::function predicate) { + poweredRail = std::move(predicate); +} + +void WorldSim::addCreature(const Creature& creature) { mobs.push_back(creature); } + +void WorldSim::setPlayer(const Vec3& pos) { + player = pos; + hasPlayer = true; +} + +void WorldSim::clearPlayer() { hasPlayer = false; } + +} // namespace vf