3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 (c) 2010 Perttu Ahola <celeron55@gmail.com>
27 #include "clientserver.h"
29 #include "jmutexautolock.h"
31 #include "constants.h"
33 #include "materials.h"
35 #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
37 void * ServerThread::Thread()
41 DSTACK(__FUNCTION_NAME);
46 m_server->AsyncRunStep();
48 //dout_server<<"Running m_server->Receive()"<<std::endl;
51 catch(con::NoIncomingDataException &e)
54 #if CATCH_UNHANDLED_EXCEPTIONS
56 This is what has to be done in threads to get suitable debug info
58 catch(std::exception &e)
60 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
61 <<e.what()<<std::endl;
71 void * EmergeThread::Thread()
75 DSTACK(__FUNCTION_NAME);
78 #if CATCH_UNHANDLED_EXCEPTIONS
84 Get block info from queue, emerge them and send them
87 After queue is empty, exit.
91 QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
95 SharedPtr<QueuedBlockEmerge> q(qptr);
99 //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
101 //TimeTaker timer("block emerge", g_device);
104 Try to emerge it from somewhere.
106 If it is only wanted as optional, only loading from disk
111 Check if any peer wants it as non-optional. In that case it
114 Also decrement the emerge queue count in clients.
117 bool optional = true;
120 core::map<u16, u8>::Iterator i;
121 for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
123 //u16 peer_id = i.getNode()->getKey();
126 u8 flags = i.getNode()->getValue();
127 if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
133 /*dstream<<"EmergeThread: p="
134 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
135 <<"optional="<<optional<<std::endl;*/
137 ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
139 core::map<v3s16, MapBlock*> changed_blocks;
140 core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
142 MapBlock *block = NULL;
143 bool got_block = true;
144 core::map<v3s16, MapBlock*> modified_blocks;
148 JMutexAutoLock envlock(m_server->m_env_mutex);
150 //TimeTaker timer("block emerge envlock", g_device);
153 bool only_from_disk = false;
156 only_from_disk = true;
158 block = map.emergeBlock(
162 lighting_invalidated_blocks);
164 // If it is a dummy, block was not found on disk
167 //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
171 catch(InvalidPositionException &e)
174 // This happens when position is over limit.
180 if(debug && changed_blocks.size() > 0)
182 dout_server<<DTIME<<"Got changed_blocks: ";
183 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
184 i.atEnd() == false; i++)
186 MapBlock *block = i.getNode()->getValue();
187 v3s16 p = block->getPos();
188 dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
190 dout_server<<std::endl;
194 Update water pressure
197 m_server->UpdateBlockWaterPressure(block, modified_blocks);
199 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
200 i.atEnd() == false; i++)
202 MapBlock *block = i.getNode()->getValue();
203 m_server->UpdateBlockWaterPressure(block, modified_blocks);
204 //v3s16 p = i.getNode()->getKey();
205 //m_server->UpdateBlockWaterPressure(p, modified_blocks);
209 Collect a list of blocks that have been modified in
210 addition to the fetched one.
213 // Add all the "changed blocks" to modified_blocks
214 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
215 i.atEnd() == false; i++)
217 MapBlock *block = i.getNode()->getValue();
218 modified_blocks.insert(block->getPos(), block);
221 /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
222 <<" blocks"<<std::endl;
223 TimeTaker timer("** updateLighting", g_device);*/
225 // Update lighting without locking the environment mutex,
226 // add modified blocks to changed blocks
227 map.updateLighting(lighting_invalidated_blocks, modified_blocks);
229 // If we got no block, there should be no invalidated blocks
232 assert(lighting_invalidated_blocks.size() == 0);
238 Set sent status of modified blocks on clients
241 // NOTE: Server's clients are also behind the connection mutex
242 JMutexAutoLock lock(m_server->m_con_mutex);
245 Add the originally fetched block to the modified list
249 modified_blocks.insert(p, block);
253 Set the modified blocks unsent for all the clients
256 for(core::map<u16, RemoteClient*>::Iterator
257 i = m_server->m_clients.getIterator();
258 i.atEnd() == false; i++)
260 RemoteClient *client = i.getNode()->getValue();
262 if(modified_blocks.size() > 0)
264 // Remove block from sent history
265 client->SetBlocksNotSent(modified_blocks);
270 #if CATCH_UNHANDLED_EXCEPTIONS
273 This is what has to be done in threads to get suitable debug info
275 catch(std::exception &e)
277 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
278 <<e.what()<<std::endl;
286 void RemoteClient::GetNextBlocks(Server *server, float dtime,
287 core::array<PrioritySortedBlockTransfer> &dest)
289 DSTACK(__FUNCTION_NAME);
293 JMutexAutoLock lock(m_blocks_sent_mutex);
294 m_nearest_unsent_reset_timer += dtime;
297 // Won't send anything if already sending
299 JMutexAutoLock lock(m_blocks_sending_mutex);
301 if(m_blocks_sending.size() >= g_settings.getU16
302 ("max_simultaneous_block_sends_per_client"))
304 //dstream<<"Not sending any blocks, Queue full."<<std::endl;
309 Player *player = server->m_env.getPlayer(peer_id);
311 v3f playerpos = player->getPosition();
312 v3f playerspeed = player->getSpeed();
314 v3s16 center_nodepos = floatToInt(playerpos);
316 v3s16 center = getNodeBlockPos(center_nodepos);
319 Get the starting value of the block finder radius.
321 s16 last_nearest_unsent_d;
324 JMutexAutoLock lock(m_blocks_sent_mutex);
326 if(m_last_center != center)
328 m_nearest_unsent_d = 0;
329 m_last_center = center;
332 /*dstream<<"m_nearest_unsent_reset_timer="
333 <<m_nearest_unsent_reset_timer<<std::endl;*/
334 if(m_nearest_unsent_reset_timer > 5.0)
336 m_nearest_unsent_reset_timer = 0;
337 m_nearest_unsent_d = 0;
338 //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
341 last_nearest_unsent_d = m_nearest_unsent_d;
343 d_start = m_nearest_unsent_d;
346 u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
347 ("max_simultaneous_block_sends_per_client");
348 u16 maximum_simultaneous_block_sends =
349 maximum_simultaneous_block_sends_setting;
352 Check the time from last addNode/removeNode.
354 Decrease send rate if player is building stuff.
357 SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
358 m_time_from_building.m_value += dtime;
359 /*if(m_time_from_building.m_value
360 < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
361 if(m_time_from_building.m_value < g_settings.getFloat(
362 "full_block_send_enable_min_time_from_building"))
364 maximum_simultaneous_block_sends
365 = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
369 u32 num_blocks_selected;
371 JMutexAutoLock lock(m_blocks_sending_mutex);
372 num_blocks_selected = m_blocks_sending.size();
376 next time d will be continued from the d from which the nearest
377 unsent block was found this time.
379 This is because not necessarily any of the blocks found this
380 time are actually sent.
382 s32 new_nearest_unsent_d = -1;
384 // Serialization version used
385 //u8 ser_version = serialization_version;
387 //bool has_incomplete_blocks = false;
389 s16 d_max = g_settings.getS16("max_block_send_distance");
390 s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
392 //dstream<<"Starting from "<<d_start<<std::endl;
394 for(s16 d = d_start; d <= d_max; d++)
396 //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
398 //if(has_incomplete_blocks == false)
400 JMutexAutoLock lock(m_blocks_sent_mutex);
402 If m_nearest_unsent_d was changed by the EmergeThread
403 (it can change it to 0 through SetBlockNotSent),
405 Else update m_nearest_unsent_d
407 if(m_nearest_unsent_d != last_nearest_unsent_d)
409 d = m_nearest_unsent_d;
410 last_nearest_unsent_d = m_nearest_unsent_d;
415 Get the border/face dot coordinates of a "d-radiused"
418 core::list<v3s16> list;
419 getFacePositions(list, d);
421 core::list<v3s16>::Iterator li;
422 for(li=list.begin(); li!=list.end(); li++)
424 v3s16 p = *li + center;
428 - Don't allow too many simultaneous transfers
429 - EXCEPT when the blocks are very close
431 Also, don't send blocks that are already flying.
434 u16 maximum_simultaneous_block_sends_now =
435 maximum_simultaneous_block_sends;
437 if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
439 maximum_simultaneous_block_sends_now =
440 maximum_simultaneous_block_sends_setting;
444 JMutexAutoLock lock(m_blocks_sending_mutex);
446 // Limit is dynamically lowered when building
447 if(num_blocks_selected
448 >= maximum_simultaneous_block_sends_now)
450 /*dstream<<"Not sending more blocks. Queue full. "
451 <<m_blocks_sending.size()
456 if(m_blocks_sending.find(p) != NULL)
463 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
464 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
465 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
466 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
467 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
468 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
471 bool generate = d <= d_max_gen;
473 // Limit the generating area vertically to half
474 if(abs(p.Y - center.Y) > d_max_gen / 2)
478 Don't send already sent blocks
481 JMutexAutoLock lock(m_blocks_sent_mutex);
483 if(m_blocks_sent.find(p) != NULL)
488 Check if map has this block
490 MapBlock *block = NULL;
493 block = server->m_env.getMap().getBlockNoCreate(p);
495 catch(InvalidPositionException &e)
499 bool surely_not_found_on_disk = false;
502 /*if(block->isIncomplete())
504 has_incomplete_blocks = true;
510 surely_not_found_on_disk = true;
515 If block has been marked to not exist on disk (dummy)
516 and generating new ones is not wanted, skip block.
518 if(generate == false && surely_not_found_on_disk == true)
525 Record the lowest d from which a a block has been
526 found being not sent and possibly to exist
528 if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
530 new_nearest_unsent_d = d;
534 Add inexistent block to emerge queue.
536 if(block == NULL || surely_not_found_on_disk)
538 /*SharedPtr<JMutexAutoLock> lock
539 (m_num_blocks_in_emerge_queue.getLock());*/
541 //TODO: Get value from somewhere
542 // Allow only one block in emerge queue
543 if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
545 // Add it to the emerge queue and trigger the thread
548 if(generate == false)
549 flags |= BLOCK_EMERGE_FLAG_FROMDISK;
551 server->m_emerge_queue.addBlock(peer_id, p, flags);
552 server->m_emergethread.trigger();
563 PrioritySortedBlockTransfer q((float)d, p, peer_id);
567 num_blocks_selected += 1;
572 if(new_nearest_unsent_d != -1)
574 JMutexAutoLock lock(m_blocks_sent_mutex);
575 m_nearest_unsent_d = new_nearest_unsent_d;
579 void RemoteClient::SendObjectData(
582 core::map<v3s16, bool> &stepped_blocks
585 DSTACK(__FUNCTION_NAME);
587 // Can't send anything without knowing version
588 if(serialization_version == SER_FMT_VER_INVALID)
590 dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
596 Send a TOCLIENT_OBJECTDATA packet.
600 u16 number of player positions
611 std::ostringstream os(std::ios_base::binary);
615 writeU16(buf, TOCLIENT_OBJECTDATA);
616 os.write((char*)buf, 2);
619 Get and write player data
622 core::list<Player*> players = server->m_env.getPlayers();
624 // Write player count
625 u16 playercount = players.size();
626 writeU16(buf, playercount);
627 os.write((char*)buf, 2);
629 core::list<Player*>::Iterator i;
630 for(i = players.begin();
631 i != players.end(); i++)
635 v3f pf = player->getPosition();
636 v3f sf = player->getSpeed();
638 v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
639 v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
640 s32 pitch_i (player->getPitch() * 100);
641 s32 yaw_i (player->getYaw() * 100);
643 writeU16(buf, player->peer_id);
644 os.write((char*)buf, 2);
645 writeV3S32(buf, position_i);
646 os.write((char*)buf, 12);
647 writeV3S32(buf, speed_i);
648 os.write((char*)buf, 12);
649 writeS32(buf, pitch_i);
650 os.write((char*)buf, 4);
651 writeS32(buf, yaw_i);
652 os.write((char*)buf, 4);
656 Get and write object data
662 For making players to be able to build to their nearby
663 environment (building is not possible on blocks that are not
666 - Add blocks to emerge queue if they are not found
668 SUGGESTION: These could be ignored from the backside of the player
671 Player *player = server->m_env.getPlayer(peer_id);
673 v3f playerpos = player->getPosition();
674 v3f playerspeed = player->getSpeed();
676 v3s16 center_nodepos = floatToInt(playerpos);
677 v3s16 center = getNodeBlockPos(center_nodepos);
679 s16 d_max = g_settings.getS16("active_object_range");
681 // Number of blocks whose objects were written to bos
684 std::ostringstream bos(std::ios_base::binary);
686 for(s16 d = 0; d <= d_max; d++)
688 core::list<v3s16> list;
689 getFacePositions(list, d);
691 core::list<v3s16>::Iterator li;
692 for(li=list.begin(); li!=list.end(); li++)
694 v3s16 p = *li + center;
697 Ignore blocks that haven't been sent to the client
700 JMutexAutoLock sentlock(m_blocks_sent_mutex);
701 if(m_blocks_sent.find(p) == NULL)
705 // Try stepping block and add it to a send queue
710 MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
713 Step block if not in stepped_blocks and add to stepped_blocks.
715 if(stepped_blocks.find(p) == NULL)
717 block->stepObjects(dtime, true, server->getDayNightRatio());
718 stepped_blocks.insert(p, true);
719 block->setChangedFlag();
722 // Skip block if there are no objects
723 if(block->getObjectCount() == 0)
732 bos.write((char*)buf, 6);
735 block->serializeObjects(bos, serialization_version);
740 Stop collecting objects if data is already too big
742 // Sum of player and object data sizes
743 s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
744 // break out if data too big
745 if(sum > MAX_OBJECTDATA_SIZE)
747 goto skip_subsequent;
751 catch(InvalidPositionException &e)
754 // Add it to the emerge queue and trigger the thread.
755 // Fetch the block only if it is on disk.
757 // Grab and increment counter
758 /*SharedPtr<JMutexAutoLock> lock
759 (m_num_blocks_in_emerge_queue.getLock());
760 m_num_blocks_in_emerge_queue.m_value++;*/
762 // Add to queue as an anonymous fetch from disk
763 u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
764 server->m_emerge_queue.addBlock(0, p, flags);
765 server->m_emergethread.trigger();
773 writeU16(buf, blockcount);
774 os.write((char*)buf, 2);
776 // Write block objects
783 //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
786 std::string s = os.str();
787 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
788 // Send as unreliable
789 server->m_con.Send(peer_id, 0, data, false);
792 void RemoteClient::GotBlock(v3s16 p)
794 JMutexAutoLock lock(m_blocks_sending_mutex);
795 JMutexAutoLock lock2(m_blocks_sent_mutex);
796 if(m_blocks_sending.find(p) != NULL)
797 m_blocks_sending.remove(p);
799 dstream<<"RemoteClient::GotBlock(): Didn't find in"
800 " m_blocks_sending"<<std::endl;
801 m_blocks_sent.insert(p, true);
804 void RemoteClient::SentBlock(v3s16 p)
806 JMutexAutoLock lock(m_blocks_sending_mutex);
807 if(m_blocks_sending.size() > 15)
809 dstream<<"RemoteClient::SentBlock(): "
810 <<"m_blocks_sending.size()="
811 <<m_blocks_sending.size()<<std::endl;
813 if(m_blocks_sending.find(p) == NULL)
814 m_blocks_sending.insert(p, 0.0);
816 dstream<<"RemoteClient::SentBlock(): Sent block"
817 " already in m_blocks_sending"<<std::endl;
820 void RemoteClient::SetBlockNotSent(v3s16 p)
822 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
823 JMutexAutoLock sentlock(m_blocks_sent_mutex);
825 m_nearest_unsent_d = 0;
827 if(m_blocks_sending.find(p) != NULL)
828 m_blocks_sending.remove(p);
829 if(m_blocks_sent.find(p) != NULL)
830 m_blocks_sent.remove(p);
833 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
835 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
836 JMutexAutoLock sentlock(m_blocks_sent_mutex);
838 m_nearest_unsent_d = 0;
840 for(core::map<v3s16, MapBlock*>::Iterator
841 i = blocks.getIterator();
842 i.atEnd()==false; i++)
844 v3s16 p = i.getNode()->getKey();
846 if(m_blocks_sending.find(p) != NULL)
847 m_blocks_sending.remove(p);
848 if(m_blocks_sent.find(p) != NULL)
849 m_blocks_sent.remove(p);
857 PlayerInfo::PlayerInfo()
862 void PlayerInfo::PrintLine(std::ostream *s)
864 (*s)<<id<<": \""<<name<<"\" ("
865 <<position.X<<","<<position.Y
866 <<","<<position.Z<<") ";
868 (*s)<<" avg_rtt="<<avg_rtt;
872 u32 PIChecksum(core::list<PlayerInfo> &l)
874 core::list<PlayerInfo>::Iterator i;
877 for(i=l.begin(); i!=l.end(); i++)
879 checksum += a * (i->id+1);
880 checksum ^= 0x435aafcd;
891 std::string mapsavedir,
895 m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
896 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
898 m_emergethread(this),
901 m_time_of_day_send_timer(0),
904 m_flowwater_timer = 0.0;
905 m_print_info_timer = 0.0;
906 m_objectdata_timer = 0.0;
907 m_emergethread_trigger_timer = 0.0;
908 m_savemap_timer = 0.0;
912 m_step_dtime_mutex.Init();
921 JMutexAutoLock clientslock(m_con_mutex);
923 for(core::map<u16, RemoteClient*>::Iterator
924 i = m_clients.getIterator();
925 i.atEnd() == false; i++)
927 u16 peer_id = i.getNode()->getKey();
931 JMutexAutoLock envlock(m_env_mutex);
932 m_env.removePlayer(peer_id);
936 delete i.getNode()->getValue();
940 void Server::start(unsigned short port)
942 DSTACK(__FUNCTION_NAME);
943 // Stop thread if already running
946 // Initialize connection
947 m_con.setTimeoutMs(30);
951 m_thread.setRun(true);
954 dout_server<<"Server started on port "<<port<<std::endl;
959 DSTACK(__FUNCTION_NAME);
960 // Stop threads (set run=false first so both start stopping)
961 m_thread.setRun(false);
962 m_emergethread.setRun(false);
964 m_emergethread.stop();
966 dout_server<<"Server threads stopped"<<std::endl;
969 void Server::step(float dtime)
971 DSTACK(__FUNCTION_NAME);
976 JMutexAutoLock lock(m_step_dtime_mutex);
977 m_step_dtime += dtime;
981 void Server::AsyncRunStep()
983 DSTACK(__FUNCTION_NAME);
987 JMutexAutoLock lock1(m_step_dtime_mutex);
988 dtime = m_step_dtime;
991 // Send blocks to clients
997 //dstream<<"Server steps "<<dtime<<std::endl;
998 //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
1001 JMutexAutoLock lock1(m_step_dtime_mutex);
1002 m_step_dtime -= dtime;
1009 m_uptime.set(m_uptime.get() + dtime);
1013 Update m_time_of_day
1016 m_time_counter += dtime;
1017 f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
1018 u32 units = (u32)(m_time_counter*speed);
1019 m_time_counter -= (f32)units / speed;
1020 m_time_of_day.set((m_time_of_day.get() + units) % 24000);
1022 //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
1025 Send to clients at constant intervals
1028 m_time_of_day_send_timer -= dtime;
1029 if(m_time_of_day_send_timer < 0.0)
1031 m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");
1033 //JMutexAutoLock envlock(m_env_mutex);
1034 JMutexAutoLock conlock(m_con_mutex);
1036 for(core::map<u16, RemoteClient*>::Iterator
1037 i = m_clients.getIterator();
1038 i.atEnd() == false; i++)
1040 RemoteClient *client = i.getNode()->getValue();
1041 //Player *player = m_env.getPlayer(client->peer_id);
1043 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1044 m_time_of_day.get());
1046 m_con.Send(client->peer_id, 0, data, true);
1052 // Process connection's timeouts
1053 JMutexAutoLock lock2(m_con_mutex);
1054 m_con.RunTimeouts(dtime);
1058 // This has to be called so that the client list gets synced
1059 // with the peer list of the connection
1060 handlePeerChanges();
1065 // This also runs Map's timers
1066 JMutexAutoLock lock(m_env_mutex);
1080 if(g_settings.getBool("endless_water") == false)
1085 float &counter = m_flowwater_timer;
1087 if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
1092 core::map<v3s16, MapBlock*> modified_blocks;
1096 JMutexAutoLock envlock(m_env_mutex);
1098 MapVoxelManipulator v(&m_env.getMap());
1099 v.m_disable_water_climb =
1100 g_settings.getBool("disable_water_climb");
1102 if(g_settings.getBool("endless_water") == false)
1103 v.flowWater(m_flow_active_nodes, 0, false, 250);
1105 v.flowWater(m_flow_active_nodes, 0, false, 50);
1107 v.blitBack(modified_blocks);
1109 ServerMap &map = ((ServerMap&)m_env.getMap());
1112 core::map<v3s16, MapBlock*> lighting_modified_blocks;
1113 map.updateLighting(modified_blocks, lighting_modified_blocks);
1115 // Add blocks modified by lighting to modified_blocks
1116 for(core::map<v3s16, MapBlock*>::Iterator
1117 i = lighting_modified_blocks.getIterator();
1118 i.atEnd() == false; i++)
1120 MapBlock *block = i.getNode()->getValue();
1121 modified_blocks.insert(block->getPos(), block);
1126 Set the modified blocks unsent for all the clients
1129 JMutexAutoLock lock2(m_con_mutex);
1131 for(core::map<u16, RemoteClient*>::Iterator
1132 i = m_clients.getIterator();
1133 i.atEnd() == false; i++)
1135 RemoteClient *client = i.getNode()->getValue();
1137 if(modified_blocks.size() > 0)
1139 // Remove block from sent history
1140 client->SetBlocksNotSent(modified_blocks);
1144 } // interval counter
1147 // Periodically print some info
1149 float &counter = m_print_info_timer;
1155 JMutexAutoLock lock2(m_con_mutex);
1157 for(core::map<u16, RemoteClient*>::Iterator
1158 i = m_clients.getIterator();
1159 i.atEnd() == false; i++)
1161 //u16 peer_id = i.getNode()->getKey();
1162 RemoteClient *client = i.getNode()->getValue();
1163 client->PrintInfo(std::cout);
1171 NOTE: Some of this could be moved to RemoteClient
1175 JMutexAutoLock envlock(m_env_mutex);
1176 JMutexAutoLock conlock(m_con_mutex);
1178 for(core::map<u16, RemoteClient*>::Iterator
1179 i = m_clients.getIterator();
1180 i.atEnd() == false; i++)
1182 RemoteClient *client = i.getNode()->getValue();
1183 Player *player = m_env.getPlayer(client->peer_id);
1185 JMutexAutoLock digmutex(client->m_dig_mutex);
1187 if(client->m_dig_tool_item == -1)
1190 client->m_dig_time_remaining -= dtime;
1192 if(client->m_dig_time_remaining > 0)
1194 client->m_time_from_building.set(0.0);
1198 v3s16 p_under = client->m_dig_position;
1200 // Mandatory parameter; actually used for nothing
1201 core::map<v3s16, MapBlock*> modified_blocks;
1207 // Get material at position
1208 material = m_env.getMap().getNode(p_under).d;
1209 // If it's not diggable, do nothing
1210 if(content_diggable(material) == false)
1212 derr_server<<"Server: Not finishing digging: Node not diggable"
1214 client->m_dig_tool_item = -1;
1218 catch(InvalidPositionException &e)
1220 derr_server<<"Server: Not finishing digging: Node not found"
1222 client->m_dig_tool_item = -1;
1228 SharedBuffer<u8> reply(replysize);
1229 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1230 writeS16(&reply[2], p_under.X);
1231 writeS16(&reply[4], p_under.Y);
1232 writeS16(&reply[6], p_under.Z);
1234 m_con.SendToAll(0, reply, true);
1236 if(g_settings.getBool("creative_mode") == false)
1238 // Add to inventory and send inventory
1239 InventoryItem *item = new MaterialItem(material, 1);
1240 player->inventory.addItem("main", item);
1241 SendInventory(player->peer_id);
1246 (this takes some time so it is done after the quick stuff)
1248 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1254 // Update water pressure around modification
1255 // This also adds it to m_flow_active_nodes if appropriate
1257 MapVoxelManipulator v(&m_env.getMap());
1258 v.m_disable_water_climb =
1259 g_settings.getBool("disable_water_climb");
1261 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1265 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1267 catch(ProcessingLimitException &e)
1269 dstream<<"Processing limit reached (1)"<<std::endl;
1272 v.blitBack(modified_blocks);
1277 // Send object positions
1279 float &counter = m_objectdata_timer;
1281 if(counter >= g_settings.getFloat("objectdata_interval"))
1283 JMutexAutoLock lock1(m_env_mutex);
1284 JMutexAutoLock lock2(m_con_mutex);
1285 SendObjectData(counter);
1291 // Trigger emergethread (it gets somehow gets to a
1292 // non-triggered but bysy state sometimes)
1294 float &counter = m_emergethread_trigger_timer;
1300 m_emergethread.trigger();
1306 float &counter = m_savemap_timer;
1308 if(counter >= g_settings.getFloat("server_map_save_interval"))
1312 JMutexAutoLock lock(m_env_mutex);
1314 // Save only changed parts
1315 m_env.getMap().save(true);
1317 // Delete unused sectors
1318 u32 deleted_count = m_env.getMap().deleteUnusedSectors(
1319 g_settings.getFloat("server_unload_unused_sectors_timeout"));
1320 if(deleted_count > 0)
1322 dout_server<<"Server: Unloaded "<<deleted_count
1323 <<" sectors from memory"<<std::endl;
1329 void Server::Receive()
1331 DSTACK(__FUNCTION_NAME);
1332 u32 data_maxsize = 10000;
1333 Buffer<u8> data(data_maxsize);
1338 JMutexAutoLock conlock(m_con_mutex);
1339 datasize = m_con.Receive(peer_id, *data, data_maxsize);
1342 // This has to be called so that the client list gets synced
1343 // with the peer list of the connection
1344 handlePeerChanges();
1346 ProcessData(*data, datasize, peer_id);
1348 catch(con::InvalidIncomingDataException &e)
1350 derr_server<<"Server::Receive(): "
1351 "InvalidIncomingDataException: what()="
1352 <<e.what()<<std::endl;
1354 catch(con::PeerNotFoundException &e)
1356 //NOTE: This is not needed anymore
1358 // The peer has been disconnected.
1359 // Find the associated player and remove it.
1361 /*JMutexAutoLock envlock(m_env_mutex);
1363 dout_server<<"ServerThread: peer_id="<<peer_id
1364 <<" has apparently closed connection. "
1365 <<"Removing player."<<std::endl;
1367 m_env.removePlayer(peer_id);*/
1371 void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
1373 DSTACK(__FUNCTION_NAME);
1374 // Environment is locked first.
1375 JMutexAutoLock envlock(m_env_mutex);
1376 JMutexAutoLock conlock(m_con_mutex);
1380 peer = m_con.GetPeer(peer_id);
1382 catch(con::PeerNotFoundException &e)
1384 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
1385 <<peer_id<<" not found"<<std::endl;
1389 //u8 peer_ser_ver = peer->serialization_version;
1390 u8 peer_ser_ver = getClient(peer->id)->serialization_version;
1398 ToServerCommand command = (ToServerCommand)readU16(&data[0]);
1400 if(command == TOSERVER_INIT)
1402 // [0] u16 TOSERVER_INIT
1403 // [2] u8 SER_FMT_VER_HIGHEST
1404 // [3] u8[20] player_name
1409 derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
1410 <<peer->id<<std::endl;
1412 // First byte after command is maximum supported
1413 // serialization version
1414 u8 client_max = data[2];
1415 u8 our_max = SER_FMT_VER_HIGHEST;
1416 // Use the highest version supported by both
1417 u8 deployed = core::min_(client_max, our_max);
1418 // If it's lower than the lowest supported, give up.
1419 if(deployed < SER_FMT_VER_LOWEST)
1420 deployed = SER_FMT_VER_INVALID;
1422 //peer->serialization_version = deployed;
1423 getClient(peer->id)->pending_serialization_version = deployed;
1425 if(deployed == SER_FMT_VER_INVALID)
1427 derr_server<<DTIME<<"Server: Cannot negotiate "
1428 "serialization version with peer "
1429 <<peer_id<<std::endl;
1437 Player *player = m_env.getPlayer(peer_id);
1439 // Check if player doesn't exist
1441 throw con::InvalidIncomingDataException
1442 ("Server::ProcessData(): INIT: Player doesn't exist");
1444 // update name if it was supplied
1445 if(datasize >= 20+3)
1448 player->updateName((const char*)&data[3]);
1451 // Now answer with a TOCLIENT_INIT
1453 SharedBuffer<u8> reply(2+1+6);
1454 writeU16(&reply[0], TOCLIENT_INIT);
1455 writeU8(&reply[2], deployed);
1456 writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
1458 m_con.Send(peer_id, 0, reply, true);
1462 if(command == TOSERVER_INIT2)
1464 derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
1465 <<peer->id<<std::endl;
1468 getClient(peer->id)->serialization_version
1469 = getClient(peer->id)->pending_serialization_version;
1472 Send some initialization data
1475 // Send player info to all players
1478 // Send inventory to player
1479 SendInventory(peer->id);
1483 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1484 m_time_of_day.get());
1485 m_con.Send(peer->id, 0, data, true);
1488 // Send information about server to player in chat
1490 std::wostringstream os(std::ios_base::binary);
1493 os<<L"uptime="<<m_uptime.get();
1494 // Information about clients
1496 for(core::map<u16, RemoteClient*>::Iterator
1497 i = m_clients.getIterator();
1498 i.atEnd() == false; i++)
1500 // Get client and check that it is valid
1501 RemoteClient *client = i.getNode()->getValue();
1502 assert(client->peer_id == i.getNode()->getKey());
1503 if(client->serialization_version == SER_FMT_VER_INVALID)
1505 // Get name of player
1506 std::wstring name = L"unknown";
1507 Player *player = m_env.getPlayer(client->peer_id);
1509 name = narrow_to_wide(player->getName());
1510 // Add name to information string
1515 SendChatMessage(peer_id, os.str());
1518 // Send information about joining in chat
1520 std::wstring name = L"unknown";
1521 Player *player = m_env.getPlayer(peer_id);
1523 name = narrow_to_wide(player->getName());
1525 std::wstring message;
1528 message += L" joined game";
1529 BroadcastChatMessage(message);
1535 if(peer_ser_ver == SER_FMT_VER_INVALID)
1537 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
1538 " serialization format invalid or not initialized."
1539 " Skipping incoming command="<<command<<std::endl;
1543 Player *player = m_env.getPlayer(peer_id);
1546 derr_server<<"Server::ProcessData(): Cancelling: "
1547 "No player for peer_id="<<peer_id
1551 if(command == TOSERVER_PLAYERPOS)
1553 if(datasize < 2+12+12+4+4)
1557 v3s32 ps = readV3S32(&data[start+2]);
1558 v3s32 ss = readV3S32(&data[start+2+12]);
1559 f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
1560 f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
1561 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
1562 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
1563 pitch = wrapDegrees(pitch);
1564 yaw = wrapDegrees(yaw);
1565 player->setPosition(position);
1566 player->setSpeed(speed);
1567 player->setPitch(pitch);
1568 player->setYaw(yaw);
1570 /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
1571 <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
1572 <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
1574 else if(command == TOSERVER_GOTBLOCKS)
1587 u16 count = data[2];
1588 for(u16 i=0; i<count; i++)
1590 if((s16)datasize < 2+1+(i+1)*6)
1591 throw con::InvalidIncomingDataException
1592 ("GOTBLOCKS length is too short");
1593 v3s16 p = readV3S16(&data[2+1+i*6]);
1594 /*dstream<<"Server: GOTBLOCKS ("
1595 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1596 RemoteClient *client = getClient(peer_id);
1597 client->GotBlock(p);
1600 else if(command == TOSERVER_DELETEDBLOCKS)
1613 u16 count = data[2];
1614 for(u16 i=0; i<count; i++)
1616 if((s16)datasize < 2+1+(i+1)*6)
1617 throw con::InvalidIncomingDataException
1618 ("DELETEDBLOCKS length is too short");
1619 v3s16 p = readV3S16(&data[2+1+i*6]);
1620 /*dstream<<"Server: DELETEDBLOCKS ("
1621 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1622 RemoteClient *client = getClient(peer_id);
1623 client->SetBlockNotSent(p);
1626 else if(command == TOSERVER_CLICK_OBJECT)
1633 [2] u8 button (0=left, 1=right)
1638 u8 button = readU8(&data[2]);
1640 p.X = readS16(&data[3]);
1641 p.Y = readS16(&data[5]);
1642 p.Z = readS16(&data[7]);
1643 s16 id = readS16(&data[9]);
1644 //u16 item_i = readU16(&data[11]);
1646 MapBlock *block = NULL;
1649 block = m_env.getMap().getBlockNoCreate(p);
1651 catch(InvalidPositionException &e)
1653 derr_server<<"CLICK_OBJECT block not found"<<std::endl;
1657 MapBlockObject *obj = block->getObject(id);
1661 derr_server<<"CLICK_OBJECT object not found"<<std::endl;
1665 //TODO: Check that object is reasonably close
1670 InventoryList *ilist = player->inventory.getList("main");
1671 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
1674 // Skip if inventory has no free space
1675 if(ilist->getUsedSlots() == ilist->getSize())
1677 dout_server<<"Player inventory has no free space"<<std::endl;
1682 Create the inventory item
1684 InventoryItem *item = NULL;
1685 // If it is an item-object, take the item from it
1686 if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
1688 item = ((ItemObject*)obj)->createInventoryItem();
1690 // Else create an item of the object
1693 item = new MapBlockObjectItem
1694 (obj->getInventoryString());
1697 // Add to inventory and send inventory
1698 ilist->addItem(item);
1699 SendInventory(player->peer_id);
1702 // Remove from block
1703 block->removeObject(id);
1706 else if(command == TOSERVER_GROUND_ACTION)
1714 [3] v3s16 nodepos_undersurface
1715 [9] v3s16 nodepos_abovesurface
1720 2: stop digging (all parameters ignored)
1722 u8 action = readU8(&data[2]);
1724 p_under.X = readS16(&data[3]);
1725 p_under.Y = readS16(&data[5]);
1726 p_under.Z = readS16(&data[7]);
1728 p_over.X = readS16(&data[9]);
1729 p_over.Y = readS16(&data[11]);
1730 p_over.Z = readS16(&data[13]);
1731 u16 item_i = readU16(&data[15]);
1733 //TODO: Check that target is reasonably close
1741 NOTE: This can be used in the future to check if
1742 somebody is cheating, by checking the timing.
1749 else if(action == 2)
1752 RemoteClient *client = getClient(peer->id);
1753 JMutexAutoLock digmutex(client->m_dig_mutex);
1754 client->m_dig_tool_item = -1;
1759 3: Digging completed
1761 else if(action == 3)
1763 // Mandatory parameter; actually used for nothing
1764 core::map<v3s16, MapBlock*> modified_blocks;
1770 // Get material at position
1771 material = m_env.getMap().getNode(p_under).d;
1772 // If it's not diggable, do nothing
1773 if(content_diggable(material) == false)
1775 derr_server<<"Server: Not finishing digging: Node not diggable"
1780 catch(InvalidPositionException &e)
1782 derr_server<<"Server: Not finishing digging: Node not found"
1787 //TODO: Send to only other clients
1790 Send the removal to all other clients
1795 SharedBuffer<u8> reply(replysize);
1796 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1797 writeS16(&reply[2], p_under.X);
1798 writeS16(&reply[4], p_under.Y);
1799 writeS16(&reply[6], p_under.Z);
1801 for(core::map<u16, RemoteClient*>::Iterator
1802 i = m_clients.getIterator();
1803 i.atEnd() == false; i++)
1805 // Get client and check that it is valid
1806 RemoteClient *client = i.getNode()->getValue();
1807 assert(client->peer_id == i.getNode()->getKey());
1808 if(client->serialization_version == SER_FMT_VER_INVALID)
1811 // Don't send if it's the same one
1812 if(peer_id == client->peer_id)
1816 m_con.Send(client->peer_id, 0, reply, true);
1820 Update and send inventory
1823 if(g_settings.getBool("creative_mode") == false)
1828 InventoryList *mlist = player->inventory.getList("main");
1831 InventoryItem *item = mlist->getItem(item_i);
1832 if(item && (std::string)item->getName() == "ToolItem")
1834 ToolItem *titem = (ToolItem*)item;
1835 std::string toolname = titem->getToolName();
1837 // Get digging properties for material and tool
1838 DiggingProperties prop =
1839 getDiggingProperties(material, toolname);
1841 if(prop.diggable == false)
1843 derr_server<<"Server: WARNING: Player digged"
1844 <<" with impossible material + tool"
1845 <<" combination"<<std::endl;
1848 bool weared_out = titem->addWear(prop.wear);
1852 mlist->deleteItem(item_i);
1858 Add digged item to inventory
1860 InventoryItem *item = new MaterialItem(material, 1);
1861 player->inventory.addItem("main", item);
1866 SendInventory(player->peer_id);
1871 (this takes some time so it is done after the quick stuff)
1873 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1879 // Update water pressure around modification
1880 // This also adds it to m_flow_active_nodes if appropriate
1882 MapVoxelManipulator v(&m_env.getMap());
1883 v.m_disable_water_climb =
1884 g_settings.getBool("disable_water_climb");
1886 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1890 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1892 catch(ProcessingLimitException &e)
1894 dstream<<"Processing limit reached (1)"<<std::endl;
1897 v.blitBack(modified_blocks);
1903 else if(action == 1)
1906 InventoryList *ilist = player->inventory.getList("main");
1911 InventoryItem *item = ilist->getItem(item_i);
1913 // If there is no item, it is not possible to add it anywhere
1918 Handle material items
1920 if(std::string("MaterialItem") == item->getName())
1923 // Don't add a node if this is not a free space
1924 MapNode n2 = m_env.getMap().getNode(p_over);
1925 if(content_buildable_to(n2.d) == false)
1928 catch(InvalidPositionException &e)
1930 derr_server<<"Server: Ignoring ADDNODE: Node not found"
1935 // Reset build time counter
1936 getClient(peer->id)->m_time_from_building.set(0.0);
1939 MaterialItem *mitem = (MaterialItem*)item;
1941 n.d = mitem->getMaterial();
1942 if(content_directional(n.d))
1943 n.dir = packDir(p_under - p_over);
1947 u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
1948 SharedBuffer<u8> reply(replysize);
1949 writeU16(&reply[0], TOCLIENT_ADDNODE);
1950 writeS16(&reply[2], p_over.X);
1951 writeS16(&reply[4], p_over.Y);
1952 writeS16(&reply[6], p_over.Z);
1953 n.serialize(&reply[8], peer_ser_ver);
1955 m_con.SendToAll(0, reply, true);
1960 InventoryList *ilist = player->inventory.getList("main");
1961 if(g_settings.getBool("creative_mode") == false && ilist)
1963 // Remove from inventory and send inventory
1964 if(mitem->getCount() == 1)
1965 ilist->deleteItem(item_i);
1969 SendInventory(peer_id);
1975 This takes some time so it is done after the quick stuff
1977 core::map<v3s16, MapBlock*> modified_blocks;
1978 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1984 InventoryList *ilist = player->inventory.getList("main");
1985 if(g_settings.getBool("creative_mode") == false && ilist)
1987 // Remove from inventory and send inventory
1988 if(mitem->getCount() == 1)
1989 ilist->deleteItem(item_i);
1993 SendInventory(peer_id);
1999 This takes some time so it is done after the quick stuff
2001 core::map<v3s16, MapBlock*> modified_blocks;
2002 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
2005 Set the modified blocks unsent for all the clients
2008 //JMutexAutoLock lock2(m_con_mutex);
2010 for(core::map<u16, RemoteClient*>::Iterator
2011 i = m_clients.getIterator();
2012 i.atEnd() == false; i++)
2014 RemoteClient *client = i.getNode()->getValue();
2016 if(modified_blocks.size() > 0)
2018 // Remove block from sent history
2019 client->SetBlocksNotSent(modified_blocks);
2028 // Update water pressure around modification
2029 // This also adds it to m_flow_active_nodes if appropriate
2031 MapVoxelManipulator v(&m_env.getMap());
2032 v.m_disable_water_climb =
2033 g_settings.getBool("disable_water_climb");
2035 VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
2039 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2041 catch(ProcessingLimitException &e)
2043 dstream<<"Processing limit reached (1)"<<std::endl;
2046 v.blitBack(modified_blocks);
2053 v3s16 blockpos = getNodeBlockPos(p_over);
2055 MapBlock *block = NULL;
2058 block = m_env.getMap().getBlockNoCreate(blockpos);
2060 catch(InvalidPositionException &e)
2062 derr_server<<"Error while placing object: "
2063 "block not found"<<std::endl;
2067 v3s16 block_pos_i_on_map = block->getPosRelative();
2068 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
2070 v3f pos = intToFloat(p_over);
2071 pos -= block_pos_f_on_map;
2073 /*dout_server<<"pos="
2074 <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
2077 MapBlockObject *obj = NULL;
2080 Handle block object items
2082 if(std::string("MBOItem") == item->getName())
2084 MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
2086 /*dout_server<<"Trying to place a MapBlockObjectItem: "
2087 "inventorystring=\""
2088 <<oitem->getInventoryString()
2089 <<"\""<<std::endl;*/
2091 obj = oitem->createObject
2092 (pos, player->getYaw(), player->getPitch());
2099 dout_server<<"Placing a miscellaneous item on map"
2102 Create an ItemObject that contains the item.
2104 ItemObject *iobj = new ItemObject(NULL, -1, pos);
2105 std::ostringstream os(std::ios_base::binary);
2106 item->serialize(os);
2107 dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
2108 iobj->setItemString(os.str());
2114 derr_server<<"WARNING: item resulted in NULL object, "
2115 <<"not placing onto map"
2120 block->addObject(obj);
2122 dout_server<<"Placed object"<<std::endl;
2124 InventoryList *ilist = player->inventory.getList("main");
2125 if(g_settings.getBool("creative_mode") == false && ilist)
2127 // Remove from inventory and send inventory
2128 ilist->deleteItem(item_i);
2130 SendInventory(peer_id);
2138 Catch invalid actions
2142 derr_server<<"WARNING: Server: Invalid action "
2143 <<action<<std::endl;
2147 else if(command == TOSERVER_RELEASE)
2156 dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
2159 else if(command == TOSERVER_SIGNTEXT)
2168 std::string datastring((char*)&data[2], datasize-2);
2169 std::istringstream is(datastring, std::ios_base::binary);
2172 is.read((char*)buf, 6);
2173 v3s16 blockpos = readV3S16(buf);
2174 is.read((char*)buf, 2);
2175 s16 id = readS16(buf);
2176 is.read((char*)buf, 2);
2177 u16 textlen = readU16(buf);
2179 for(u16 i=0; i<textlen; i++)
2181 is.read((char*)buf, 1);
2182 text += (char)buf[0];
2185 MapBlock *block = NULL;
2188 block = m_env.getMap().getBlockNoCreate(blockpos);
2190 catch(InvalidPositionException &e)
2192 derr_server<<"Error while setting sign text: "
2193 "block not found"<<std::endl;
2197 MapBlockObject *obj = block->getObject(id);
2200 derr_server<<"Error while setting sign text: "
2201 "object not found"<<std::endl;
2205 if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
2207 derr_server<<"Error while setting sign text: "
2208 "object is not a sign"<<std::endl;
2212 ((SignObject*)obj)->setText(text);
2214 obj->getBlock()->setChangedFlag();
2216 else if(command == TOSERVER_INVENTORY_ACTION)
2218 // Ignore inventory changes if in creative mode
2219 if(g_settings.getBool("creative_mode") == true)
2221 dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
2225 // Strip command and create a stream
2226 std::string datastring((char*)&data[2], datasize-2);
2227 dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
2228 std::istringstream is(datastring, std::ios_base::binary);
2230 InventoryAction *a = InventoryAction::deSerialize(is);
2234 Handle craftresult specially
2236 bool disable_action = false;
2237 if(a->getType() == IACTION_MOVE)
2239 IMoveAction *ma = (IMoveAction*)a;
2240 // Don't allow moving anything to craftresult
2241 if(ma->to_name == "craftresult")
2244 disable_action = true;
2246 // When something is removed from craftresult
2247 if(ma->from_name == "craftresult")
2249 disable_action = true;
2250 // Remove stuff from craft
2251 InventoryList *clist = player->inventory.getList("craft");
2254 u16 count = ma->count;
2257 clist->decrementMaterials(count);
2260 // Feed action to player inventory
2261 a->apply(&player->inventory);
2264 // If something appeared in craftresult, throw it
2266 InventoryList *rlist = player->inventory.getList("craftresult");
2267 InventoryList *mlist = player->inventory.getList("main");
2268 if(rlist && mlist && rlist->getUsedSlots() == 1)
2270 InventoryItem *item1 = rlist->changeItem(0, NULL);
2271 mlist->addItem(item1);
2275 if(disable_action == false)
2277 // Feed action to player inventory
2278 a->apply(&player->inventory);
2283 SendInventory(player->peer_id);
2287 dstream<<"TOSERVER_INVENTORY_ACTION: "
2288 <<"InventoryAction::deSerialize() returned NULL"
2292 else if(command == TOSERVER_CHAT_MESSAGE)
2300 std::string datastring((char*)&data[2], datasize-2);
2301 std::istringstream is(datastring, std::ios_base::binary);
2304 is.read((char*)buf, 2);
2305 u16 len = readU16(buf);
2307 std::wstring message;
2308 for(u16 i=0; i<len; i++)
2310 is.read((char*)buf, 2);
2311 message += (wchar_t)readU16(buf);
2314 dstream<<"CHAT: "<<wide_to_narrow(message)<<std::endl;
2316 // Get player name of this client
2317 std::wstring name = narrow_to_wide(player->getName());
2319 std::wstring line = std::wstring(L"<")+name+L"> "+message;
2322 Send the message to all other clients
2324 for(core::map<u16, RemoteClient*>::Iterator
2325 i = m_clients.getIterator();
2326 i.atEnd() == false; i++)
2328 // Get client and check that it is valid
2329 RemoteClient *client = i.getNode()->getValue();
2330 assert(client->peer_id == i.getNode()->getKey());
2331 if(client->serialization_version == SER_FMT_VER_INVALID)
2334 // Don't send if it's the same one
2335 if(peer_id == client->peer_id)
2338 SendChatMessage(client->peer_id, line);
2343 derr_server<<"WARNING: Server::ProcessData(): Ignoring "
2344 "unknown command "<<command<<std::endl;
2348 catch(SendFailedException &e)
2350 derr_server<<"Server::ProcessData(): SendFailedException: "
2356 /*void Server::Send(u16 peer_id, u16 channelnum,
2357 SharedBuffer<u8> data, bool reliable)
2359 JMutexAutoLock lock(m_con_mutex);
2360 m_con.Send(peer_id, channelnum, data, reliable);
2363 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
2365 DSTACK(__FUNCTION_NAME);
2367 Create a packet with the block in the right format
2370 std::ostringstream os(std::ios_base::binary);
2371 block->serialize(os, ver);
2372 std::string s = os.str();
2373 SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
2375 u32 replysize = 8 + blockdata.getSize();
2376 SharedBuffer<u8> reply(replysize);
2377 v3s16 p = block->getPos();
2378 writeU16(&reply[0], TOCLIENT_BLOCKDATA);
2379 writeS16(&reply[2], p.X);
2380 writeS16(&reply[4], p.Y);
2381 writeS16(&reply[6], p.Z);
2382 memcpy(&reply[8], *blockdata, blockdata.getSize());
2384 /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2385 <<": \tpacket size: "<<replysize<<std::endl;*/
2390 m_con.Send(peer_id, 1, reply, true);
2393 core::list<PlayerInfo> Server::getPlayerInfo()
2395 DSTACK(__FUNCTION_NAME);
2396 JMutexAutoLock envlock(m_env_mutex);
2397 JMutexAutoLock conlock(m_con_mutex);
2399 core::list<PlayerInfo> list;
2401 core::list<Player*> players = m_env.getPlayers();
2403 core::list<Player*>::Iterator i;
2404 for(i = players.begin();
2405 i != players.end(); i++)
2409 Player *player = *i;
2411 con::Peer *peer = m_con.GetPeer(player->peer_id);
2413 info.address = peer->address;
2414 info.avg_rtt = peer->avg_rtt;
2416 catch(con::PeerNotFoundException &e)
2418 // Outdated peer info
2420 info.address = Address(0,0,0,0,0);
2424 snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
2425 info.position = player->getPosition();
2427 list.push_back(info);
2433 void Server::peerAdded(con::Peer *peer)
2435 DSTACK(__FUNCTION_NAME);
2436 dout_server<<"Server::peerAdded(): peer->id="
2437 <<peer->id<<std::endl;
2440 c.type = PEER_ADDED;
2441 c.peer_id = peer->id;
2443 m_peer_change_queue.push_back(c);
2446 void Server::deletingPeer(con::Peer *peer, bool timeout)
2448 DSTACK(__FUNCTION_NAME);
2449 dout_server<<"Server::deletingPeer(): peer->id="
2450 <<peer->id<<", timeout="<<timeout<<std::endl;
2453 c.type = PEER_REMOVED;
2454 c.peer_id = peer->id;
2455 c.timeout = timeout;
2456 m_peer_change_queue.push_back(c);
2459 void Server::SendObjectData(float dtime)
2461 DSTACK(__FUNCTION_NAME);
2463 core::map<v3s16, bool> stepped_blocks;
2465 for(core::map<u16, RemoteClient*>::Iterator
2466 i = m_clients.getIterator();
2467 i.atEnd() == false; i++)
2469 u16 peer_id = i.getNode()->getKey();
2470 RemoteClient *client = i.getNode()->getValue();
2471 assert(client->peer_id == peer_id);
2473 if(client->serialization_version == SER_FMT_VER_INVALID)
2476 client->SendObjectData(this, dtime, stepped_blocks);
2480 void Server::SendPlayerInfos()
2482 DSTACK(__FUNCTION_NAME);
2484 //JMutexAutoLock envlock(m_env_mutex);
2486 core::list<Player*> players = m_env.getPlayers();
2488 u32 player_count = players.getSize();
2489 u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
2491 SharedBuffer<u8> data(datasize);
2492 writeU16(&data[0], TOCLIENT_PLAYERINFO);
2495 core::list<Player*>::Iterator i;
2496 for(i = players.begin();
2497 i != players.end(); i++)
2499 Player *player = *i;
2501 /*dstream<<"Server sending player info for player with "
2502 "peer_id="<<player->peer_id<<std::endl;*/
2504 writeU16(&data[start], player->peer_id);
2505 snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
2506 start += 2+PLAYERNAME_SIZE;
2509 //JMutexAutoLock conlock(m_con_mutex);
2512 m_con.SendToAll(0, data, true);
2530 ItemSpec(enum ItemSpecType a_type, std::string a_name):
2536 ItemSpec(enum ItemSpecType a_type, u16 a_num):
2542 enum ItemSpecType type;
2543 // Only other one of these is used
2549 items: a pointer to an array of 9 pointers to items
2550 specs: a pointer to an array of 9 ItemSpecs
2552 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
2554 u16 items_min_x = 100;
2555 u16 items_max_x = 100;
2556 u16 items_min_y = 100;
2557 u16 items_max_y = 100;
2558 for(u16 y=0; y<3; y++)
2559 for(u16 x=0; x<3; x++)
2561 if(items[y*3 + x] == NULL)
2563 if(items_min_x == 100 || x < items_min_x)
2565 if(items_min_y == 100 || y < items_min_y)
2567 if(items_max_x == 100 || x > items_max_x)
2569 if(items_max_y == 100 || y > items_max_y)
2572 // No items at all, just return false
2573 if(items_min_x == 100)
2576 u16 items_w = items_max_x - items_min_x + 1;
2577 u16 items_h = items_max_y - items_min_y + 1;
2579 u16 specs_min_x = 100;
2580 u16 specs_max_x = 100;
2581 u16 specs_min_y = 100;
2582 u16 specs_max_y = 100;
2583 for(u16 y=0; y<3; y++)
2584 for(u16 x=0; x<3; x++)
2586 if(specs[y*3 + x].type == ITEM_NONE)
2588 if(specs_min_x == 100 || x < specs_min_x)
2590 if(specs_min_y == 100 || y < specs_min_y)
2592 if(specs_max_x == 100 || x > specs_max_x)
2594 if(specs_max_y == 100 || y > specs_max_y)
2597 // No specs at all, just return false
2598 if(specs_min_x == 100)
2601 u16 specs_w = specs_max_x - specs_min_x + 1;
2602 u16 specs_h = specs_max_y - specs_min_y + 1;
2605 if(items_w != specs_w || items_h != specs_h)
2608 for(u16 y=0; y<specs_h; y++)
2609 for(u16 x=0; x<specs_w; x++)
2611 u16 items_x = items_min_x + x;
2612 u16 items_y = items_min_y + y;
2613 u16 specs_x = specs_min_x + x;
2614 u16 specs_y = specs_min_y + y;
2615 InventoryItem *item = items[items_y * 3 + items_x];
2616 ItemSpec &spec = specs[specs_y * 3 + specs_x];
2618 if(spec.type == ITEM_NONE)
2620 // Has to be no item
2626 // There should be an item
2630 std::string itemname = item->getName();
2632 if(spec.type == ITEM_MATERIAL)
2634 if(itemname != "MaterialItem")
2636 MaterialItem *mitem = (MaterialItem*)item;
2637 if(mitem->getMaterial() != spec.num)
2640 else if(spec.type == ITEM_CRAFT)
2642 if(itemname != "CraftItem")
2644 CraftItem *mitem = (CraftItem*)item;
2645 if(mitem->getSubName() != spec.name)
2648 else if(spec.type == ITEM_TOOL)
2650 // Not supported yet
2653 else if(spec.type == ITEM_MBO)
2655 // Not supported yet
2660 // Not supported yet
2668 void Server::SendInventory(u16 peer_id)
2670 DSTACK(__FUNCTION_NAME);
2672 Player* player = m_env.getPlayer(peer_id);
2675 Calculate crafting stuff
2678 InventoryList *clist = player->inventory.getList("craft");
2679 InventoryList *rlist = player->inventory.getList("craftresult");
2682 rlist->clearItems();
2686 InventoryItem *items[9];
2687 for(u16 i=0; i<9; i++)
2689 items[i] = clist->getItem(i);
2698 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
2699 if(checkItemCombination(items, specs))
2701 rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
2710 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2711 if(checkItemCombination(items, specs))
2713 rlist->addItem(new CraftItem("Stick", 4));
2722 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2723 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2724 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2725 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2726 specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2727 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2728 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2729 if(checkItemCombination(items, specs))
2731 rlist->addItem(new MapBlockObjectItem("Sign"));
2740 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
2741 specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
2742 if(checkItemCombination(items, specs))
2744 rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
2753 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2754 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2755 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2756 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2757 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2758 if(checkItemCombination(items, specs))
2760 rlist->addItem(new ToolItem("WPick", 0));
2769 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2770 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2771 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2772 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2773 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2774 if(checkItemCombination(items, specs))
2776 rlist->addItem(new ToolItem("STPick", 0));
2785 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2786 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2787 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2788 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2789 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2790 if(checkItemCombination(items, specs))
2792 rlist->addItem(new ToolItem("MesePick", 0));
2803 std::ostringstream os;
2804 //os.imbue(std::locale("C"));
2806 player->inventory.serialize(os);
2808 std::string s = os.str();
2810 SharedBuffer<u8> data(s.size()+2);
2811 writeU16(&data[0], TOCLIENT_INVENTORY);
2812 memcpy(&data[2], s.c_str(), s.size());
2815 m_con.Send(peer_id, 0, data, true);
2818 void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
2820 DSTACK(__FUNCTION_NAME);
2822 std::ostringstream os(std::ios_base::binary);
2826 writeU16(buf, TOCLIENT_CHAT_MESSAGE);
2827 os.write((char*)buf, 2);
2830 writeU16(buf, message.size());
2831 os.write((char*)buf, 2);
2834 for(u32 i=0; i<message.size(); i++)
2838 os.write((char*)buf, 2);
2842 std::string s = os.str();
2843 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
2845 m_con.Send(peer_id, 0, data, true);
2848 void Server::BroadcastChatMessage(const std::wstring &message)
2850 for(core::map<u16, RemoteClient*>::Iterator
2851 i = m_clients.getIterator();
2852 i.atEnd() == false; i++)
2854 // Get client and check that it is valid
2855 RemoteClient *client = i.getNode()->getValue();
2856 assert(client->peer_id == i.getNode()->getKey());
2857 if(client->serialization_version == SER_FMT_VER_INVALID)
2860 SendChatMessage(client->peer_id, message);
2864 void Server::SendBlocks(float dtime)
2866 DSTACK(__FUNCTION_NAME);
2868 JMutexAutoLock envlock(m_env_mutex);
2870 core::array<PrioritySortedBlockTransfer> queue;
2872 s32 total_sending = 0;
2874 for(core::map<u16, RemoteClient*>::Iterator
2875 i = m_clients.getIterator();
2876 i.atEnd() == false; i++)
2878 RemoteClient *client = i.getNode()->getValue();
2879 assert(client->peer_id == i.getNode()->getKey());
2881 total_sending += client->SendingCount();
2883 if(client->serialization_version == SER_FMT_VER_INVALID)
2886 client->GetNextBlocks(this, dtime, queue);
2890 // Lowest priority number comes first.
2891 // Lowest is most important.
2894 JMutexAutoLock conlock(m_con_mutex);
2896 for(u32 i=0; i<queue.size(); i++)
2898 //TODO: Calculate limit dynamically
2899 if(total_sending >= g_settings.getS32
2900 ("max_simultaneous_block_sends_server_total"))
2903 PrioritySortedBlockTransfer q = queue[i];
2905 MapBlock *block = NULL;
2908 block = m_env.getMap().getBlockNoCreate(q.pos);
2910 catch(InvalidPositionException &e)
2915 RemoteClient *client = getClient(q.peer_id);
2917 SendBlockNoLock(q.peer_id, block, client->serialization_version);
2919 client->SentBlock(q.pos);
2926 RemoteClient* Server::getClient(u16 peer_id)
2928 DSTACK(__FUNCTION_NAME);
2929 //JMutexAutoLock lock(m_con_mutex);
2930 core::map<u16, RemoteClient*>::Node *n;
2931 n = m_clients.find(peer_id);
2932 // A client should exist for all peers
2934 return n->getValue();
2937 void Server::UpdateBlockWaterPressure(MapBlock *block,
2938 core::map<v3s16, MapBlock*> &modified_blocks)
2940 MapVoxelManipulator v(&m_env.getMap());
2941 v.m_disable_water_climb =
2942 g_settings.getBool("disable_water_climb");
2944 VoxelArea area(block->getPosRelative(),
2945 block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
2949 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2951 catch(ProcessingLimitException &e)
2953 dstream<<"Processing limit reached (1)"<<std::endl;
2956 v.blitBack(modified_blocks);
2959 void Server::handlePeerChange(PeerChange &c)
2961 JMutexAutoLock envlock(m_env_mutex);
2962 JMutexAutoLock conlock(m_con_mutex);
2964 if(c.type == PEER_ADDED)
2971 core::map<u16, RemoteClient*>::Node *n;
2972 n = m_clients.find(c.peer_id);
2973 // The client shouldn't already exist
2977 RemoteClient *client = new RemoteClient();
2978 client->peer_id = c.peer_id;
2979 m_clients.insert(client->peer_id, client);
2983 Player *player = m_env.getPlayer(c.peer_id);
2985 // The player shouldn't already exist
2986 assert(player == NULL);
2988 player = new ServerRemotePlayer();
2989 player->peer_id = c.peer_id;
2995 // We're going to throw the player to this position
2996 //v2s16 nodepos(29990,29990);
2997 //v2s16 nodepos(9990,9990);
2999 v2s16 sectorpos = getNodeSectorPos(nodepos);
3000 // Get zero sector (it could have been unloaded to disk)
3001 m_env.getMap().emergeSector(sectorpos);
3002 // Get ground height at origin
3003 f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
3004 // The sector should have been generated -> groundheight exists
3005 assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
3006 // Don't go underwater
3007 if(groundheight < WATER_LEVEL)
3008 groundheight = WATER_LEVEL;
3010 player->setPosition(intToFloat(v3s16(
3017 Add player to environment
3020 m_env.addPlayer(player);
3023 Add stuff to inventory
3026 if(g_settings.getBool("creative_mode"))
3030 InventoryItem *item = new ToolItem("STPick", 32000);
3031 void* r = player->inventory.addItem("main", item);
3034 // Give all materials
3035 assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
3036 for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
3038 // Skip some materials
3039 if(i == CONTENT_OCEAN)
3042 InventoryItem *item = new MaterialItem(i, 1);
3043 player->inventory.addItem("main", item);
3047 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3048 void* r = player->inventory.addItem("main", item);
3053 InventoryItem *item = new MapBlockObjectItem("Rat");
3054 bool r = player->inventory.addItem("main", item);
3061 InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
3062 void* r = player->inventory.addItem("main", item);
3066 InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
3067 void* r = player->inventory.addItem("main", item);
3071 InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
3072 void* r = player->inventory.addItem("main", item);
3076 InventoryItem *item = new CraftItem("Stick", 4);
3077 void* r = player->inventory.addItem("main", item);
3081 InventoryItem *item = new ToolItem("WPick", 32000);
3082 void* r = player->inventory.addItem("main", item);
3086 InventoryItem *item = new ToolItem("STPick", 32000);
3087 void* r = player->inventory.addItem("main", item);
3090 /*// Give some lights
3092 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
3093 bool r = player->inventory.addItem("main", item);
3097 for(u16 i=0; i<4; i++)
3099 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3100 bool r = player->inventory.addItem("main", item);
3103 /*// Give some other stuff
3105 InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
3106 bool r = player->inventory.addItem("main", item);
3113 else if(c.type == PEER_REMOVED)
3120 core::map<u16, RemoteClient*>::Node *n;
3121 n = m_clients.find(c.peer_id);
3122 // The client should exist
3125 // Collect information about leaving in chat
3126 std::wstring message;
3128 std::wstring name = L"unknown";
3129 Player *player = m_env.getPlayer(c.peer_id);
3131 name = narrow_to_wide(player->getName());
3135 message += L" left game";
3137 message += L" (timed out)";
3142 m_env.removePlayer(c.peer_id);
3146 delete m_clients[c.peer_id];
3147 m_clients.remove(c.peer_id);
3149 // Send player info to all remaining clients
3152 // Send leave chat message to all remaining clients
3153 BroadcastChatMessage(message);
3162 void Server::handlePeerChanges()
3164 while(m_peer_change_queue.size() > 0)
3166 PeerChange c = m_peer_change_queue.pop_front();
3168 dout_server<<"Server: Handling peer change: "
3169 <<"id="<<c.peer_id<<", timeout="<<c.timeout
3172 handlePeerChange(c);