Skip to content
Snippets Groups Projects
server.cpp 126 KiB
Newer Older
Perttu Ahola's avatar
Perttu Ahola committed
/*
Minetest-c55
Perttu Ahola's avatar
Perttu Ahola committed
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
Perttu Ahola's avatar
Perttu Ahola committed

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
Perttu Ahola's avatar
Perttu Ahola committed
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
Perttu Ahola's avatar
Perttu Ahola committed

You should have received a copy of the GNU Lesser General Public License along
Perttu Ahola's avatar
Perttu Ahola committed
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

Perttu Ahola's avatar
Perttu Ahola committed
#include "server.h"
#include <iostream>
Perttu Ahola's avatar
Perttu Ahola committed
#include "clientserver.h"
#include "map.h"
#include "jmutexautolock.h"
#include "main.h"
#include "constants.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "config.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "mapblock.h"
#include "settings.h"
#include "profiler.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "script.h"
#include "scriptapi.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "nodedef.h"
#include "craftdef.h"
#include "content_mapnode.h"
#include "content_nodemeta.h"
#include "content_sao.h"
#include "mods.h"
#include "tool.h"
#include "sound.h" // dummySoundManager
#include "event_manager.h"
#include "hex.h"
#include "util/string.h"
#include "util/pointedthing.h"
Perttu Ahola's avatar
Perttu Ahola committed
#include "util/mathconstants.h"
#include "rollback.h"

#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
Perttu Ahola's avatar
Perttu Ahola committed

#define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)

class MapEditEventIgnorer
{
public:
	MapEditEventIgnorer(bool *flag):
		m_flag(flag)
	{
		if(*m_flag == false)
			*m_flag = true;
		else
			m_flag = NULL;
	}

	~MapEditEventIgnorer()
	{
		if(m_flag)
		{
			assert(*m_flag);
			*m_flag = false;
		}
	}
	
private:
	bool *m_flag;
};

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;
};

Perttu Ahola's avatar
Perttu Ahola committed
void * ServerThread::Thread()
{
	ThreadStarted();

Perttu Ahola's avatar
Perttu Ahola committed
	DSTACK(__FUNCTION_NAME);

Perttu Ahola's avatar
Perttu Ahola committed
	while(getRun())
	{
		try{
			//TimeTaker timer("AsyncRunStep() + Receive()");

			{
				//TimeTaker timer("AsyncRunStep()");
				m_server->AsyncRunStep();
			}
Perttu Ahola's avatar
Perttu Ahola committed
		
			//infostream<<"Running m_server->Receive()"<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
			m_server->Receive();
		}
		catch(con::NoIncomingDataException &e)
		{
		}
Perttu Ahola's avatar
Perttu Ahola committed
		catch(con::PeerNotFoundException &e)
		{
			infostream<<"Server: PeerNotFoundException"<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
		}
		catch(con::ConnectionBindFailed &e)
		{
			m_server->setAsyncFatalError(e.what());
		}
		catch(LuaError &e)
		{
			m_server->setAsyncFatalError(e.what());
		}
Perttu Ahola's avatar
Perttu Ahola committed
	}
	
	END_DEBUG_EXCEPTION_HANDLER(errorstream)
Perttu Ahola's avatar
Perttu Ahola committed

	return NULL;
}

void * EmergeThread::Thread()
{
	ThreadStarted();

Perttu Ahola's avatar
Perttu Ahola committed
	DSTACK(__FUNCTION_NAME);

	bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");

	v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Get block info from queue, emerge them and send them
		to clients.

		After queue is empty, exit.
	*/
	while(getRun())
Perttu Ahola's avatar
Perttu Ahola committed
		QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
		if(qptr == NULL)
			break;
		
		SharedPtr<QueuedBlockEmerge> q(qptr);

		v3s16 &p = q->pos;
		//infostream<<"EmergeThread::Thread(): running"<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed

		//TimeTaker timer("block emerge");
Perttu Ahola's avatar
Perttu Ahola committed
		
		/*
			Try to emerge it from somewhere.

			If it is only wanted as optional, only loading from disk
			will be allowed.
		*/
		
		/*
			Check if any peer wants it as non-optional. In that case it
			will be generated.

			Also decrement the emerge queue count in clients.
		*/

Perttu Ahola's avatar
Perttu Ahola committed

		{
			core::map<u16, u8>::Iterator i;
			for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
			{
				//u16 peer_id = i.getNode()->getKey();

				// Check flags
				u8 flags = i.getNode()->getValue();
				if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
Perttu Ahola's avatar
Perttu Ahola committed
				
			}
		}
					<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
					<<"only_from_disk="<<only_from_disk<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
		
Perttu Ahola's avatar
Perttu Ahola committed
		ServerMap &map = ((ServerMap&)m_server->m_env->getMap());
Perttu Ahola's avatar
Perttu Ahola committed
			
		MapBlock *block = NULL;
		bool got_block = true;
		core::map<v3s16, MapBlock*> modified_blocks;
			Try to fetch block from memory or disk.
			If not found and asked to generate, initialize generator.
		
		bool started_generate = false;
		mapgen::BlockMakeData data;

Perttu Ahola's avatar
Perttu Ahola committed
		{
			JMutexAutoLock envlock(m_server->m_env_mutex);
			
			// Load sector if it isn't loaded
			if(map.getSectorNoGenerateNoEx(p2d) == NULL)
			if(!block || block->isDummy() || !block->isGenerated())
Perttu Ahola's avatar
Perttu Ahola committed
			{
					infostream<<"EmergeThread: not in memory, "
							<<"attempting to load from disk"<<std::endl;
			}
			
			// If could not load and allowed to generate, start generation
			// inside this same envlock
			if(only_from_disk == false &&
					(block == NULL || block->isGenerated() == false)){
					infostream<<"EmergeThread: generating"<<std::endl;
				started_generate = true;
		/*
			If generator was initialized, generate now when envlock is free.
		*/
		if(started_generate)
Perttu Ahola's avatar
Perttu Ahola committed
			{
				ScopeProfiler sp(g_profiler, "EmergeThread: mapgen::make_block",
						SPT_AVG);
				TimeTaker t("mapgen::make_block()");

				mapgen::make_block(&data);

				if(enable_mapgen_debug_info == false)
					t.stop(true); // Hide output
			}
				// Lock environment again to access the map
				JMutexAutoLock envlock(m_server->m_env_mutex);
Perttu Ahola's avatar
Perttu Ahola committed
				
				ScopeProfiler sp(g_profiler, "EmergeThread: after "
						"mapgen::make_block (envlock)", SPT_AVG);

				// Blit data back on map, update lighting, add mobs and
				// whatever this does
				map.finishBlockMake(&data, modified_blocks);

				// Get central block
				block = map.getBlockNoCreateNoEx(p);
				
				// If block doesn't exist, don't try doing anything with it
				// This happens if the block is not in generation boundaries
				if(!block)
					break;
				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
				*/
				//MapEditEventIgnorer ign(&m_server->m_ignore_map_edit_events);
				MapEditEventAreaIgnorer ign(
						&m_server->m_ignore_map_edit_events_area,
						VoxelArea(minp, maxp));
				{
					TimeTaker timer("on_generated");
					scriptapi_environment_on_generated(m_server->m_lua,
							minp, maxp, mapgen::get_blockseed(data.seed, minp));
					/*int t = timer.stop(true);
					dstream<<"on_generated took "<<t<<"ms"<<std::endl;*/
				if(enable_mapgen_debug_info)
					infostream<<"EmergeThread: ended up with: "
							<<analyze_block(block)<<std::endl;

Perttu Ahola's avatar
Perttu Ahola committed
		}

Perttu Ahola's avatar
Perttu Ahola committed
		/*
			Set sent status of modified blocks on clients
		*/
	
		// NOTE: Server's clients are also behind the connection mutex
		JMutexAutoLock lock(m_server->m_con_mutex);

		/*
			Add the originally fetched block to the modified list
		*/
		if(got_block)
		{
			modified_blocks.insert(p, block);
		}
		
		/*
			Set the modified blocks unsent for all the clients
		*/
		
		for(core::map<u16, RemoteClient*>::Iterator
				i = m_server->m_clients.getIterator();
				i.atEnd() == false; i++)
		{
			RemoteClient *client = i.getNode()->getValue();
			
			if(modified_blocks.size() > 0)
			{
				// Remove block from sent history
				client->SetBlocksNotSent(modified_blocks);
			}
		}
	}
	catch(VersionMismatchException &e)
	{
		std::ostringstream err;
		err<<"World data version mismatch in MapBlock "<<PP(last_tried_pos)<<std::endl;
		err<<"----"<<std::endl;
		err<<"\""<<e.what()<<"\""<<std::endl;
		err<<"See debug.txt."<<std::endl;
		err<<"World probably saved by a newer version of Minetest."<<std::endl;
		m_server->setAsyncFatalError(err.str());
	}
	catch(SerializationError &e)
	{
		std::ostringstream err;
		err<<"Invalid data in MapBlock "<<PP(last_tried_pos)<<std::endl;
		err<<"----"<<std::endl;
		err<<"\""<<e.what()<<"\""<<std::endl;
		err<<"See debug.txt."<<std::endl;
		err<<"You can ignore this using [ignore_world_load_errors = true]."<<std::endl;
		m_server->setAsyncFatalError(err.str());
Perttu Ahola's avatar
Perttu Ahola committed
	}
	END_DEBUG_EXCEPTION_HANDLER(errorstream)
Perttu Ahola's avatar
Perttu Ahola committed

	log_deregister_thread();

Perttu Ahola's avatar
Perttu Ahola committed
	return NULL;
}

v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const
{
	if(pos_exists) *pos_exists = false;
	switch(type){
	case SSP_LOCAL:
		return v3f(0,0,0);
	case SSP_POSITIONAL:
		if(pos_exists) *pos_exists = true;
		return pos;
	case SSP_OBJECT: {
		if(object == 0)
			return v3f(0,0,0);
		ServerActiveObject *sao = env->getActiveObject(object);
		if(!sao)
			return v3f(0,0,0);
		if(pos_exists) *pos_exists = true;
		return sao->getBasePosition(); }
	}
	return v3f(0,0,0);
}

Perttu Ahola's avatar
Perttu Ahola committed
void RemoteClient::GetNextBlocks(Server *server, float dtime,
		core::array<PrioritySortedBlockTransfer> &dest)
{
	DSTACK(__FUNCTION_NAME);
	
	/*u32 timer_result;
	TimeTaker timer("RemoteClient::GetNextBlocks", &timer_result);*/
	
	// Increment timers
	m_nothing_to_send_pause_timer -= dtime;
Perttu Ahola's avatar
Perttu Ahola committed
	m_nearest_unsent_reset_timer += dtime;
	
	if(m_nothing_to_send_pause_timer >= 0)
		return;
Perttu Ahola's avatar
Perttu Ahola committed

	Player *player = server->m_env->getPlayer(peer_id);
	// This can happen sometimes; clients and players are not in perfect sync.
	if(player == NULL)
		return;
Perttu Ahola's avatar
Perttu Ahola committed
	// Won't send anything if already sending
	if(m_blocks_sending.size() >= g_settings->getU16
			("max_simultaneous_block_sends_per_client"))
		//infostream<<"Not sending any blocks, Queue full."<<std::endl;
	//TimeTaker timer("RemoteClient::GetNextBlocks");
	
Perttu Ahola's avatar
Perttu Ahola committed
	v3f playerpos = player->getPosition();
	v3f playerspeed = player->getSpeed();
	v3f playerspeeddir(0,0,0);
	if(playerspeed.getLength() > 1.0*BS)
		playerspeeddir = playerspeed / playerspeed.getLength();
	// Predict to next block
	v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;
	v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);
Perttu Ahola's avatar
Perttu Ahola committed

	v3s16 center = getNodeBlockPos(center_nodepos);
	
	// Camera position and direction
	v3f camera_pos = player->getEyePosition();
	v3f camera_dir = v3f(0,0,1);
	camera_dir.rotateYZBy(player->getPitch());
	camera_dir.rotateXZBy(player->getYaw());
	/*infostream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
			<<camera_dir.Z<<")"<<std::endl;*/

Perttu Ahola's avatar
Perttu Ahola committed
	/*
		Get the starting value of the block finder radius.
	*/
		
	if(m_last_center != center)
	{
		m_nearest_unsent_d = 0;
		m_last_center = center;
	}
	/*infostream<<"m_nearest_unsent_reset_timer="
			<<m_nearest_unsent_reset_timer<<std::endl;*/
Perttu Ahola's avatar
Perttu Ahola committed
	// Reset periodically to workaround for some bugs or stuff
	if(m_nearest_unsent_reset_timer > 20.0)
	{
		m_nearest_unsent_reset_timer = 0;
		m_nearest_unsent_d = 0;
Perttu Ahola's avatar
Perttu Ahola committed
		//infostream<<"Resetting m_nearest_unsent_d for "
		//		<<server->getPlayerName(peer_id)<<std::endl;
	//s16 last_nearest_unsent_d = m_nearest_unsent_d;
	s16 d_start = m_nearest_unsent_d;
	//infostream<<"d_start="<<d_start<<std::endl;
	u16 max_simul_sends_setting = g_settings->getU16
			("max_simultaneous_block_sends_per_client");
	u16 max_simul_sends_usually = max_simul_sends_setting;
	/*
		Check the time from last addNode/removeNode.
		
		Decrease send rate if player is building stuff.
	*/
	m_time_from_building += dtime;
	if(m_time_from_building < g_settings->getFloat(
				"full_block_send_enable_min_time_from_building"))
		max_simul_sends_usually
			= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
	/*
		Number of blocks sending + number of blocks selected for sending
	*/
	u32 num_blocks_selected = m_blocks_sending.size();
	
	/*
		next time d will be continued from the d from which the nearest
		unsent block was found this time.

		This is because not necessarily any of the blocks found this
		time are actually sent.
	*/
	s32 new_nearest_unsent_d = -1;
	s16 d_max = g_settings->getS16("max_block_send_distance");
	s16 d_max_gen = g_settings->getS16("max_block_generate_distance");
	// Don't loop very much at a time
Perttu Ahola's avatar
Perttu Ahola committed
	s16 max_d_increment_at_time = 2;
	if(d_max > d_start + max_d_increment_at_time)
		d_max = d_start + max_d_increment_at_time;
	/*if(d_max_gen > d_start+2)
		d_max_gen = d_start+2;*/
	
	//infostream<<"Starting from "<<d_start<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	s32 nearest_emerged_d = -1;
	s32 nearest_emergefull_d = -1;
	s32 nearest_sent_d = -1;
	bool queue_is_full = false;
	
	s16 d;
	for(d = d_start; d <= d_max; d++)
Perttu Ahola's avatar
Perttu Ahola committed
		/*errorstream<<"checking d="<<d<<" for "
				<<server->getPlayerName(peer_id)<<std::endl;*/
		//infostream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
		/*
			If m_nearest_unsent_d was changed by the EmergeThread
			(it can change it to 0 through SetBlockNotSent),
			update our d to it.
			Else update m_nearest_unsent_d
		*/
		/*if(m_nearest_unsent_d != last_nearest_unsent_d)
			d = m_nearest_unsent_d;
			last_nearest_unsent_d = m_nearest_unsent_d;
Perttu Ahola's avatar
Perttu Ahola committed

		/*
			Get the border/face dot coordinates of a "d-radiused"
			box
		*/
		core::list<v3s16> list;
		getFacePositions(list, d);
		
		core::list<v3s16>::Iterator li;
		for(li=list.begin(); li!=list.end(); li++)
		{
			v3s16 p = *li + center;
			
			/*
				Send throttling
				- Don't allow too many simultaneous transfers
				- EXCEPT when the blocks are very close
Perttu Ahola's avatar
Perttu Ahola committed

				Also, don't send blocks that are already flying.
			*/
			// Start with the usual maximum
			u16 max_simul_dynamic = max_simul_sends_usually;
			// If block is very close, allow full maximum
			if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
				max_simul_dynamic = max_simul_sends_setting;
			// Don't select too many blocks for sending
			if(num_blocks_selected >= max_simul_dynamic)
			{
				queue_is_full = true;
				goto queue_full_break;
			}
			
			// Don't send blocks that are currently being transferred
			if(m_blocks_sending.find(p) != NULL)
				continue;
		
Perttu Ahola's avatar
Perttu Ahola committed
			/*
				Do not go over-limit
			*/
			if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
			|| p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
				continue;
		
			// If this is true, inexistent block will be made from scratch
Perttu Ahola's avatar
Perttu Ahola committed
			bool generate = d <= d_max_gen;
Perttu Ahola's avatar
Perttu Ahola committed
				/*// Limit the generating area vertically to 2/3
				if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
Perttu Ahola's avatar
Perttu Ahola committed
					generate = false;*/

Perttu Ahola's avatar
Perttu Ahola committed
				// Limit the send area vertically to 1/2
				if(abs(p.Y - center.Y) > d_max / 2)
Perttu Ahola's avatar
Perttu Ahola committed
					continue;
Perttu Ahola's avatar
Perttu Ahola committed
#if 0
			/*
				If block is far away, don't generate it unless it is
				near ground level.
			if(d >= 4)
			{
	#if 1
				// Block center y in nodes
				f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2);
				// Don't generate if it's very high or very low
				if(y < -64 || y > 64)
					generate = false;
	#endif
	#if 0
				v2s16 p2d_nodes_center(
					MAP_BLOCKSIZE*p.X,
					MAP_BLOCKSIZE*p.Z);
				
				// Get ground height in nodes
Perttu Ahola's avatar
Perttu Ahola committed
				s16 gh = server->m_env->getServerMap().findGroundLevel(
						p2d_nodes_center);
				// If differs a lot, don't generate
				if(fabs(gh - y) > MAP_BLOCKSIZE*2)
					generate = false;
					// Actually, don't even send it
					//continue;
	#endif
Perttu Ahola's avatar
Perttu Ahola committed
#if 1
				Don't generate or send if not in sight
				FIXME This only works if the client uses a small enough
				FOV setting. The default of 72 degrees is fine.
			float camera_fov = (72.0*M_PI/180) * 4./3.;
			if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false)
Perttu Ahola's avatar
Perttu Ahola committed
#endif
Perttu Ahola's avatar
Perttu Ahola committed
			/*
				Don't send already sent blocks
			*/
			{
				if(m_blocks_sent.find(p) != NULL)
Perttu Ahola's avatar
Perttu Ahola committed
					continue;
Perttu Ahola's avatar
Perttu Ahola committed
			/*
				Check if map has this block
			*/
Perttu Ahola's avatar
Perttu Ahola committed
			MapBlock *block = server->m_env->getMap().getBlockNoCreateNoEx(p);
Perttu Ahola's avatar
Perttu Ahola committed
			
			bool surely_not_found_on_disk = false;
Perttu Ahola's avatar
Perttu Ahola committed
			if(block != NULL)
			{
				// Reset usage timer, this block will be of use in the future.
				block->resetUsageTimer();

				// Block is dummy if data doesn't exist.
				// It means it has been not found from disk and not generated
Perttu Ahola's avatar
Perttu Ahola committed
				if(block->isDummy())
				{
					surely_not_found_on_disk = true;
				}
				
				// Block is valid if lighting is up-to-date and data exists
				if(block->isValid() == false)
				{
					block_is_invalid = true;
				}
				/*if(block->isFullyGenerated() == false)
				v2s16 p2d(p.X, p.Z);
Perttu Ahola's avatar
Perttu Ahola committed
				ServerMap *map = (ServerMap*)(&server->m_env->getMap());
				v2s16 chunkpos = map->sector_to_chunk(p2d);
				if(map->chunkNonVolatile(chunkpos) == false)
					block_is_invalid = true;
#endif
				if(block->isGenerated() == false)
					block_is_invalid = true;
#if 1
				/*
					If block is not close, don't send it unless it is near
					ground level.

					Block is near ground level if night-time mesh
					differs from day-time mesh.
Perttu Ahola's avatar
Perttu Ahola committed
				if(d >= 4)
Perttu Ahola's avatar
Perttu Ahola committed
			}

			/*
				If block has been marked to not exist on disk (dummy)
				and generating new ones is not wanted, skip block.
Perttu Ahola's avatar
Perttu Ahola committed
			*/
			if(generate == false && surely_not_found_on_disk == true)
			{
				// get next one.
				continue;
			}

			/*
				Add inexistent block to emerge queue.
			*/
			if(block == NULL || surely_not_found_on_disk || block_is_invalid)
Perttu Ahola's avatar
Perttu Ahola committed
			{
				//TODO: Get value from somewhere
				// Allow only one block in emerge queue
				//if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
				// Allow two blocks in queue per client
Perttu Ahola's avatar
Perttu Ahola committed
				//if(server->m_emerge_queue.peerItemCount(peer_id) < 2)
				u32 max_emerge = 5;
				// Make it more responsive when needing to generate stuff
				if(surely_not_found_on_disk)
				if(server->m_emerge_queue.peerItemCount(peer_id) < max_emerge)
					//infostream<<"Adding block to emerge queue"<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
					// Add it to the emerge queue and trigger the thread
					
					u8 flags = 0;
					if(generate == false)
						flags |= BLOCK_EMERGE_FLAG_FROMDISK;
Perttu Ahola's avatar
Perttu Ahola committed
					
					server->m_emerge_queue.addBlock(peer_id, p, flags);
					server->m_emergethread.trigger();
Perttu Ahola's avatar
Perttu Ahola committed

					if(nearest_emerged_d == -1)
						nearest_emerged_d = d;
				} else {
					if(nearest_emergefull_d == -1)
						nearest_emergefull_d = d;
Perttu Ahola's avatar
Perttu Ahola committed
				}
				
				// get next one.
				continue;
			}

Perttu Ahola's avatar
Perttu Ahola committed
			if(nearest_sent_d == -1)
				nearest_sent_d = d;

				Add block to send queue
Perttu Ahola's avatar
Perttu Ahola committed
			/*errorstream<<"sending from d="<<d<<" to "
					<<server->getPlayerName(peer_id)<<std::endl;*/

Perttu Ahola's avatar
Perttu Ahola committed
			PrioritySortedBlockTransfer q((float)d, p, peer_id);

			dest.push_back(q);

			num_blocks_selected += 1;
	//infostream<<"Stopped at "<<d<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	// If nothing was found for sending and nothing was queued for
	// emerging, continue next time browsing from here
	if(nearest_emerged_d != -1){
		new_nearest_unsent_d = nearest_emerged_d;
	} else if(nearest_emergefull_d != -1){
		new_nearest_unsent_d = nearest_emergefull_d;
	} else {
		if(d > g_settings->getS16("max_block_send_distance")){
			new_nearest_unsent_d = 0;
			m_nothing_to_send_pause_timer = 2.0;
			/*infostream<<"GetNextBlocks(): d wrapped around for "
					<<server->getPlayerName(peer_id)
					<<"; setting to 0 and pausing"<<std::endl;*/
		} else {
			if(nearest_sent_d != -1)
				new_nearest_unsent_d = nearest_sent_d;
			else
				new_nearest_unsent_d = d;
		}
	if(new_nearest_unsent_d != -1)
		m_nearest_unsent_d = new_nearest_unsent_d;

	/*timer_result = timer.stop(true);
	if(timer_result != 0)
darkrose's avatar
darkrose committed
		infostream<<"GetNextBlocks timeout: "<<timer_result<<" (!=0)"<<std::endl;*/
Perttu Ahola's avatar
Perttu Ahola committed

void RemoteClient::GotBlock(v3s16 p)
{
	if(m_blocks_sending.find(p) != NULL)
		m_blocks_sending.remove(p);
	else
		/*infostream<<"RemoteClient::GotBlock(): Didn't find in"
				" m_blocks_sending"<<std::endl;*/
		m_excess_gotblocks++;
	}
Perttu Ahola's avatar
Perttu Ahola committed
	m_blocks_sent.insert(p, true);
}

void RemoteClient::SentBlock(v3s16 p)
{
	if(m_blocks_sending.find(p) == NULL)
		m_blocks_sending.insert(p, 0.0);
	else
		infostream<<"RemoteClient::SentBlock(): Sent block"
Perttu Ahola's avatar
Perttu Ahola committed
				" already in m_blocks_sending"<<std::endl;
}

void RemoteClient::SetBlockNotSent(v3s16 p)
{
	m_nearest_unsent_d = 0;
	
	if(m_blocks_sending.find(p) != NULL)
		m_blocks_sending.remove(p);
	if(m_blocks_sent.find(p) != NULL)
		m_blocks_sent.remove(p);
}

void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
{
	m_nearest_unsent_d = 0;
	
	for(core::map<v3s16, MapBlock*>::Iterator
			i = blocks.getIterator();
			i.atEnd()==false; i++)
	{
		v3s16 p = i.getNode()->getKey();

		if(m_blocks_sending.find(p) != NULL)
			m_blocks_sending.remove(p);
		if(m_blocks_sent.find(p) != NULL)
			m_blocks_sent.remove(p);
	}
}

/*
	PlayerInfo
*/

PlayerInfo::PlayerInfo()
{
	name[0] = 0;
	avg_rtt = 0;
Perttu Ahola's avatar
Perttu Ahola committed
}

void PlayerInfo::PrintLine(std::ostream *s)
{
	(*s)<<id<<": ";
	(*s)<<"\""<<name<<"\" ("
Perttu Ahola's avatar
Perttu Ahola committed
			<<(position.X/10)<<","<<(position.Y/10)
			<<","<<(position.Z/10)<<") ";
Perttu Ahola's avatar
Perttu Ahola committed
	address.print(s);
	(*s)<<" avg_rtt="<<avg_rtt;
	(*s)<<std::endl;
}

/*
	Server
*/

Server::Server(
		const std::string &path_world,
		const std::string &path_config,
		const SubgameSpec &gamespec,
		bool simple_singleplayer_mode
Perttu Ahola's avatar
Perttu Ahola committed
	):
	m_path_world(path_world),
	m_path_config(path_config),
	m_gamespec(gamespec),
	m_simple_singleplayer_mode(simple_singleplayer_mode),
	m_async_fatal_error(""),
Perttu Ahola's avatar
Perttu Ahola committed
	m_env(NULL),
Perttu Ahola's avatar
Perttu Ahola committed
	m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
	m_banmanager(path_world+DIR_DELIM+"ipban.txt"),
	m_rollback(NULL),
	m_rollback_sink_enabled(true),
	m_enable_rollback_recording(false),
Perttu Ahola's avatar
Perttu Ahola committed
	m_lua(NULL),
	m_itemdef(createItemDefManager()),
	m_craftdef(createCraftDefManager()),
	m_event(new EventManager()),
Perttu Ahola's avatar
Perttu Ahola committed
	m_thread(this),
	m_emergethread(this),
	m_shutdown_requested(false),
	m_ignore_map_edit_events(false),
	m_ignore_map_edit_events_peer_id(0)
Perttu Ahola's avatar
Perttu Ahola committed
{
	m_liquid_transform_timer = 0.0;
	m_print_info_timer = 0.0;
	m_objectdata_timer = 0.0;
	m_emergethread_trigger_timer = 0.0;
	m_savemap_timer = 0.0;
	
Perttu Ahola's avatar
Perttu Ahola committed
	m_env_mutex.Init();
	m_con_mutex.Init();
	m_step_dtime_mutex.Init();
	m_step_dtime = 0.0;
Perttu Ahola's avatar
Perttu Ahola committed

	if(path_world == "")
		throw ServerError("Supplied empty world path");
	
	if(!gamespec.isValid())
		throw ServerError("Supplied invalid gamespec");
Perttu Ahola's avatar
Perttu Ahola committed
	
	infostream<<"Server created for gameid \""<<m_gamespec.id<<"\"";
	if(m_simple_singleplayer_mode)
		infostream<<" in simple singleplayer mode"<<std::endl;
	else
		infostream<<std::endl;
	infostream<<"- world:  "<<m_path_world<<std::endl;
	infostream<<"- config: "<<m_path_config<<std::endl;
	infostream<<"- game:   "<<m_gamespec.path<<std::endl;
	// Create rollback manager
	std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt";
	m_rollback = createRollbackManager(rollback_path, this);

	// Add world mod search path
	m_modspaths.push_front(m_path_world + DIR_DELIM + "worldmods");
	// Add addon mod search path
Perttu Ahola's avatar
Perttu Ahola committed
	for(std::set<std::string>::const_iterator i = m_gamespec.mods_paths.begin();
			i != m_gamespec.mods_paths.end(); i++)
		m_modspaths.push_front((*i));
	// Print out mod search paths
	for(core::list<std::string>::Iterator i = m_modspaths.begin();
			i != m_modspaths.end(); i++){
		std::string modspath = *i;
		infostream<<"- mods:   "<<modspath<<std::endl;
Perttu Ahola's avatar
Perttu Ahola committed
	// Path to builtin.lua
	std::string builtinpath = getBuiltinLuaPath() + DIR_DELIM + "builtin.lua";
Perttu Ahola's avatar
Perttu Ahola committed

	// Create world if it doesn't exist