/*
Minetest-c55
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "server.h"
#include "utility.h"
#include <iostream>
#include "clientserver.h"
#include "map.h"
#include "jmutexautolock.h"
#include "main.h"
#include "constants.h"
#include "voxel.h"
#include "materials.h"
#include "mineral.h"
#include "config.h"
#include "servercommand.h"
#include "filesys.h"
#include "content_mapnode.h"
#include "content_craft.h"
#include "content_nodemeta.h"
#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;
};
void * ServerThread::Thread()
{
ThreadStarted();
DSTACK(__FUNCTION_NAME);
BEGIN_DEBUG_EXCEPTION_HANDLER
while(getRun())
{
try{
//TimeTaker timer("AsyncRunStep() + Receive()");
{
//TimeTaker timer("AsyncRunStep()");
m_server->AsyncRunStep();
}
//dout_server<<"Running m_server->Receive()"<<std::endl;
m_server->Receive();
}
catch(con::NoIncomingDataException &e)
{
}
catch(con::PeerNotFoundException &e)
{
dout_server<<"Server: PeerNotFoundException"<<std::endl;
}
}
END_DEBUG_EXCEPTION_HANDLER
return NULL;
}
void * EmergeThread::Thread()
{
ThreadStarted();
DSTACK(__FUNCTION_NAME);
//bool debug=false;
BEGIN_DEBUG_EXCEPTION_HANDLER
/*
Get block info from queue, emerge them and send them
to clients.
After queue is empty, exit.
*/
while(getRun())
{
QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
if(qptr == NULL)
break;
SharedPtr<QueuedBlockEmerge> q(qptr);
v3s16 &p = q->pos;
v2s16 p2d(p.X,p.Z);
/*
Do not generate 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;
//derr_server<<"EmergeThread::Thread(): running"<<std::endl;
//TimeTaker timer("block emerge");
/*
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.
*/
bool optional = true;
{
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)
optional = false;
}
}
/*dstream<<"EmergeThread: p="
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
<<"optional="<<optional<<std::endl;*/
ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
//core::map<v3s16, MapBlock*> changed_blocks;
//core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
MapBlock *block = NULL;
bool got_block = true;
core::map<v3s16, MapBlock*> modified_blocks;
bool only_from_disk = false;
if(optional)
only_from_disk = true;
/*
Fetch block from map or generate a single block
*/
{
JMutexAutoLock envlock(m_server->m_env_mutex);
// Load sector if it isn't loaded
if(map.getSectorNoGenerateNoEx(p2d) == NULL)
//map.loadSectorFull(p2d);
map.loadSectorMeta(p2d);
block = map.getBlockNoCreateNoEx(p);
if(!block || block->isDummy() || !block->isGenerated())
{
// Get, load or create sector
/*ServerMapSector *sector =
(ServerMapSector*)map.createSector(p2d);*/
// Load/generate block
/*block = map.emergeBlock(p, sector, changed_blocks,
lighting_invalidated_blocks);*/
block = map.loadBlock(p);
if(block == NULL && only_from_disk == false)
block = map.generateBlock(p, modified_blocks);
//block = map.generateBlock(p, changed_blocks);
/*block = map.generateBlock(p, block, sector, changed_blocks,
lighting_invalidated_blocks);*/
if(block == NULL)
{
got_block = false;
}
else
{
/*
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);
// Activate objects and stuff
m_server->m_env.activateBlock(block, 3600);
}
}
else
{
/*if(block->getLightingExpired()){
lighting_invalidated_blocks[block->getPos()] = block;
}*/
}
// TODO: Some additional checking and lighting updating,
// see emergeBlock
}
{//envlock
JMutexAutoLock envlock(m_server->m_env_mutex);
if(got_block)
{
/*
Collect a list of blocks that have been modified in
addition to the fetched one.
*/
#if 0
if(lighting_invalidated_blocks.size() > 0)
{
/*dstream<<"lighting "<<lighting_invalidated_blocks.size()
<<" blocks"<<std::endl;*/
// 50-100ms for single block generation
//TimeTaker timer("** EmergeThread updateLighting");
// Update lighting without locking the environment mutex,
// add modified blocks to changed blocks
map.updateLighting(lighting_invalidated_blocks, modified_blocks);
}
// Add all from changed_blocks to modified_blocks
for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
modified_blocks.insert(block->getPos(), block);
}
#endif
}
// If we got no block, there should be no invalidated blocks
else
{
//assert(lighting_invalidated_blocks.size() == 0);
}
}//envlock
/*
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);
}
}
}
END_DEBUG_EXCEPTION_HANDLER
return NULL;
}
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;
if(m_nothing_to_send_pause_timer >= 0)
{
// Keep this reset
m_nearest_unsent_reset_timer = 0;
return;
}
// Won't send anything if already sending
if(m_blocks_sending.size() >= g_settings.getU16
("max_simultaneous_block_sends_per_client"))
{
//dstream<<"Not sending any blocks, Queue full."<<std::endl;
return;
}
//TimeTaker timer("RemoteClient::GetNextBlocks");
Player *player = server->m_env.getPlayer(peer_id);
assert(player != NULL);
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);
v3s16 center = getNodeBlockPos(center_nodepos);
// Camera position and direction
v3f camera_pos =
playerpos + v3f(0, BS+BS/2, 0);
v3f camera_dir = v3f(0,0,1);
camera_dir.rotateYZBy(player->getPitch());
camera_dir.rotateXZBy(player->getYaw());
/*dstream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
<<camera_dir.Z<<")"<<std::endl;*/
/*
Get the starting value of the block finder radius.
*/
if(m_last_center != center)
{
m_nearest_unsent_d = 0;
m_last_center = center;
}
/*dstream<<"m_nearest_unsent_reset_timer="
<<m_nearest_unsent_reset_timer<<std::endl;*/
// This has to be incremented only when the nothing to send pause
// is not active
m_nearest_unsent_reset_timer += dtime;
// Reset periodically to avoid possible bugs or other mishaps
if(m_nearest_unsent_reset_timer > 10.0)
{
m_nearest_unsent_reset_timer = 0;
m_nearest_unsent_d = 0;
/*dstream<<"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;
//dstream<<"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
if(d_max > d_start+1)
d_max = d_start+1;
/*if(d_max_gen > d_start+2)
d_max_gen = d_start+2;*/
//dstream<<"Starting from "<<d_start<<std::endl;
bool sending_something = false;
bool no_blocks_found_for_sending = true;
bool queue_is_full = false;
s16 d;
for(d = d_start; d <= d_max; d++)
{
//dstream<<"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;
}*/
/*
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
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;
/*
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
bool generate = d <= d_max_gen;
{
/*// Limit the generating area vertically to 2/3
if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
generate = false;*/
// Limit the send area vertically to 2/3
if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
continue;
}
#if 1
/*
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
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
}
#endif
//dstream<<"d="<<d<<std::endl;
/*
Don't generate or send if not in sight
*/
if(isBlockInSight(p, camera_pos, camera_dir, 10000*BS) == false)
{
continue;
}
/*
Don't send already sent blocks
*/
{
if(m_blocks_sent.find(p) != NULL)
{
continue;
}
}
/*
Check if map has this block
*/
MapBlock *block = server->m_env.getMap().getBlockNoCreateNoEx(p);
bool surely_not_found_on_disk = false;
bool block_is_invalid = false;
if(block != NULL)
{
// Block is dummy if data doesn't exist.
// It means it has been not found from disk and not generated
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)
{
block_is_invalid = true;
}*/
#if 0
v2s16 p2d(p.X, p.Z);
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.
*/
if(d > 3)
{
if(block->dayNightDiffed() == false)
continue;
}
#endif
}
/*
If block has been marked to not exist on disk (dummy)
and generating new ones is not wanted, skip block.
*/
if(generate == false && surely_not_found_on_disk == true)
{
// get next one.
continue;
}
/*
Record the lowest d from which a block has been
found being not sent and possibly to exist
*/
if(no_blocks_found_for_sending)
{
if(generate == true)
new_nearest_unsent_d = d;
}
no_blocks_found_for_sending = false;
/*
Add inexistent block to emerge queue.
*/
if(block == NULL || surely_not_found_on_disk || block_is_invalid)
{
//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
if(server->m_emerge_queue.peerItemCount(peer_id) < 2)
{
//dstream<<"Adding block to emerge queue"<<std::endl;
// Add it to the emerge queue and trigger the thread
u8 flags = 0;
if(generate == false)
flags |= BLOCK_EMERGE_FLAG_FROMDISK;
server->m_emerge_queue.addBlock(peer_id, p, flags);
server->m_emergethread.trigger();
}
// get next one.
continue;
}
/*
Add block to send queue
*/
PrioritySortedBlockTransfer q((float)d, p, peer_id);
dest.push_back(q);
num_blocks_selected += 1;
sending_something = true;
}
}
queue_full_break:
//dstream<<"Stopped at "<<d<<std::endl;
if(no_blocks_found_for_sending)
{
if(queue_is_full == false)
new_nearest_unsent_d = d;
}
if(new_nearest_unsent_d != -1)
m_nearest_unsent_d = new_nearest_unsent_d;
if(sending_something == false)
{
m_nothing_to_send_counter++;
if((s16)m_nothing_to_send_counter >=
g_settings.getS16("max_block_send_distance"))
{
// Pause time in seconds
m_nothing_to_send_pause_timer = 1.0;
/*dstream<<"nothing to send to "
<<server->getPlayerName(peer_id)
<<" (d="<<d<<")"<<std::endl;*/
}
}
else
{
m_nothing_to_send_counter = 0;
}
/*timer_result = timer.stop(true);
if(timer_result != 0)
dstream<<"GetNextBlocks duration: "<<timer_result<<" (!=0)"<<std::endl;*/
}
void RemoteClient::SendObjectData(
Server *server,
float dtime,
core::map<v3s16, bool> &stepped_blocks
)
{
DSTACK(__FUNCTION_NAME);
// Can't send anything without knowing version
if(serialization_version == SER_FMT_VER_INVALID)
{
dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
<<std::endl;
return;
}
/*
Send a TOCLIENT_OBJECTDATA packet.
Sent as unreliable.
u16 command
u16 number of player positions
for each player:
v3s32 position*100
v3s32 speed*100
s32 pitch*100
s32 yaw*100
u16 count of blocks
for each block:
block objects
*/
std::ostringstream os(std::ios_base::binary);
|