X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Femerge.cpp;h=55ae99caf25e657f609e575c503f453d45e79d9a;hb=7aea5cb88f9a8cc9f9ca52ecd4d13cfd7ab16e69;hp=499aaf291916d962818f1bccd50967e0d957d33b;hpb=5961106f9ffd9e9a2bc23e4bd700f6ce07505c9a;p=minetest.git diff --git a/src/emerge.cpp b/src/emerge.cpp index 499aaf291..55ae99caf 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -19,447 +19,713 @@ with this program; if not, write to the Free Software Foundation, Inc., */ -#include "server.h" +#include "emerge.h" + #include #include -#include "clientserver.h" -#include "map.h" -#include "jmutexautolock.h" -#include "main.h" -#include "constants.h" -#include "voxel.h" + +#include "util/container.h" +#include "util/thread.h" +#include "threading/event.h" + #include "config.h" -#include "mapblock.h" -#include "serverobject.h" -#include "settings.h" -#include "script.h" -#include "scriptapi.h" -#include "profiler.h" +#include "constants.h" +#include "environment.h" #include "log.h" +#include "map.h" +#include "mapblock.h" +#include "mapgen/mg_biome.h" +#include "mapgen/mg_ore.h" +#include "mapgen/mg_decoration.h" +#include "mapgen/mg_schematic.h" #include "nodedef.h" -#include "biome.h" -#include "emerge.h" -#include "mapgen_v6.h" -#include "mapgen_v7.h" -#include "mapgen_indev.h" -#include "mapgen_singlenode.h" - - -/////////////////////////////// Emerge Manager //////////////////////////////// - -EmergeManager::EmergeManager(IGameDef *gamedef) { - //register built-in mapgens - registerMapgen("v6", new MapgenFactoryV6()); - //registerMapgen("v7", new MapgenFactoryV7()); - registerMapgen("indev", new MapgenFactoryIndev()); - registerMapgen("singlenode", new MapgenFactorySinglenode()); - - this->ndef = gamedef->getNodeDefManager(); - this->biomedef = new BiomeDefManager(); - this->params = NULL; - - mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); - - queuemutex.Init(); - - int nthreads; - if (g_settings->get("num_emerge_threads").empty()) { - int nprocs = porting::getNumberOfProcessors(); - // leave a proc for the main thread and one for some other misc threads - nthreads = (nprocs > 2) ? nprocs - 2 : 1; - } else { - nthreads = g_settings->getU16("num_emerge_threads"); +#include "profiler.h" +#include "scripting_server.h" +#include "server.h" +#include "settings.h" +#include "voxel.h" + +class EmergeThread : public Thread { +public: + bool enable_mapgen_debug_info; + int id; + + EmergeThread(Server *server, int ethreadid); + ~EmergeThread() = default; + + void *run(); + void signal(); + + // Requires queue mutex held + bool pushBlock(const v3s16 &pos); + + void cancelPendingItems(); + + static void runCompletionCallbacks( + const v3s16 &pos, EmergeAction action, + const EmergeCallbackList &callbacks); + +private: + Server *m_server; + ServerMap *m_map; + EmergeManager *m_emerge; + Mapgen *m_mapgen; + + Event m_queue_event; + std::queue m_block_queue; + + bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); + + EmergeAction getBlockOrStartGen( + const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); + MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, + std::map *modified_blocks); + + friend class EmergeManager; +}; + +class MapEditEventAreaIgnorer +{ +public: + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): + m_ignorevariable(ignorevariable) + { + if(m_ignorevariable->getVolume() == 0) + *m_ignorevariable = a; + else + m_ignorevariable = NULL; + } + + ~MapEditEventAreaIgnorer() + { + if(m_ignorevariable) + { + assert(m_ignorevariable->getVolume() != 0); + *m_ignorevariable = VoxelArea(); + } } + +private: + VoxelArea *m_ignorevariable; +}; + +EmergeParams::~EmergeParams() +{ + infostream << "EmergeParams: destroying " << this << std::endl; + // Delete everything that was cloned on creation of EmergeParams + delete biomegen; + delete biomemgr; + delete oremgr; + delete decomgr; + delete schemmgr; +} + +EmergeParams::EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, + const BiomeManager *biomemgr, + const OreManager *oremgr, const DecorationManager *decomgr, + const SchematicManager *schemmgr) : + ndef(parent->ndef), + enable_mapgen_debug_info(parent->enable_mapgen_debug_info), + gen_notify_on(parent->gen_notify_on), + gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids), + biomemgr(biomemgr->clone()), oremgr(oremgr->clone()), + decomgr(decomgr->clone()), schemmgr(schemmgr->clone()) +{ + this->biomegen = biomegen->clone(this->biomemgr); +} + +//// +//// EmergeManager +//// + +EmergeManager::EmergeManager(Server *server) +{ + this->ndef = server->getNodeDefManager(); + this->biomemgr = new BiomeManager(server); + this->oremgr = new OreManager(server); + this->decomgr = new DecorationManager(server); + this->schemmgr = new SchematicManager(server); + + // initialized later + this->mgparams = nullptr; + this->biomegen = nullptr; + + // Note that accesses to this variable are not synchronized. + // This is because the *only* thread ever starting or stopping + // EmergeThreads should be the ServerThread. + + enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + + s16 nthreads = 1; + g_settings->getS16NoEx("num_emerge_threads", nthreads); + // If automatic, leave a proc for the main thread and one for + // some other misc thread + if (nthreads == 0) + nthreads = Thread::getNumberOfProcessors() - 2; if (nthreads < 1) nthreads = 1; - - qlimit_total = g_settings->getU16("emergequeue_limit_total"); - qlimit_diskonly = g_settings->get("emergequeue_limit_diskonly").empty() ? - nthreads * 5 + 1 : - g_settings->getU16("emergequeue_limit_diskonly"); - qlimit_generate = g_settings->get("emergequeue_limit_generate").empty() ? - nthreads + 1 : - g_settings->getU16("emergequeue_limit_generate"); - - for (int i = 0; i != nthreads; i++) - emergethread.push_back(new EmergeThread((Server *)gamedef, i)); - + + m_qlimit_total = g_settings->getU32("emergequeue_limit_total"); + // FIXME: these fallback values are probably not good + if (!g_settings->getU32NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly)) + m_qlimit_diskonly = nthreads * 5 + 1; + if (!g_settings->getU32NoEx("emergequeue_limit_generate", m_qlimit_generate)) + m_qlimit_generate = nthreads + 1; + + // don't trust user input for something very important like this + m_qlimit_total = rangelim(m_qlimit_total, 1, 1000000); + m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 1, 1000000); + m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000); + + for (s16 i = 0; i < nthreads; i++) + m_threads.push_back(new EmergeThread(server, i)); + infostream << "EmergeManager: using " << nthreads << " threads" << std::endl; } -EmergeManager::~EmergeManager() { - for (unsigned int i = 0; i != emergethread.size(); i++) { - emergethread[i]->setRun(false); - emergethread[i]->qevent.signal(); - emergethread[i]->stop(); - delete emergethread[i]; - delete mapgen[i]; +EmergeManager::~EmergeManager() +{ + for (u32 i = 0; i != m_threads.size(); i++) { + EmergeThread *thread = m_threads[i]; + + if (m_threads_active) { + thread->stop(); + thread->signal(); + thread->wait(); + } + + delete thread; + + // Mapgen init might not be finished if there is an error during startup. + if (m_mapgens.size() > i) + delete m_mapgens[i]; } - - delete biomedef; - delete params; + + delete biomemgr; + delete oremgr; + delete decomgr; + delete schemmgr; +} + + +BiomeManager *EmergeManager::getWritableBiomeManager() +{ + FATAL_ERROR_IF(!m_mapgens.empty(), + "Writable managers can only be returned before mapgen init"); + return biomemgr; } +OreManager *EmergeManager::getWritableOreManager() +{ + FATAL_ERROR_IF(!m_mapgens.empty(), + "Writable managers can only be returned before mapgen init"); + return oremgr; +} + +DecorationManager *EmergeManager::getWritableDecorationManager() +{ + FATAL_ERROR_IF(!m_mapgens.empty(), + "Writable managers can only be returned before mapgen init"); + return decomgr; +} + +SchematicManager *EmergeManager::getWritableSchematicManager() +{ + FATAL_ERROR_IF(!m_mapgens.empty(), + "Writable managers can only be returned before mapgen init"); + return schemmgr; +} + + +void EmergeManager::initMapgens(MapgenParams *params) +{ + FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised."); + + mgparams = params; + + v3s16 csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); + biomegen = biomemgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize); -void EmergeManager::initMapgens(MapgenParams *mgparams) { - Mapgen *mg; - - if (mapgen.size()) + for (u32 i = 0; i != m_threads.size(); i++) { + EmergeParams *p = new EmergeParams(this, biomegen, + biomemgr, oremgr, decomgr, schemmgr); + infostream << "EmergeManager: Created params " << p + << " for thread " << i << std::endl; + m_mapgens.push_back(Mapgen::createMapgen(params->mgtype, params, p)); + } +} + + +Mapgen *EmergeManager::getCurrentMapgen() +{ + if (!m_threads_active) + return nullptr; + + for (u32 i = 0; i != m_threads.size(); i++) { + EmergeThread *t = m_threads[i]; + if (t->isRunning() && t->isCurrentThread()) + return t->m_mapgen; + } + + return nullptr; +} + + +void EmergeManager::startThreads() +{ + if (m_threads_active) return; - - biomedef->resolveNodeNames(ndef); - - this->params = mgparams; - for (unsigned int i = 0; i != emergethread.size(); i++) { - mg = createMapgen(params->mg_name, 0, params); - if (!mg) { - infostream << "EmergeManager: falling back to mapgen v6" << std::endl; - delete params; - params = createMapgenParams("v6"); - mg = createMapgen("v6", 0, params); - } - mapgen.push_back(mg); + + for (u32 i = 0; i != m_threads.size(); i++) + m_threads[i]->start(); + + m_threads_active = true; +} + + +void EmergeManager::stopThreads() +{ + if (!m_threads_active) + return; + + // Request thread stop in parallel + for (u32 i = 0; i != m_threads.size(); i++) { + m_threads[i]->stop(); + m_threads[i]->signal(); } + + // Then do the waiting for each + for (u32 i = 0; i != m_threads.size(); i++) + m_threads[i]->wait(); + + m_threads_active = false; +} + + +bool EmergeManager::isRunning() +{ + return m_threads_active; } -bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) { - std::map::const_iterator iter; - BlockEmergeData *bedata; - u16 count; - u8 flags = 0; - int idx = 0; - +bool EmergeManager::enqueueBlockEmerge( + session_t peer_id, + v3s16 blockpos, + bool allow_generate, + bool ignore_queue_limits) +{ + u16 flags = 0; if (allow_generate) - flags |= BLOCK_EMERGE_ALLOWGEN; + flags |= BLOCK_EMERGE_ALLOW_GEN; + if (ignore_queue_limits) + flags |= BLOCK_EMERGE_FORCE_QUEUE; + + return enqueueBlockEmergeEx(blockpos, peer_id, flags, NULL, NULL); +} + + +bool EmergeManager::enqueueBlockEmergeEx( + v3s16 blockpos, + session_t peer_id, + u16 flags, + EmergeCompletionCallback callback, + void *callback_param) +{ + EmergeThread *thread = NULL; + bool entry_already_exists = false; { - JMutexAutoLock queuelock(queuemutex); - - count = blocks_enqueued.size(); - if (count >= qlimit_total) - return false; + MutexAutoLock queuelock(m_queue_mutex); - count = peer_queue_count[peer_id]; - u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly; - if (count >= qlimit_peer) + if (!pushBlockEmergeData(blockpos, peer_id, flags, + callback, callback_param, &entry_already_exists)) return false; - - iter = blocks_enqueued.find(p); - if (iter != blocks_enqueued.end()) { - bedata = iter->second; - bedata->flags |= flags; + + if (entry_already_exists) return true; - } - bedata = new BlockEmergeData; - bedata->flags = flags; - bedata->peer_requested = peer_id; - blocks_enqueued.insert(std::make_pair(p, bedata)); - - peer_queue_count[peer_id] = count + 1; - - // insert into the EmergeThread queue with the least items - int lowestitems = emergethread[0]->blockqueue.size(); - for (unsigned int i = 1; i != emergethread.size(); i++) { - int nitems = emergethread[i]->blockqueue.size(); - if (nitems < lowestitems) { - idx = i; - lowestitems = nitems; - } - } - - emergethread[idx]->blockqueue.push(p); + thread = getOptimalThread(); + thread->pushBlock(blockpos); } - emergethread[idx]->qevent.signal(); - + + thread->signal(); + return true; } -int EmergeManager::getGroundLevelAtPoint(v2s16 p) { - if (mapgen.size() == 0 || !mapgen[0]) { - errorstream << "EmergeManager: getGroundLevelAtPoint() called" - " before mapgen initialized" << std::endl; - return 0; - } - - return mapgen[0]->getGroundLevelAtPoint(p); +bool EmergeManager::isBlockInQueue(v3s16 pos) +{ + MutexAutoLock queuelock(m_queue_mutex); + return m_blocks_enqueued.find(pos) != m_blocks_enqueued.end(); } -bool EmergeManager::isBlockUnderground(v3s16 blockpos) { - /* - v2s16 p = v2s16((blockpos.X * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2, - (blockpos.Y * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2); - int ground_level = getGroundLevelAtPoint(p); - return blockpos.Y * (MAP_BLOCKSIZE + 1) <= min(water_level, ground_level); - */ +// +// Mapgen-related helper functions +// - //yuck, but then again, should i bother being accurate? - //the height of the nodes in a single block is quite variable - return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params->water_level; -} +// TODO(hmmmm): Move this to ServerMap +v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize) +{ + s16 coff = -chunksize / 2; + v3s16 chunk_offset(coff, coff, coff); -u32 EmergeManager::getBlockSeed(v3s16 p) { - return (u32)(params->seed & 0xFFFFFFFF) + - p.Z * 38134234 + - p.Y * 42123 + - p.X * 23; + return getContainerPos(blockpos - chunk_offset, chunksize) + * chunksize + chunk_offset; } -Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid, - MapgenParams *mgparams) { - std::map::const_iterator iter; - iter = mglist.find(mgname); - if (iter == mglist.end()) { - errorstream << "EmergeManager; mapgen " << mgname << - " not registered" << std::endl; - return NULL; +int EmergeManager::getSpawnLevelAtPoint(v2s16 p) +{ + if (m_mapgens.empty() || !m_mapgens[0]) { + errorstream << "EmergeManager: getSpawnLevelAtPoint() called" + " before mapgen init" << std::endl; + return 0; } - - MapgenFactory *mgfactory = iter->second; - return mgfactory->createMapgen(mgid, mgparams, this); + + return m_mapgens[0]->getSpawnLevelAtPoint(p); } -MapgenParams *EmergeManager::createMapgenParams(std::string mgname) { - std::map::const_iterator iter; - iter = mglist.find(mgname); - if (iter == mglist.end()) { - errorstream << "EmergeManager: mapgen " << mgname << - " not registered" << std::endl; - return NULL; +// TODO(hmmmm): Move this to ServerMap +bool EmergeManager::isBlockUnderground(v3s16 blockpos) +{ + // Use a simple heuristic + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= mgparams->water_level; +} + +bool EmergeManager::pushBlockEmergeData( + v3s16 pos, + u16 peer_requested, + u16 flags, + EmergeCompletionCallback callback, + void *callback_param, + bool *entry_already_exists) +{ + u32 &count_peer = m_peer_queue_count[peer_requested]; + + if ((flags & BLOCK_EMERGE_FORCE_QUEUE) == 0) { + if (m_blocks_enqueued.size() >= m_qlimit_total) + return false; + + if (peer_requested != PEER_ID_INEXISTENT) { + u32 qlimit_peer = (flags & BLOCK_EMERGE_ALLOW_GEN) ? + m_qlimit_generate : m_qlimit_diskonly; + if (count_peer >= qlimit_peer) + return false; + } else { + // limit block enqueue requests for active blocks to 1/2 of total + if (count_peer * 2 >= m_qlimit_total) + return false; + } } - - MapgenFactory *mgfactory = iter->second; - return mgfactory->createMapgenParams(); + + std::pair::iterator, bool> findres; + findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData())); + + BlockEmergeData &bedata = findres.first->second; + *entry_already_exists = !findres.second; + + if (callback) + bedata.callbacks.emplace_back(callback, callback_param); + + if (*entry_already_exists) { + bedata.flags |= flags; + } else { + bedata.flags = flags; + bedata.peer_requested = peer_requested; + + count_peer++; + } + + return true; } -MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) { - std::string mg_name = settings->get("mg_name"); - MapgenParams *mgparams = createMapgenParams(mg_name); - if (!mgparams) - return NULL; - - mgparams->mg_name = mg_name; - mgparams->seed = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed"); - mgparams->water_level = settings->getS16("water_level"); - mgparams->chunksize = settings->getS16("chunksize"); - mgparams->flags = settings->getFlagStr("mg_flags", flagdesc_mapgen); - - if (!mgparams->readParams(settings)) { - delete mgparams; - return NULL; +bool EmergeManager::popBlockEmergeData(v3s16 pos, BlockEmergeData *bedata) +{ + auto it = m_blocks_enqueued.find(pos); + if (it == m_blocks_enqueued.end()) + return false; + + *bedata = it->second; + + auto it2 = m_peer_queue_count.find(bedata->peer_requested); + if (it2 == m_peer_queue_count.end()) + return false; + + u32 &count_peer = it2->second; + + assert(count_peer != 0); + count_peer--; + + m_blocks_enqueued.erase(it); + + return true; +} + + +EmergeThread *EmergeManager::getOptimalThread() +{ + size_t nthreads = m_threads.size(); + + FATAL_ERROR_IF(nthreads == 0, "No emerge threads!"); + + size_t index = 0; + size_t nitems_lowest = m_threads[0]->m_block_queue.size(); + + for (size_t i = 1; i < nthreads; i++) { + size_t nitems = m_threads[i]->m_block_queue.size(); + if (nitems < nitems_lowest) { + index = i; + nitems_lowest = nitems; + } } - return mgparams; + + return m_threads[index]; +} + + +//// +//// EmergeThread +//// + +EmergeThread::EmergeThread(Server *server, int ethreadid) : + enable_mapgen_debug_info(false), + id(ethreadid), + m_server(server), + m_map(NULL), + m_emerge(NULL), + m_mapgen(NULL) +{ + m_name = "Emerge-" + itos(ethreadid); } -void EmergeManager::setParamsToSettings(Settings *settings) { - settings->set("mg_name", params->mg_name); - settings->setU64("seed", params->seed); - settings->setS16("water_level", params->water_level); - settings->setS16("chunksize", params->chunksize); - settings->setFlagStr("mg_flags", params->flags, flagdesc_mapgen); +void EmergeThread::signal() +{ + m_queue_event.signal(); +} + - params->writeParams(settings); +bool EmergeThread::pushBlock(const v3s16 &pos) +{ + m_block_queue.push(pos); + return true; } -void EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) { - mglist.insert(std::make_pair(mgname, mgfactory)); - infostream << "EmergeManager: registered mapgen " << mgname << std::endl; +void EmergeThread::cancelPendingItems() +{ + MutexAutoLock queuelock(m_emerge->m_queue_mutex); + + while (!m_block_queue.empty()) { + BlockEmergeData bedata; + v3s16 pos; + + pos = m_block_queue.front(); + m_block_queue.pop(); + + m_emerge->popBlockEmergeData(pos, &bedata); + + runCompletionCallbacks(pos, EMERGE_CANCELLED, bedata.callbacks); + } } -////////////////////////////// Emerge Thread ////////////////////////////////// +void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action, + const EmergeCallbackList &callbacks) +{ + for (size_t i = 0; i != callbacks.size(); i++) { + EmergeCompletionCallback callback; + void *param; + + callback = callbacks[i].first; + param = callbacks[i].second; + + callback(pos, action, param); + } +} -bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) { - std::map::iterator iter; - JMutexAutoLock queuelock(emerge->queuemutex); - if (blockqueue.empty()) +bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) +{ + MutexAutoLock queuelock(m_emerge->m_queue_mutex); + + if (m_block_queue.empty()) return false; - v3s16 p = blockqueue.front(); - blockqueue.pop(); - - *pos = p; - - iter = emerge->blocks_enqueued.find(p); - if (iter == emerge->blocks_enqueued.end()) - return false; //uh oh, queue and map out of sync!! - - BlockEmergeData *bedata = iter->second; - *flags = bedata->flags; - - emerge->peer_queue_count[bedata->peer_requested]--; - - delete bedata; - emerge->blocks_enqueued.erase(iter); - + + *pos = m_block_queue.front(); + m_block_queue.pop(); + + m_emerge->popBlockEmergeData(*pos, bedata); + return true; } -bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, - BlockMakeData *data, bool allow_gen) { - v2s16 p2d(p.X, p.Z); - //envlock: usually takes <=1ms, sometimes 90ms or ~400ms to acquire - JMutexAutoLock envlock(m_server->m_env_mutex); - - // Load sector if it isn't loaded - if (map->getSectorNoGenerateNoEx(p2d) == NULL) - map->loadSectorMeta(p2d); - - // Attempt to load block - MapBlock *block = map->getBlockNoCreateNoEx(p); - if (!block || block->isDummy() || !block->isGenerated()) { - EMERGE_DBG_OUT("not in memory, attempting to load from disk"); - block = map->loadBlock(p); +EmergeAction EmergeThread::getBlockOrStartGen( + const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *bmdata) +{ + MutexAutoLock envlock(m_server->m_env_mutex); + + // 1). Attempt to fetch block from memory + *block = m_map->getBlockNoCreateNoEx(pos); + if (*block && !(*block)->isDummy()) { + if ((*block)->isGenerated()) + return EMERGE_FROM_MEMORY; + } else { + // 2). Attempt to load block from disk if it was not in the memory + *block = m_map->loadBlock(pos); + if (*block && (*block)->isGenerated()) + return EMERGE_FROM_DISK; } - // If could not load and allowed to generate, - // start generation inside this same envlock - if (allow_gen && (block == NULL || !block->isGenerated())) { - EMERGE_DBG_OUT("generating"); - *b = block; - return map->initBlockMake(data, p); + // 3). Attempt to start generation + if (allow_gen && m_map->initBlockMake(pos, bmdata)) + return EMERGE_GENERATED; + + // All attempts failed; cancel this block emerge + return EMERGE_CANCELLED; +} + + +MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, + std::map *modified_blocks) +{ + MutexAutoLock envlock(m_server->m_env_mutex); + ScopeProfiler sp(g_profiler, + "EmergeThread: after Mapgen::makeChunk", SPT_AVG); + + /* + Perform post-processing on blocks (invalidate lighting, queue liquid + transforms, etc.) to finish block make + */ + m_map->finishBlockMake(bmdata, modified_blocks); + + MapBlock *block = m_map->getBlockNoCreateNoEx(pos); + if (!block) { + errorstream << "EmergeThread::finishGen: Couldn't grab block we " + "just generated: " << PP(pos) << std::endl; + return NULL; } - - *b = block; - return false; + + v3s16 minp = bmdata->blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = bmdata->blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + // Ignore map edit events, they will not need to be sent + // to anybody because the block hasn't been sent to anybody + MapEditEventAreaIgnorer ign( + &m_server->m_ignore_map_edit_events_area, + VoxelArea(minp, maxp)); + + /* + Run Lua on_generated callbacks + */ + try { + m_server->getScriptIface()->environment_OnGenerated( + minp, maxp, m_mapgen->blockseed); + } catch (LuaError &e) { + m_server->setAsyncFatalError(e); + } + + EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); + + /* + Clear mapgen state + */ + assert(!m_mapgen->generating); + m_mapgen->gennotify.clearEvents(); + m_mapgen->vm = nullptr; + + /* + Activate the block + */ + m_server->m_env->activateBlock(block, 0); + + return block; } -void *EmergeThread::Thread() { - ThreadStarted(); - log_register_thread("EmergeThread" + id); - DSTACK(__FUNCTION_NAME); +void *EmergeThread::run() +{ BEGIN_DEBUG_EXCEPTION_HANDLER - v3s16 last_tried_pos(-32768,-32768,-32768); // For error output - v3s16 p; - u8 flags; - - map = (ServerMap *)&(m_server->m_env->getMap()); - emerge = m_server->m_emerge; - mapgen = emerge->mapgen[id]; - enable_mapgen_debug_info = emerge->mapgen_debug_info; - - while (getRun()) + v3s16 pos; + std::map modified_blocks; + + m_map = &m_server->m_env->getServerMap(); + m_emerge = m_server->m_emerge; + m_mapgen = m_emerge->m_mapgens[id]; + enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; + try { - if (!popBlockEmerge(&p, &flags)) { - qevent.wait(); + while (!stopRequested()) { + BlockEmergeData bedata; + BlockMakeData bmdata; + EmergeAction action; + MapBlock *block = nullptr; + + if (!popBlockEmerge(&pos, &bedata)) { + m_queue_event.wait(); continue; } - last_tried_pos = p; - if (blockpos_over_limit(p)) + if (blockpos_over_max_limit(pos)) continue; - bool allow_generate = flags & BLOCK_EMERGE_ALLOWGEN; - EMERGE_DBG_OUT("p=" PP(p) " allow_generate=" << allow_generate); - - /* - Try to fetch block from memory or disk. - If not found and asked to generate, initialize generator. - */ - BlockMakeData data; - MapBlock *block = NULL; - std::map modified_blocks; - - if (getBlockOrStartGen(p, &block, &data, allow_generate)) { - { - ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG); - TimeTaker t("mapgen::make_block()"); + bool allow_gen = bedata.flags & BLOCK_EMERGE_ALLOW_GEN; + EMERGE_DBG_OUT("pos=" PP(pos) " allow_gen=" << allow_gen); - mapgen->makeChunk(&data); + action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); + if (action == EMERGE_GENERATED) { + { + ScopeProfiler sp(g_profiler, + "EmergeThread: Mapgen::makeChunk", SPT_AVG); - if (enable_mapgen_debug_info == false) - t.stop(true); // Hide output + m_mapgen->makeChunk(&bmdata); } - { - //envlock: usually 0ms, but can take either 30 or 400ms to acquire - JMutexAutoLock envlock(m_server->m_env_mutex); - ScopeProfiler sp(g_profiler, "EmergeThread: after " - "Mapgen::makeChunk (envlock)", SPT_AVG); - - map->finishBlockMake(&data, modified_blocks); - - block = map->getBlockNoCreateNoEx(p); - if (block) { - /* - Do some post-generate stuff - */ - v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE; - v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE + - v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); - - // Ignore map edit events, they will not need to be sent - // to anybody because the block hasn't been sent to anybody - MapEditEventAreaIgnorer - ign(&m_server->m_ignore_map_edit_events_area, - VoxelArea(minp, maxp)); - { // takes about 90ms with -O1 on an e3-1230v2 - scriptapi_environment_on_generated(m_server->m_lua, - minp, maxp, emerge->getBlockSeed(minp)); - } - - EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); - - m_server->m_env->activateBlock(block, 0); - } - } + block = finishGen(pos, &bmdata, &modified_blocks); + if (!block) + action = EMERGE_ERRORED; } - /* - Set sent status of modified blocks on clients - */ + runCompletionCallbacks(pos, action, bedata.callbacks); - // NOTE: Server's clients are also behind the connection mutex - //conlock: consistently takes 30-40ms to acquire - JMutexAutoLock lock(m_server->m_con_mutex); - // Add the originally fetched block to the modified list if (block) - modified_blocks[p] = block; - - // Set the modified blocks unsent for all the clients - for (std::map::iterator - i = m_server->m_clients.begin(); - i != m_server->m_clients.end(); ++i) { - RemoteClient *client = i->second; - if (modified_blocks.size() > 0) { - // Remove block from sent history - client->SetBlocksNotSent(modified_blocks); - } - } + modified_blocks[pos] = block; + + if (!modified_blocks.empty()) + m_server->SetBlocksNotSent(modified_blocks); + modified_blocks.clear(); } - catch (VersionMismatchException &e) { + } catch (VersionMismatchException &e) { std::ostringstream err; - err << "World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); - } - catch (SerializationError &e) { + } catch (SerializationError &e) { std::ostringstream err; - err << "Invalid data in MapBlock "<setAsyncFatalError(err.str()); } - - END_DEBUG_EXCEPTION_HANDLER(errorstream) - log_deregister_thread(); + + cancelPendingItems(); + + END_DEBUG_EXCEPTION_HANDLER return NULL; }