Minetest源码分析十一:EmergeManager
EmergeThread:生成地图的线程类
class EmergeThread : public JThread
{
public:
Server *m_server;
ServerMap *map;
EmergeManager *emerge;
Mapgen *mapgen;
bool enable_mapgen_debug_info;
int id;
Event qevent;
std::queue
blockqueue;
EmergeThread(Server *server, int ethreadid): JThread(), m_server(server), map(NULL), emerge(NULL), mapgen(NULL), enable_mapgen_debug_info(false), id(ethreadid)
{
}
void *Thread();
bool popBlockEmerge(v3s16 *pos, u8 *flags);
bool getBlockOrStartGen(v3s16 p, MapBlock **b,
BlockMakeData *data, bool allow_generate);
};
成员变量:
Server *m_server:
EmergeManager *emerge:管理Emerge类,有可能支持多线程Emerge。
ServerMap *map:服务端存储地图数据,以及地图初始化
Mapgen *mapgen:地图生成node的实例对象
std::queue<v3s16> blockqueue:存储blocks位置的队列。
std::queue<v3s16> blockqueue 什么时候变更值?
1)线程中变更、使用数据:EmergeThread::popBlockEmerge()方法中使用, v3s16 p = blockqueue.front();blockqueue.pop(); popBlockEmerge()只有在Thread()线程循环中一直使用,即一直循环每次获取一条blockqueue的v3s16数据(需要生成的block位置信息)。
2)线程外变更数据:EmergeManager::enqueueBlockEmerge()这个函数中更改的,emergethread[idx]->blockqueue.push(p),在这里加入需要生成的block位置信息到blockqueue队列中。
方法:
void *Thread();
bool popBlockEmerge(v3s16 *pos, u8 *flags);
bool getBlockOrStartGen(v3s16 p, MapBlock **b,BlockMakeData *data, bool allow_generate);
主要是Thread()这个线程函数,另外两个方法都是在线程中使用的。
1.void *Thread()
生成地图的线程,是一个循环,不断地使用popBlockEmerge()方法获取每次需要生成的block的位置,然后采用getBlockOrStartGen()方法,根据位置获取或者生成出来对应的MapBlock以及其BlockMeshData,最后更新设置server中的clients的not sent blocks列表。
流程图如下:
涉及函数如下:
void *EmergeThread::Thread()
{
ThreadStarted();
log_register_thread("EmergeThread" + itos(id));
DSTACK(__FUNCTION_NAME);
BEGIN_DEBUG_EXCEPTION_HANDLER
v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
v3s16 p;
u8 flags = 0;
map = (ServerMap *)&(m_server->m_env->getMap());
emerge = m_server->m_emerge;
mapgen = emerge->mapgen[id];
enable_mapgen_debug_info = emerge->mapgen_debug_info;
porting::setThreadName("EmergeThread");
while (!StopRequested())
try {
if (!popBlockEmerge(&p, &flags)) {
qevent.wait();
continue;
}
last_tried_pos = p;
if (blockpos_over_limit(p))
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) && mapgen) {
{
ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG);
TimeTaker t("mapgen::make_block()");
mapgen->makeChunk(&data);
if (enable_mapgen_debug_info == false)
t.stop(true); // Hide output
}
{
//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));
try { // takes about 90ms with -O1 on an e3-1230v2
m_server->getScriptIface()->environment_OnGenerated(
minp, maxp, emerge->getBlockSeed(minp));
} catch(LuaError &e) {
m_server->setAsyncFatalError(e.what());
}
EMERGE_DBG_OUT("ended up with: " << analyze_block(block));
m_server->m_env->activateBlock(block, 0);
}
}
}
/*
Set sent status of modified blocks on clients
*/
// Add the originally fetched block to the modified list
if (block)
modified_blocks[p] = block;
if (modified_blocks.size() > 0) {
m_server->SetBlocksNotSent(modified_blocks);
}
}
catch (VersionMismatchException &e) {
std::ostringstream err;
。。。
m_server->setAsyncFatalError(err.str());
}
catch (SerializationError &e) {
std::ostringstream err;
。。。
m_server->setAsyncFatalError(err.str());
}
log_deregister_thread();
return NULL;
}
where启动Emerge线程?
minetest->server.cpp
void Server::AsyncRunStep(bool initial_step) 只有这一处启动,且启动之后,再进入启动会return。
void Server::AsyncRunStep(bool initial_step)
{
。。。
m_emerge->startThreads();
。。。
}
(2)bool popBlockEmerge(v3s16 *pos, u8 *flags)
获取需要生成的block位置以及flag,flag是来确定是否可以generate的,有些譬如边界处超出地图范围了是不需要生成的。位置信息是直接从blockqueue队列变量中获得的,每获得一条block位置信息,就将它从queue中删除掉。flag的信息是通过EmergeManager的std::map变量blocks_enqueued 获得的,通过位置获取对应的BlockEmergeData数据,从而得到flag信息。
使用:Thread()方法中最开始的地方使用,获取pos以及flags来进行获取或者生成p处的block。
涉及到的函数:
struct BlockEmergeData
{
u16 peer_requested;
u8 flags;
};
bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) {
std::map
::iterator iter;
JMutexAutoLock queuelock(emerge->queuemutex);
if (blockqueue.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);
return true;
}
(3)bool getBlockOrStartGen(v3s16 p, MapBlock **b,BlockMakeData *data, bool allow_generate)
参数:p和allow_generate是输入变量,MapBlock **b和BlockMakeData *data是输出变量。
这个方法的作用主要是获取位置处的block,如果获取不到,在允许创建的情况下初始化创建一个block出来。先是Load sector,然后是load block,如果block没有load出来,再考虑创建block。因为Mapblock的信息是在MapSector中管理的,而MapSector是在Map中管理的,所以显示加载sector,获取位置p处的MapSector,若没有就加载。然后再load block,是从MapSector获取(内存中),若获取不到,再从硬盘中加载(数据库、sectors文件等)。最后如果MapBlock仍然为空说明还没有这个数据,此时若允许创建生成,则调用ServerMap的创建Block函数(initBlockMake)。
涉及到的函数:
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);
if (block && block->isGenerated())
map->prepareBlock(block);
}
// 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);
}
*b = block;
return false;
}
struct BlockMakeData {
ManualMapVoxelManipulator *vmanip;
u64 seed;
v3s16 blockpos_min;
v3s16 blockpos_max;
v3s16 blockpos_requested;
UniqueQueue
transforming_liquid;
INodeDefManager *nodedef;
BlockMakeData():
vmanip(NULL),
seed(0),
nodedef(NULL)
{}
~BlockMakeData() { delete vmanip; }
};