From eb9d45e9065a94c93dc2f1624c22f026b9be3d5f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 19:18:06 +0200 Subject: [PATCH 01/21] Moved MaxPlayers and Description from cWorld to cServer. Also started creating a new cWorld::cTickThread class, but not used yet. --- source/Bindings.cpp | 235 ++++++++++++++----------- source/Bindings.h | 2 +- source/ClientHandle.cpp | 14 +- source/Protocol/ProtocolRecognizer.cpp | 17 +- source/Server.cpp | 64 ++++--- source/Server.h | 17 +- source/World.cpp | 73 +++++--- source/World.h | 38 ++-- 8 files changed, 274 insertions(+), 186 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index fb532b6f2..ca6b42902 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 14:53:45. +** Generated automatically by tolua++-1.0.92 on 08/11/13 19:12:37. */ #ifndef __cplusplus @@ -11306,6 +11306,135 @@ static int tolua_get_cPlugin_NewLua___cWebPlugin__(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: GetDescription of class cServer */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_GetDescription00 +static int tolua_AllToLua_cServer_GetDescription00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"const cServer",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + const cServer* self = (const cServer*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetDescription'", NULL); +#endif + { + const AString tolua_ret = (const AString) self->GetDescription(); + tolua_pushcppstring(tolua_S,(const char*)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetDescription'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + +/* method: GetMaxPlayers of class cServer */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_GetMaxPlayers00 +static int tolua_AllToLua_cServer_GetMaxPlayers00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"const cServer",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + const cServer* self = (const cServer*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetMaxPlayers'", NULL); +#endif + { + int tolua_ret = (int) self->GetMaxPlayers(); + tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetMaxPlayers'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + +/* method: GetNumPlayers of class cServer */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_GetNumPlayers00 +static int tolua_AllToLua_cServer_GetNumPlayers00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"const cServer",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + const cServer* self = (const cServer*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetNumPlayers'", NULL); +#endif + { + int tolua_ret = (int) self->GetNumPlayers(); + tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'GetNumPlayers'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + +/* method: SetMaxPlayers of class cServer */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_SetMaxPlayers00 +static int tolua_AllToLua_cServer_SetMaxPlayers00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"cServer",0,&tolua_err) || + !tolua_isnumber(tolua_S,2,0,&tolua_err) || + !tolua_isnoobj(tolua_S,3,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + cServer* self = (cServer*) tolua_tousertype(tolua_S,1,0); + int a_MaxPlayers = ((int) tolua_tonumber(tolua_S,2,0)); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'SetMaxPlayers'", NULL); +#endif + { + self->SetMaxPlayers(a_MaxPlayers); + } + } + return 0; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'SetMaxPlayers'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: BroadcastChat of class cServer */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_BroadcastChat00 static int tolua_AllToLua_cServer_BroadcastChat00(lua_State* tolua_S) @@ -11954,103 +12083,6 @@ static int tolua_AllToLua_cWorld_UnloadUnusedChunks00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE -/* method: GetMaxPlayers of class cWorld */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_GetMaxPlayers00 -static int tolua_AllToLua_cWorld_GetMaxPlayers00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"const cWorld",0,&tolua_err) || - !tolua_isnoobj(tolua_S,2,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - const cWorld* self = (const cWorld*) tolua_tousertype(tolua_S,1,0); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetMaxPlayers'", NULL); -#endif - { - unsigned int tolua_ret = (unsigned int) self->GetMaxPlayers(); - tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'GetMaxPlayers'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - -/* method: SetMaxPlayers of class cWorld */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_SetMaxPlayers00 -static int tolua_AllToLua_cWorld_SetMaxPlayers00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cWorld",0,&tolua_err) || - !tolua_isnumber(tolua_S,2,0,&tolua_err) || - !tolua_isnoobj(tolua_S,3,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cWorld* self = (cWorld*) tolua_tousertype(tolua_S,1,0); - int iMax = ((int) tolua_tonumber(tolua_S,2,0)); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'SetMaxPlayers'", NULL); -#endif - { - self->SetMaxPlayers(iMax); - } - } - return 0; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'SetMaxPlayers'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - -/* method: GetNumPlayers of class cWorld */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_GetNumPlayers00 -static int tolua_AllToLua_cWorld_GetNumPlayers00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cWorld",0,&tolua_err) || - !tolua_isnoobj(tolua_S,2,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cWorld* self = (cWorld*) tolua_tousertype(tolua_S,1,0); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetNumPlayers'", NULL); -#endif - { - unsigned int tolua_ret = (unsigned int) self->GetNumPlayers(); - tolua_pushnumber(tolua_S,(lua_Number)tolua_ret); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'GetNumPlayers'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - /* method: RegenerateChunk of class cWorld */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_RegenerateChunk00 static int tolua_AllToLua_cWorld_RegenerateChunk00(lua_State* tolua_S) @@ -29783,6 +29815,10 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_endmodule(tolua_S); tolua_cclass(tolua_S,"cServer","cServer","",NULL); tolua_beginmodule(tolua_S,"cServer"); + tolua_function(tolua_S,"GetDescription",tolua_AllToLua_cServer_GetDescription00); + tolua_function(tolua_S,"GetMaxPlayers",tolua_AllToLua_cServer_GetMaxPlayers00); + tolua_function(tolua_S,"GetNumPlayers",tolua_AllToLua_cServer_GetNumPlayers00); + tolua_function(tolua_S,"SetMaxPlayers",tolua_AllToLua_cServer_SetMaxPlayers00); tolua_function(tolua_S,"BroadcastChat",tolua_AllToLua_cServer_BroadcastChat00); tolua_function(tolua_S,"SendMessage",tolua_AllToLua_cServer_SendMessage00); tolua_function(tolua_S,"GetServerID",tolua_AllToLua_cServer_GetServerID00); @@ -29806,9 +29842,6 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"GetDimension",tolua_AllToLua_cWorld_GetDimension00); tolua_function(tolua_S,"GetHeight",tolua_AllToLua_cWorld_GetHeight00); tolua_function(tolua_S,"UnloadUnusedChunks",tolua_AllToLua_cWorld_UnloadUnusedChunks00); - tolua_function(tolua_S,"GetMaxPlayers",tolua_AllToLua_cWorld_GetMaxPlayers00); - tolua_function(tolua_S,"SetMaxPlayers",tolua_AllToLua_cWorld_SetMaxPlayers00); - tolua_function(tolua_S,"GetNumPlayers",tolua_AllToLua_cWorld_GetNumPlayers00); tolua_function(tolua_S,"RegenerateChunk",tolua_AllToLua_cWorld_RegenerateChunk00); tolua_function(tolua_S,"GenerateChunk",tolua_AllToLua_cWorld_GenerateChunk00); tolua_function(tolua_S,"SetBlock",tolua_AllToLua_cWorld_SetBlock00); diff --git a/source/Bindings.h b/source/Bindings.h index ec1e372f7..d80c98782 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 14:53:46. +** Generated automatically by tolua++-1.0.92 on 08/11/13 19:12:37. */ /* Exported function */ diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index 102153de1..14b052652 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -421,11 +421,11 @@ void cClientHandle::HandlePing(void) // Somebody tries to retrieve information about the server AString Reply; Printf(Reply, "%s%s%i%s%i", - cRoot::Get()->GetDefaultWorld()->GetDescription().c_str(), - cChatColor::Delimiter.c_str(), - cRoot::Get()->GetDefaultWorld()->GetNumPlayers(), - cChatColor::Delimiter.c_str(), - cRoot::Get()->GetDefaultWorld()->GetMaxPlayers() + cRoot::Get()->GetServer()->GetDescription().c_str(), + cChatColor::Delimiter.c_str(), + cRoot::Get()->GetServer()->GetNumPlayers(), + cChatColor::Delimiter.c_str(), + cRoot::Get()->GetServer()->GetMaxPlayers() ); Kick(Reply.c_str()); } @@ -1176,7 +1176,7 @@ bool cClientHandle::HandleHandshake(const AString & a_Username) { if (!cRoot::Get()->GetPluginManager()->CallHookHandshake(this, a_Username)) { - if (cRoot::Get()->GetDefaultWorld()->GetNumPlayers() >= cRoot::Get()->GetDefaultWorld()->GetMaxPlayers()) + if (cRoot::Get()->GetServer()->GetNumPlayers() >= cRoot::Get()->GetServer()->GetMaxPlayers()) { Kick("The server is currently full :(-- Try again later"); return false; @@ -1191,7 +1191,7 @@ bool cClientHandle::HandleHandshake(const AString & a_Username) void cClientHandle::HandleEntityAction(int a_EntityID, char a_ActionID) { - if( a_EntityID != m_Player->GetUniqueID() ) + if (a_EntityID != m_Player->GetUniqueID()) { // We should only receive entity actions from the entity that is performing the action return; diff --git a/source/Protocol/ProtocolRecognizer.cpp b/source/Protocol/ProtocolRecognizer.cpp index 290f2df75..35c8fe993 100644 --- a/source/Protocol/ProtocolRecognizer.cpp +++ b/source/Protocol/ProtocolRecognizer.cpp @@ -14,6 +14,7 @@ #include "Protocol16x.h" #include "../ClientHandle.h" #include "../Root.h" +#include "../Server.h" #include "../World.h" #include "../ChatColor.h" @@ -729,11 +730,11 @@ void cProtocolRecognizer::HandleServerPing(void) { // http://wiki.vg/wiki/index.php?title=Protocol&oldid=3099#Server_List_Ping_.280xFE.29 Printf(Reply, "%s%s%i%s%i", - cRoot::Get()->GetDefaultWorld()->GetDescription().c_str(), - cChatColor::Delimiter.c_str(), - cRoot::Get()->GetDefaultWorld()->GetNumPlayers(), - cChatColor::Delimiter.c_str(), - cRoot::Get()->GetDefaultWorld()->GetMaxPlayers() + cRoot::Get()->GetServer()->GetDescription().c_str(), + cChatColor::Delimiter.c_str(), + cRoot::Get()->GetServer()->GetNumPlayers(), + cChatColor::Delimiter.c_str(), + cRoot::Get()->GetServer()->GetMaxPlayers() ); break; } @@ -759,9 +760,9 @@ void cProtocolRecognizer::HandleServerPing(void) // http://wiki.vg/wiki/index.php?title=Server_List_Ping&oldid=3100 AString NumPlayers; - Printf(NumPlayers, "%d", cRoot::Get()->GetDefaultWorld()->GetNumPlayers()); + Printf(NumPlayers, "%d", cRoot::Get()->GetServer()->GetNumPlayers()); AString MaxPlayers; - Printf(MaxPlayers, "%d", cRoot::Get()->GetDefaultWorld()->GetMaxPlayers()); + Printf(MaxPlayers, "%d", cRoot::Get()->GetServer()->GetMaxPlayers()); AString ProtocolVersionNum; Printf(ProtocolVersionNum, "%d", cRoot::Get()->m_PrimaryServerVersion); @@ -775,7 +776,7 @@ void cProtocolRecognizer::HandleServerPing(void) Reply.push_back(0); Reply.append(ProtocolVersionTxt); Reply.push_back(0); - Reply.append(cRoot::Get()->GetDefaultWorld()->GetDescription()); + Reply.append(cRoot::Get()->GetServer()->GetDescription()); Reply.push_back(0); Reply.append(NumPlayers); Reply.push_back(0); diff --git a/source/Server.cpp b/source/Server.cpp index b07c65f04..509c84af3 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -76,6 +76,38 @@ struct cServer::sServerState +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer: + +cServer::cServer(void) : + m_pState(new sServerState), + m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"), + m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"), + m_Millisecondsf(0), + m_Milliseconds(0), + m_bIsConnected(false), + m_bRestarting(false), + m_RCONServer(*this) +{ +} + + + + + +cServer::~cServer() +{ + // TODO: Shut down the server gracefully + m_pState->bStopTickThread = true; + delete m_pState->pTickThread; m_pState->pTickThread = NULL; + + delete m_pState; +} + + + + + void cServer::ClientDestroying(const cClientHandle * a_Client) { m_SocketThreads.StopReading(a_Client); @@ -123,6 +155,9 @@ void cServer::RemoveClient(const cClientHandle * a_Client) bool cServer::InitServer(cIniFile & a_SettingsIni) { + m_Description = a_SettingsIni.GetValue ("Server", "Description", "MCServer! - In C++!").c_str(); + m_MaxPlayers = a_SettingsIni.GetValueI("Server", "MaxPlayers", 100); + if (m_bIsConnected) { LOGERROR("ERROR: Trying to initialize server while server is already running!"); @@ -201,35 +236,6 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) -cServer::cServer(void) - : m_pState(new sServerState) - , m_ListenThreadIPv4(*this, cSocket::IPv4, "Client") - , m_ListenThreadIPv6(*this, cSocket::IPv6, "Client") - , m_Millisecondsf(0) - , m_Milliseconds(0) - , m_bIsConnected(false) - , m_bRestarting(false) - , m_RCONServer(*this) -{ -} - - - - - -cServer::~cServer() -{ - // TODO: Shut down the server gracefully - m_pState->bStopTickThread = true; - delete m_pState->pTickThread; m_pState->pTickThread = NULL; - - delete m_pState; -} - - - - - void cServer::PrepareKeys(void) { // TODO: Save and load key for persistence across sessions diff --git a/source/Server.h b/source/Server.h index dd7a08735..44e20eec1 100644 --- a/source/Server.h +++ b/source/Server.h @@ -37,7 +37,18 @@ class cServer // tolua_export public: // tolua_export bool InitServer(cIniFile & a_SettingsIni); - bool IsConnected(void) const { return m_bIsConnected;} // returns connection status + // tolua_begin + + const AString & GetDescription(void) const {return m_Description; } + + // Player counts: + int GetMaxPlayers(void) const {return m_MaxPlayers; } + int GetNumPlayers(void) const { return m_NumPlayers; } + void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; } + + // tolua_end + + // bool IsConnected(void) const { return m_bIsConnected;} // returns connection status void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export @@ -131,6 +142,10 @@ private: cRCONServer m_RCONServer; + AString m_Description; + int m_MaxPlayers; + int m_NumPlayers; + cServer(void); ~cServer(); diff --git a/source/World.cpp b/source/World.cpp index 776bacff8..ebfee971b 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -128,6 +128,9 @@ protected: +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWorldLightingProgress: + /// A simple thread that displays the progress of world lighting in cWorld::InitializeSpawn() class cWorldLightingProgress : public cIsThread @@ -187,10 +190,46 @@ cWorld::cLock::cLock(cWorld & a_World) : +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWorld::cTickThread: + +cWorld::cTickThread::cTickThread(cWorld & a_World) : + super(Printf("WorldTickThread: %s", a_World.GetName().c_str())), + m_World(a_World) +{ +} + + + + + +void cWorld::cTickThread::Execute(void) +{ + const int ClocksPerTick = CLOCKS_PER_SEC / 20; + clock_t LastTime = clock(); + while (!m_ShouldTerminate) + { + clock_t Start = clock(); + m_World.Tick((float)(LastTime - Start) / CLOCKS_PER_SEC); + clock_t Now = clock(); + if (Now - Start < ClocksPerTick) + { + cSleep::MilliSleep(1000 * (ClocksPerTick - (Now - Start)) / CLOCKS_PER_SEC); + } + LastTime = Start; + } +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cWorld: cWorld::cWorld(const AString & a_WorldName) : + m_WorldName(a_WorldName), + m_IniFileName(m_WorldName + "/world.ini"), m_WorldAgeSecs(0), m_TimeOfDaySecs(0), m_WorldAge(0), @@ -199,24 +238,26 @@ cWorld::cWorld(const AString & a_WorldName) : m_LastSpawnMonster(0), m_RSList(0), m_Weather(eWeather_Sunny), - m_WeatherInterval(24000) // Guaranteed 1 day of sunshine at server start :) + m_WeatherInterval(24000), // Guaranteed 1 day of sunshine at server start :) + m_TickThread(*this) { LOGD("cWorld::cWorld(%s)", a_WorldName.c_str()); - m_WorldName = a_WorldName; - m_IniFileName = m_WorldName + "/world.ini"; cMakeDir::MakeDir(m_WorldName.c_str()); - MTRand r1; - m_SpawnX = (double)((r1.randInt() % 1000) - 500); + // TODO: Find a proper spawn location, based on the biomes (not in ocean) + m_SpawnX = (double)((m_TickRand.randInt() % 1000) - 500); m_SpawnY = cChunkDef::Height; - m_SpawnZ = (double)((r1.randInt() % 1000) - 500); + m_SpawnZ = (double)((m_TickRand.randInt() % 1000) - 500); m_GameMode = eGameMode_Creative; AString StorageSchema("Default"); cIniFile IniFile(m_IniFileName); - IniFile.ReadFile(); + if (!IniFile.ReadFile()) + { + LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str()); + } AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld"); m_Dimension = StringToDimension(Dimension); switch (m_Dimension) @@ -268,9 +309,6 @@ cWorld::cWorld(const AString & a_WorldName) : m_bAnimals = IniFile2.GetValueB("Monsters", "AnimalsOn", true); m_SpawnMonsterRate = (Int64)(IniFile2.GetValueF("Monsters", "AnimalSpawnInterval", 10) * 20); // Convert from secs to ticks - // TODO: Move this into cServer instead: - SetMaxPlayers(IniFile2.GetValueI("Server", "MaxPlayers", 100)); - m_Description = IniFile2.GetValue("Server", "Description", "MCServer! - In C++!").c_str(); } m_ChunkMap = new cChunkMap(this); @@ -1893,19 +1931,6 @@ void cWorld::CollectPickupsByPlayer(cPlayer * a_Player) -void cWorld::SetMaxPlayers(int iMax) -{ - m_MaxPlayers = MAX_PLAYERS; - if (iMax > 0 && iMax < MAX_PLAYERS) - { - m_MaxPlayers = iMax; - } -} - - - - - void cWorld::AddPlayer(cPlayer * a_Player) { cCSLock Lock(m_CSPlayers); @@ -2299,11 +2324,13 @@ void cWorld::RemoveEntity(cEntity * a_Entity) +/* unsigned int cWorld::GetNumPlayers(void) { cCSLock Lock(m_CSPlayers); return m_Players.size(); } +*/ diff --git a/source/World.h b/source/World.h index b9182e300..1ae56a410 100644 --- a/source/World.h +++ b/source/World.h @@ -204,13 +204,6 @@ public: void CollectPickupsByPlayer(cPlayer * a_Player); - // MOTD - const AString & GetDescription(void) const {return m_Description; } // FIXME: This should not be in cWorld - - // Max Players - unsigned int GetMaxPlayers(void) const {return m_MaxPlayers; } // tolua_export - void SetMaxPlayers(int iMax); // tolua_export - void AddPlayer( cPlayer* a_Player ); void RemovePlayer( cPlayer* a_Player ); @@ -223,8 +216,6 @@ public: /// Finds a player from a partial or complete player name and calls the callback - case-insensitive bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << - unsigned int GetNumPlayers(); // tolua_export - // TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action) cPlayer * FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit); @@ -484,8 +475,6 @@ public: inline int GetStorageLoadQueueLength(void) { return m_Storage.GetLoadQueueLength(); } // tolua_export inline int GetStorageSaveQueueLength(void) { return m_Storage.GetSaveQueueLength(); } // tolua_export - void Tick(float a_Dt); - void InitializeSpawn(void); /// Stops threads that belong to this world (part of deinit) @@ -540,7 +529,25 @@ public: private: friend class cRoot; + + class cTickThread : + public cIsThread + { + typedef cIsThread super; + public: + cTickThread(cWorld & a_World); + + protected: + cWorld & m_World; + + // cIsThread overrides: + virtual void Execute(void) override; + } ; + + AString m_WorldName; + AString m_IniFileName; + /// The dimension of the world, used by the client to provide correct lighting scheme eDimension m_Dimension; @@ -583,8 +590,6 @@ private: cWorldStorage m_Storage; - AString m_Description; - unsigned int m_MaxPlayers; cChunkMap * m_ChunkMap; @@ -616,13 +621,14 @@ private: cChunkSender m_ChunkSender; cLightingThread m_Lighting; + cTickThread m_TickThread; + - AString m_WorldName; - AString m_IniFileName; - cWorld(const AString & a_WorldName); ~cWorld(); + void Tick(float a_Dt); + void TickWeather(float a_Dt); // Handles weather each tick void TickSpawnMobs(float a_Dt); // Handles mob spawning each tick From 2baa6634ec797d4a64c25fca9b5a0451b69bf886 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 19:40:15 +0200 Subject: [PATCH 02/21] cIsThread: Added the Stop() method and debugging output in Wait() --- source/OSSupport/IsThread.cpp | 44 ++++++++++++++++++----------------- source/OSSupport/IsThread.h | 4 +++- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/source/OSSupport/IsThread.cpp b/source/OSSupport/IsThread.cpp index 257c5c876..45e329a68 100644 --- a/source/OSSupport/IsThread.cpp +++ b/source/OSSupport/IsThread.cpp @@ -116,36 +116,38 @@ bool cIsThread::Start(void) +void cIsThread::Stop(void) +{ + if (m_Handle == NULL) + { + return; + } + m_ShouldTerminate = true; + Wait(); +} + + + + + bool cIsThread::Wait(void) { - #ifdef _WIN32 + if (m_Handle == NULL) + { + return true; + } + LOGD("Waiting for thread %s to finish", m_ThreadName.c_str()); - if (m_Handle == NULL) - { - return true; - } - // Cannot log, logger may already be stopped: - // LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str()); + #ifdef _WIN32 int res = WaitForSingleObject(m_Handle, INFINITE); m_Handle = NULL; - // Cannot log, logger may already be stopped: - // LOG("Thread \"%s\" %s terminated, GLE = %d", m_ThreadName.c_str(), (res == WAIT_OBJECT_0) ? "" : "not", GetLastError()); + LOGD("Thread %s finished", m_ThreadName.c_str()); return (res == WAIT_OBJECT_0); - #else // _WIN32 - - if (!m_HasStarted) - { - return true; - } - // Cannot log, logger may already be stopped: - // LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str()); int res = pthread_join(m_Handle, NULL); - m_HasStarted = false; - // Cannot log, logger may already be stopped: - // LOG("Thread \"%s\" %s terminated, errno = %d", m_ThreadName.c_str(), (res == 0) ? "" : "not", errno); + m_Handle = NULL; + LOGD("Thread %s finished", m_ThreadName.c_str()); return (res == 0); - #endif // else _WIN32 } diff --git a/source/OSSupport/IsThread.h b/source/OSSupport/IsThread.h index 2a4451a4a..e795b25a0 100644 --- a/source/OSSupport/IsThread.h +++ b/source/OSSupport/IsThread.h @@ -39,6 +39,9 @@ public: /// Starts the thread; returns without waiting for the actual start bool Start(void); + /// Signals the thread to terminate and waits until it's finished + void Stop(void); + /// Waits for the thread to finish. Doesn't signalize the ShouldTerminate flag bool Wait(void); @@ -70,7 +73,6 @@ private: } #endif // else _WIN32 - } ; From c59d80a2af12d902577ea8cb022e81dd0740247f Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 19:46:27 +0200 Subject: [PATCH 03/21] Removed cServer::m_pState, dissolved into direct member variables. The server tick thread is now in the cServer::cTickThread object. --- source/Server.cpp | 141 ++++++++++++++++------------------------------ source/Server.h | 35 ++++++++---- 2 files changed, 75 insertions(+), 101 deletions(-) diff --git a/source/Server.cpp b/source/Server.cpp index 509c84af3..4247a1dfe 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -59,18 +59,42 @@ typedef std::list< cClientHandle* > ClientList; -struct cServer::sServerState +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer::cTickThread: + +cServer::cTickThread::cTickThread(cServer & a_Server) : + super("ServerTickThread"), + m_Server(a_Server) { - sServerState() - : pTickThread(NULL) - , bStopTickThread(false) - {} +} - cThread* pTickThread; bool bStopTickThread; - cEvent RestartEvent; - std::string ServerID; -}; + + + +void cServer::cTickThread::Execute(void) +{ + cTimer Timer; + + long long msPerTick = 50; // TODO - Put this in server config file + long long LastTime = Timer.GetNowTime(); + + while (!m_ShouldTerminate) + { + long long NowTime = Timer.GetNowTime(); + float DeltaTime = (float)(NowTime-LastTime); + m_ShouldTerminate = !m_Server.Tick(DeltaTime); + long long TickTime = Timer.GetNowTime() - NowTime; + + if (TickTime < msPerTick) + { + // Stretch tick time until it's at least msPerTick + cSleep::MilliSleep((unsigned int)(msPerTick - TickTime)); + } + + LastTime = NowTime; + } +} @@ -80,14 +104,12 @@ struct cServer::sServerState // cServer: cServer::cServer(void) : - m_pState(new sServerState), m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"), m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"), - m_Millisecondsf(0), - m_Milliseconds(0), m_bIsConnected(false), m_bRestarting(false), - m_RCONServer(*this) + m_RCONServer(*this), + m_TickThread(*this) { } @@ -95,19 +117,6 @@ cServer::cServer(void) : -cServer::~cServer() -{ - // TODO: Shut down the server gracefully - m_pState->bStopTickThread = true; - delete m_pState->pTickThread; m_pState->pTickThread = NULL; - - delete m_pState; -} - - - - - void cServer::ClientDestroying(const cClientHandle * a_Client) { m_SocketThreads.StopReading(a_Client); @@ -199,18 +208,17 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) m_bIsConnected = true; - m_pState->ServerID = "-"; + m_ServerID = "-"; if (a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true)) { MTRand mtrand1; - unsigned int r1 = (mtrand1.randInt()%1147483647) + 1000000000; - unsigned int r2 = (mtrand1.randInt()%1147483647) + 1000000000; + unsigned int r1 = (mtrand1.randInt() % 1147483647) + 1000000000; + unsigned int r2 = (mtrand1.randInt() % 1147483647) + 1000000000; std::ostringstream sid; sid << std::hex << r1; sid << std::hex << r2; - std::string ServerID = sid.str(); - ServerID.resize(16, '0'); - m_pState->ServerID = ServerID; + m_ServerID = sid.str(); + m_ServerID.resize(16, '0'); } m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE); @@ -309,15 +317,8 @@ void cServer::BroadcastChat(const AString & a_Message, const cClientHandle * a_E bool cServer::Tick(float a_Dt) { - m_Millisecondsf += a_Dt; - if (m_Millisecondsf > 1.f) - { - m_Milliseconds += (int)m_Millisecondsf; - m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf; - } - - cRoot::Get()->TickWorlds(a_Dt); // TODO - Maybe give all worlds their own thread? - + cRoot::Get()->TickWorlds(a_Dt); + cClientHandleList RemoveClients; { cCSLock Lock(m_CSClients); @@ -338,8 +339,6 @@ bool cServer::Tick(float a_Dt) delete *itr; } // for itr - RemoveClients[] - cRoot::Get()->GetPluginManager()->Tick(a_Dt); - if (!m_bRestarting) { return true; @@ -347,7 +346,7 @@ bool cServer::Tick(float a_Dt) else { m_bRestarting = false; - m_pState->RestartEvent.Set(); + m_RestartEvent.Set(); return false; } } @@ -356,42 +355,8 @@ bool cServer::Tick(float a_Dt) -void ServerTickThread( void * a_Param ) -{ - LOG("ServerTickThread"); - cServer *CServerObj = (cServer*)a_Param; - - cTimer Timer; - - long long msPerTick = 50; // TODO - Put this in server config file - long long LastTime = Timer.GetNowTime(); - - bool bKeepGoing = true; - while( bKeepGoing ) - { - long long NowTime = Timer.GetNowTime(); - float DeltaTime = (float)(NowTime-LastTime); - bKeepGoing = CServerObj->Tick( DeltaTime ); - long long TickTime = Timer.GetNowTime() - NowTime; - - if( TickTime < msPerTick ) // Stretch tick time until it's at least msPerTick - { - cSleep::MilliSleep( (unsigned int)( msPerTick - TickTime ) ); - } - - LastTime = NowTime; - } - - LOG("TICK THREAD STOPPED"); -} - - - - - bool cServer::Start(void) { - m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" ); if (!m_ListenThreadIPv4.Start()) { return false; @@ -400,7 +365,10 @@ bool cServer::Start(void) { return false; } - m_pState->pTickThread->Start( true ); + if (!m_TickThread.Start()) + { + return false; + } return true; } @@ -503,13 +471,13 @@ void cServer::SendMessage(const AString & a_Message, cPlayer * a_Player /* = NUL -void cServer::Shutdown() +void cServer::Shutdown(void) { m_ListenThreadIPv4.Stop(); m_ListenThreadIPv6.Stop(); m_bRestarting = true; - m_pState->RestartEvent.Wait(); + m_RestartEvent.Wait(); cRoot::Get()->SaveAllChunks(); @@ -526,15 +494,6 @@ void cServer::Shutdown() -const AString & cServer::GetServerID(void) const -{ - return m_pState->ServerID; -} - - - - - void cServer::KickUser(int a_ClientID, const AString & a_Reason) { cCSLock Lock(m_CSClients); @@ -568,7 +527,7 @@ void cServer::AuthenticateUser(int a_ClientID) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cServer::cClientPacketThread: +// cServer::cNotifyWriteThread: cServer::cNotifyWriteThread::cNotifyWriteThread(void) : super("ClientPacketThread"), diff --git a/source/Server.h b/source/Server.h index 44e20eec1..983dc4de8 100644 --- a/source/Server.h +++ b/source/Server.h @@ -52,8 +52,6 @@ public: // tolua_export void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export - bool Tick(float a_Dt); - bool Start(void); bool Command(cClientHandle & a_Client, AString & a_Cmd); @@ -71,7 +69,7 @@ public: // tolua_export void KickUser(int a_ClientID, const AString & a_Reason); void AuthenticateUser(int a_ClientID); // Called by cAuthenticator to auth the specified user - const AString & GetServerID(void) const; // tolua_export + const AString & GetServerID(void) const { return m_ServerID; } // tolua_export void ClientDestroying(const cClientHandle * a_Client); // Called by cClientHandle::Destroy(); stop m_SocketThreads from calling back into a_Client @@ -114,8 +112,22 @@ private: void NotifyClientWrite(const cClientHandle * a_Client); } ; - struct sServerState; - sServerState * m_pState; + /// The server tick thread takes care of the players who aren't yet spawned in a world + class cTickThread : + public cIsThread + { + typedef cIsThread super; + + public: + cTickThread(cServer & a_Server); + + protected: + cServer & m_Server; + + // cIsThread overrides: + virtual void Execute(void) override; + } ; + cNotifyWriteThread m_NotifyWriteThread; @@ -129,10 +141,6 @@ private: int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini - // Time since server was started - float m_Millisecondsf; - unsigned int m_Milliseconds; - bool m_bIsConnected; // true - connected false - not connected bool m_bRestarting; @@ -146,13 +154,20 @@ private: int m_MaxPlayers; int m_NumPlayers; + cTickThread m_TickThread; + cEvent m_RestartEvent; + + /// The server ID used for client authentication + AString m_ServerID; + cServer(void); - ~cServer(); /// Loads, or generates, if missing, RSA keys for protocol encryption void PrepareKeys(void); + bool Tick(float a_Dt); + // cListenThread::cCallback overrides: virtual void OnConnectionAccepted(cSocket & a_Socket) override; }; // tolua_export From 4c5590636cf4a311f03e735878557b1e7b3362dd Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 20:16:41 +0200 Subject: [PATCH 04/21] Each world now ticks in a separate thread. --- source/Root.cpp | 15 +- source/Root.h | 5 +- source/Server.cpp | 2 +- source/Server.h | 2 +- source/World.cpp | 204 ++++++++++++++------------- source/World.h | 9 +- source/WorldStorage/WorldStorage.cpp | 9 ++ source/WorldStorage/WorldStorage.h | 1 + 8 files changed, 135 insertions(+), 112 deletions(-) diff --git a/source/Root.cpp b/source/Root.cpp index 5ec27aa0d..166932cf2 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -270,8 +270,9 @@ void cRoot::LoadWorlds(void) void cRoot::StartWorlds(void) { - for( WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr ) + for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) { + itr->second->Start(); itr->second->InitializeSpawn(); } } @@ -282,9 +283,9 @@ void cRoot::StartWorlds(void) void cRoot::StopWorlds(void) { - for( WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr ) + for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) { - itr->second->StopThreads(); + itr->second->Stop(); } } @@ -344,7 +345,7 @@ bool cRoot::ForEachWorld(cWorldListCallback & a_Callback) -void cRoot::TickWorlds(float a_Dt) +void cRoot::TickCommands(void) { // Execute any pending commands: cCommandQueue PendingCommands; @@ -356,12 +357,6 @@ void cRoot::TickWorlds(float a_Dt) { ExecuteConsoleCommand(itr->m_Command, *(itr->m_Output)); } - - // Tick the worlds: - for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr) - { - itr->second->Tick(a_Dt); - } } diff --git a/source/Root.h b/source/Root.h index 1e2befcd4..262c9b0e5 100644 --- a/source/Root.h +++ b/source/Root.h @@ -85,9 +85,10 @@ public: /// Called by cAuthenticator to auth the specified user void AuthenticateUser(int a_ClientID); - - void TickWorlds(float a_Dt); + /// Executes commands queued in the command queue + void TickCommands(void); + /// Returns the number of chunks loaded int GetTotalChunkCount(void); // tolua_export diff --git a/source/Server.cpp b/source/Server.cpp index 4247a1dfe..0045d4808 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -317,7 +317,7 @@ void cServer::BroadcastChat(const AString & a_Message, const cClientHandle * a_E bool cServer::Tick(float a_Dt) { - cRoot::Get()->TickWorlds(a_Dt); + cRoot::Get()->TickCommands(); cClientHandleList RemoveClients; { diff --git a/source/Server.h b/source/Server.h index 983dc4de8..a00485fa2 100644 --- a/source/Server.h +++ b/source/Server.h @@ -62,7 +62,7 @@ public: // tolua_export /// Binds the built-in console commands with the plugin manager static void BindBuiltInConsoleCommands(void); - void Shutdown(); + void Shutdown(void); void SendMessage(const AString & a_Message, cPlayer * a_Player = NULL, bool a_bExclude = false ); // tolua_export diff --git a/source/World.cpp b/source/World.cpp index ebfee971b..af66d1ead 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -230,6 +230,7 @@ void cWorld::cTickThread::Execute(void) cWorld::cWorld(const AString & a_WorldName) : m_WorldName(a_WorldName), m_IniFileName(m_WorldName + "/world.ini"), + m_StorageSchema("Default"), m_WorldAgeSecs(0), m_TimeOfDaySecs(0), m_WorldAge(0), @@ -244,102 +245,6 @@ cWorld::cWorld(const AString & a_WorldName) : LOGD("cWorld::cWorld(%s)", a_WorldName.c_str()); cMakeDir::MakeDir(m_WorldName.c_str()); - - // TODO: Find a proper spawn location, based on the biomes (not in ocean) - m_SpawnX = (double)((m_TickRand.randInt() % 1000) - 500); - m_SpawnY = cChunkDef::Height; - m_SpawnZ = (double)((m_TickRand.randInt() % 1000) - 500); - m_GameMode = eGameMode_Creative; - - AString StorageSchema("Default"); - - cIniFile IniFile(m_IniFileName); - if (!IniFile.ReadFile()) - { - LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str()); - } - AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld"); - m_Dimension = StringToDimension(Dimension); - switch (m_Dimension) - { - case dimNether: - case dimOverworld: - case dimEnd: - { - break; - } - default: - { - LOGWARNING("Unknown dimension: \"%s\". Setting to Overworld", Dimension.c_str()); - m_Dimension = dimOverworld; - break; - } - } // switch (m_Dimension) - m_SpawnX = IniFile.GetValueSetF("SpawnPosition", "X", m_SpawnX); - m_SpawnY = IniFile.GetValueSetF("SpawnPosition", "Y", m_SpawnY); - m_SpawnZ = IniFile.GetValueSetF("SpawnPosition", "Z", m_SpawnZ); - StorageSchema = IniFile.GetValueSet ("Storage", "Schema", StorageSchema); - m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3); - m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3); - m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false); - m_IsCarrotsBonemealable = IniFile.GetValueSetB("Plants", "IsCarrotsBonemealable", true); - m_IsCropsBonemealable = IniFile.GetValueSetB("Plants", "IsCropsBonemealable", true); - m_IsGrassBonemealable = IniFile.GetValueSetB("Plants", "IsGrassBonemealable", true); - m_IsMelonStemBonemealable = IniFile.GetValueSetB("Plants", "IsMelonStemBonemealable", true); - m_IsMelonBonemealable = IniFile.GetValueSetB("Plants", "IsMelonBonemealable", false); - m_IsPotatoesBonemealable = IniFile.GetValueSetB("Plants", "IsPotatoesBonemealable", true); - m_IsPumpkinStemBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinStemBonemealable", true); - m_IsPumpkinBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinBonemealable", false); - m_IsSaplingBonemealable = IniFile.GetValueSetB("Plants", "IsSaplingBonemealable", true); - m_IsSugarcaneBonemealable = IniFile.GetValueSetB("Plants", "IsSugarcaneBonemealable", false); - m_bEnabledPVP = IniFile.GetValueSetB("PVP", "Enabled", true); - m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", false); - - m_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode); - - m_Lighting.Start(this); - m_Storage.Start(this, StorageSchema); - m_Generator.Start(this, IniFile); - - m_bAnimals = true; - m_SpawnMonsterRate = 200; // 1 mob each 10 seconds - cIniFile IniFile2("settings.ini"); - if (IniFile2.ReadFile()) - { - m_bAnimals = IniFile2.GetValueB("Monsters", "AnimalsOn", true); - m_SpawnMonsterRate = (Int64)(IniFile2.GetValueF("Monsters", "AnimalSpawnInterval", 10) * 20); // Convert from secs to ticks - - } - - m_ChunkMap = new cChunkMap(this); - - m_ChunkSender.Start(this); - - m_LastSave = 0; - m_LastUnload = 0; - - // preallocate some memory for ticking blocks so we don�t need to allocate that often - m_BlockTickQueue.reserve(1000); - m_BlockTickQueueCopy.reserve(1000); - - // Simulators: - m_SimulatorManager = new cSimulatorManager(*this); - m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER); - m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA); - m_SandSimulator = new cSandSimulator(*this, IniFile); - m_FireSimulator = new cFireSimulator(*this, IniFile); - m_RedstoneSimulator = new cRedstoneSimulator(*this); - - // Water and Lava simulators get registered in InitializeFluidSimulator() - m_SimulatorManager->RegisterSimulator(m_SandSimulator, 1); - m_SimulatorManager->RegisterSimulator(m_FireSimulator, 1); - m_SimulatorManager->RegisterSimulator(m_RedstoneSimulator, 1); - - // Save any changes that the defaults may have done to the ini file: - if (!IniFile.WriteFile()) - { - LOGWARNING("Could not write world config to %s", m_IniFileName.c_str()); - } } @@ -542,10 +447,115 @@ void cWorld::InitializeSpawn(void) -void cWorld::StopThreads(void) +void cWorld::Start(void) { + // TODO: Find a proper spawn location, based on the biomes (not in ocean) + m_SpawnX = (double)((m_TickRand.randInt() % 1000) - 500); + m_SpawnY = cChunkDef::Height; + m_SpawnZ = (double)((m_TickRand.randInt() % 1000) - 500); + m_GameMode = eGameMode_Creative; + + cIniFile IniFile(m_IniFileName); + if (!IniFile.ReadFile()) + { + LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str()); + } + AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld"); + m_Dimension = StringToDimension(Dimension); + switch (m_Dimension) + { + case dimNether: + case dimOverworld: + case dimEnd: + { + break; + } + default: + { + LOGWARNING("Unknown dimension: \"%s\". Setting to Overworld", Dimension.c_str()); + m_Dimension = dimOverworld; + break; + } + } // switch (m_Dimension) + m_SpawnX = IniFile.GetValueSetF("SpawnPosition", "X", m_SpawnX); + m_SpawnY = IniFile.GetValueSetF("SpawnPosition", "Y", m_SpawnY); + m_SpawnZ = IniFile.GetValueSetF("SpawnPosition", "Z", m_SpawnZ); + m_StorageSchema = IniFile.GetValueSet ("Storage", "Schema", m_StorageSchema); + m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3); + m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3); + m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false); + m_IsCarrotsBonemealable = IniFile.GetValueSetB("Plants", "IsCarrotsBonemealable", true); + m_IsCropsBonemealable = IniFile.GetValueSetB("Plants", "IsCropsBonemealable", true); + m_IsGrassBonemealable = IniFile.GetValueSetB("Plants", "IsGrassBonemealable", true); + m_IsMelonStemBonemealable = IniFile.GetValueSetB("Plants", "IsMelonStemBonemealable", true); + m_IsMelonBonemealable = IniFile.GetValueSetB("Plants", "IsMelonBonemealable", false); + m_IsPotatoesBonemealable = IniFile.GetValueSetB("Plants", "IsPotatoesBonemealable", true); + m_IsPumpkinStemBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinStemBonemealable", true); + m_IsPumpkinBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinBonemealable", false); + m_IsSaplingBonemealable = IniFile.GetValueSetB("Plants", "IsSaplingBonemealable", true); + m_IsSugarcaneBonemealable = IniFile.GetValueSetB("Plants", "IsSugarcaneBonemealable", false); + m_bEnabledPVP = IniFile.GetValueSetB("PVP", "Enabled", true); + m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", false); + + m_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode); + + m_bAnimals = true; + m_SpawnMonsterRate = 200; // 1 mob each 10 seconds + cIniFile IniFile2("settings.ini"); + if (IniFile2.ReadFile()) + { + m_bAnimals = IniFile2.GetValueB("Monsters", "AnimalsOn", true); + m_SpawnMonsterRate = (Int64)(IniFile2.GetValueF("Monsters", "AnimalSpawnInterval", 10) * 20); // Convert from secs to ticks + + } + + m_ChunkMap = new cChunkMap(this); + + m_LastSave = 0; + m_LastUnload = 0; + + // preallocate some memory for ticking blocks so we don�t need to allocate that often + m_BlockTickQueue.reserve(1000); + m_BlockTickQueueCopy.reserve(1000); + + // Simulators: + m_SimulatorManager = new cSimulatorManager(*this); + m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER); + m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA); + m_SandSimulator = new cSandSimulator(*this, IniFile); + m_FireSimulator = new cFireSimulator(*this, IniFile); + m_RedstoneSimulator = new cRedstoneSimulator(*this); + + // Water and Lava simulators get registered in InitializeFluidSimulator() + m_SimulatorManager->RegisterSimulator(m_SandSimulator, 1); + m_SimulatorManager->RegisterSimulator(m_FireSimulator, 1); + m_SimulatorManager->RegisterSimulator(m_RedstoneSimulator, 1); + + m_Lighting.Start(this); + m_Storage.Start(this, m_StorageSchema); + m_Generator.Start(this, IniFile); + m_ChunkSender.Start(this); + m_TickThread.Start(); + + // Save any changes that the defaults may have done to the ini file: + if (!IniFile.WriteFile()) + { + LOGWARNING("Could not write world config to %s", m_IniFileName.c_str()); + } + +} + + + + + +void cWorld::Stop(void) +{ + m_TickThread.Stop(); + m_Lighting.Stop(); m_Generator.Stop(); m_ChunkSender.Stop(); + m_Storage.Stop(); } diff --git a/source/World.h b/source/World.h index 1ae56a410..a12a9e40e 100644 --- a/source/World.h +++ b/source/World.h @@ -477,8 +477,12 @@ public: void InitializeSpawn(void); + /// Starts threads that belong to this world + void Start(void); + /// Stops threads that belong to this world (part of deinit) - void StopThreads(void); + void Stop(void); + void TickQueuedBlocks(float a_Dt); struct BlockTickQueueItem @@ -548,6 +552,9 @@ private: AString m_WorldName; AString m_IniFileName; + /// Name of the storage schema used to load and save chunks + AString m_StorageSchema; + /// The dimension of the world, used by the client to provide correct lighting scheme eDimension m_Dimension; diff --git a/source/WorldStorage/WorldStorage.cpp b/source/WorldStorage/WorldStorage.cpp index 8b055b240..7ff5ae8e8 100644 --- a/source/WorldStorage/WorldStorage.cpp +++ b/source/WorldStorage/WorldStorage.cpp @@ -84,6 +84,15 @@ bool cWorldStorage::Start(cWorld * a_World, const AString & a_StorageSchemaName) +void cWorldStorage::Stop(void) +{ + WaitForFinish(); +} + + + + + void cWorldStorage::WaitForFinish(void) { LOG("Waiting for the world storage to finish saving"); diff --git a/source/WorldStorage/WorldStorage.h b/source/WorldStorage/WorldStorage.h index 064b2ffaf..bf8dbd3d5 100644 --- a/source/WorldStorage/WorldStorage.h +++ b/source/WorldStorage/WorldStorage.h @@ -75,6 +75,7 @@ public: void UnqueueSave(const cChunkCoords & a_Chunk); bool Start(cWorld * a_World, const AString & a_StorageSchemaName); // Hide the cIsThread's Start() method, we need to provide args + void Stop(void); // Hide the cIsThread's Stop() method, we need to signal the event void WaitForFinish(void); void WaitForQueuesEmpty(void); From ac9224da919fb642aba8dde71d1c8deaf0cc8005 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 20:22:42 +0200 Subject: [PATCH 05/21] cIsThread threads get a window identification on Win. This enables tools such as TaskInfo to report the thread name directly. --- source/OSSupport/IsThread.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/OSSupport/IsThread.h b/source/OSSupport/IsThread.h index e795b25a0..9b7f0b73e 100644 --- a/source/OSSupport/IsThread.h +++ b/source/OSSupport/IsThread.h @@ -57,7 +57,9 @@ private: static DWORD_PTR __stdcall thrExecute(LPVOID a_Param) { + HWND IdentificationWnd = CreateWindow("STATIC", ((cIsThread *)a_Param)->m_ThreadName.c_str(), 0, 0, 0, 0, WS_OVERLAPPED, NULL, NULL, NULL, NULL); ((cIsThread *)a_Param)->Execute(); + DestroyWindow(IdentificationWnd); return 0; } From 829cc866cd63c50ebff4dac2f942a0df93d269fc Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 11 Aug 2013 21:05:44 +0200 Subject: [PATCH 06/21] Added cWorld:QueueSaveAllChunks() function for saving chunks asynchronously. The cWorld:SaveAllChunks() is therefore deprecated in the API and will be removed soon, use QueueSaveAllChunks() instead. --- source/Bindings.cpp | 34 ++++++++++++++++++++++++++- source/Bindings.h | 2 +- source/World.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++- source/World.h | 38 ++++++++++++++++++++++++++++-- 4 files changed, 125 insertions(+), 5 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index ca6b42902..46c5f0f07 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 19:12:37. +** Generated automatically by tolua++-1.0.92 on 08/11/13 20:59:51. */ #ifndef __cplusplus @@ -13434,6 +13434,37 @@ static int tolua_AllToLua_cWorld_SaveAllChunks00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: QueueSaveAllChunks of class cWorld */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_QueueSaveAllChunks00 +static int tolua_AllToLua_cWorld_QueueSaveAllChunks00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"cWorld",0,&tolua_err) || + !tolua_isnoobj(tolua_S,2,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + cWorld* self = (cWorld*) tolua_tousertype(tolua_S,1,0); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'QueueSaveAllChunks'", NULL); +#endif + { + self->QueueSaveAllChunks(); + } + } + return 0; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'QueueSaveAllChunks'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: GetNumChunks of class cWorld */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_GetNumChunks00 static int tolua_AllToLua_cWorld_GetNumChunks00(lua_State* tolua_S) @@ -29878,6 +29909,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"GetBiomeAt",tolua_AllToLua_cWorld_GetBiomeAt00); tolua_function(tolua_S,"GetName",tolua_AllToLua_cWorld_GetName00); tolua_function(tolua_S,"SaveAllChunks",tolua_AllToLua_cWorld_SaveAllChunks00); + tolua_function(tolua_S,"QueueSaveAllChunks",tolua_AllToLua_cWorld_QueueSaveAllChunks00); tolua_function(tolua_S,"GetNumChunks",tolua_AllToLua_cWorld_GetNumChunks00); tolua_function(tolua_S,"GetGeneratorQueueLength",tolua_AllToLua_cWorld_GetGeneratorQueueLength00); tolua_function(tolua_S,"GetLightingQueueLength",tolua_AllToLua_cWorld_GetLightingQueueLength00); diff --git a/source/Bindings.h b/source/Bindings.h index d80c98782..10f874e41 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 19:12:37. +** Generated automatically by tolua++-1.0.92 on 08/11/13 20:59:52. */ /* Exported function */ diff --git a/source/World.cpp b/source/World.cpp index af66d1ead..9212202e9 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -1,3 +1,4 @@ + #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "BlockID.h" @@ -242,7 +243,7 @@ cWorld::cWorld(const AString & a_WorldName) : m_WeatherInterval(24000), // Guaranteed 1 day of sunshine at server start :) m_TickThread(*this) { - LOGD("cWorld::cWorld(%s)", a_WorldName.c_str()); + LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str()); cMakeDir::MakeDir(m_WorldName.c_str()); } @@ -587,6 +588,7 @@ void cWorld::Tick(float a_Dt) m_ChunkMap->Tick(a_Dt); TickQueuedBlocks(a_Dt); + TickQueuedTasks(); GetSimulatorManager()->Simulate(a_Dt); @@ -781,6 +783,27 @@ void cWorld::TickSpawnMobs(float a_Dt) +void cWorld::TickQueuedTasks(void) +{ + // Make a copy of the tasks to avoid deadlocks on accessing m_Tasks + cTasks Tasks; + { + cCSLock Lock(m_CSTasks); + std::swap(Tasks, m_Tasks); + } + + // Execute and delete each task: + for (cTasks::iterator itr = m_Tasks.begin(), end = m_Tasks.end(); itr != end; ++itr) + { + (*itr)->Run(*this); + delete *itr; + } // for itr - m_Tasks[] +} + + + + + void cWorld::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) { return m_ChunkMap->WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ); @@ -2307,6 +2330,25 @@ void cWorld::SaveAllChunks(void) +void cWorld::QueueSaveAllChunks(void) +{ + QueueTask(new cWorld::cTaskSaveAllChunks); +} + + + + + +void cWorld::QueueTask(cTask * a_Task) +{ + cCSLock Lock(m_CSTasks); + m_Tasks.push_back(a_Task); +} + + + + + void cWorld::AddEntity(cEntity * a_Entity) { m_ChunkMap->AddEntity(a_Entity); @@ -2552,3 +2594,15 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cWorld::cTaskSaveAllChunks: + +void cWorld::cTaskSaveAllChunks::Run(cWorld & a_World) +{ + a_World.SaveAllChunks(); +} + + + + diff --git a/source/World.h b/source/World.h index a12a9e40e..d84920470 100644 --- a/source/World.h +++ b/source/World.h @@ -69,6 +69,24 @@ public: public: cLock(cWorld & a_World); } ; + + /// A common ancestor for all tasks queued onto the tick thread + class cTask + { + public: + virtual void Run(cWorld & a_World) = 0; + } ; + + typedef std::vector cTasks; + + class cTaskSaveAllChunks : + public cTask + { + protected: + // cTask overrides: + virtual void Run(cWorld & a_World) override; + } ; + // tolua_begin @@ -461,10 +479,17 @@ public: if(a_Z < 0 && a_Z % cChunkDef::Width != 0) a_ChunkZ--; } - void SaveAllChunks(void); // tolua_export + /// Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead + void SaveAllChunks(void); // tolua_export + + /// Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources + void QueueSaveAllChunks(void); // tolua_export + + /// Queues a task onto the tick thread. The task object will be deleted once the task is finished + void QueueTask(cTask * a_Task); /// Returns the number of chunks loaded - int GetNumChunks() const; // tolua_export + int GetNumChunks() const; // tolua_export /// Returns the number of chunks loaded and dirty, and in the lighting queue void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue); @@ -629,6 +654,12 @@ private: cChunkSender m_ChunkSender; cLightingThread m_Lighting; cTickThread m_TickThread; + + /// Guards the m_Tasks + cCriticalSection m_CSTasks; + + /// Tasks that have been queued onto the tick thread; guarded by m_CSTasks + cTasks m_Tasks; cWorld(const AString & a_WorldName); @@ -639,6 +670,9 @@ private: void TickWeather(float a_Dt); // Handles weather each tick void TickSpawnMobs(float a_Dt); // Handles mob spawning each tick + /// Executes all tasks queued onto the tick thread + void TickQueuedTasks(void); + /// Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section) cFluidSimulator * InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock); }; // tolua_export From 6914bf4c65425172f3535b3a81936d7f75bdd42c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 12 Aug 2013 07:55:53 +0200 Subject: [PATCH 07/21] Removed unused cServer::IsConnected() function. --- source/Server.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/Server.h b/source/Server.h index a00485fa2..176c82a40 100644 --- a/source/Server.h +++ b/source/Server.h @@ -46,11 +46,9 @@ public: // tolua_export int GetNumPlayers(void) const { return m_NumPlayers; } void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; } - // tolua_end + void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); - // bool IsConnected(void) const { return m_bIsConnected;} // returns connection status - - void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export + // tolua_end bool Start(void); From c628ab03e9aae0b8e56f260ad02cfc7d51285a71 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 12 Aug 2013 08:35:13 +0200 Subject: [PATCH 08/21] Removed cServer::BroadcastChat() and cServer::SendMessage(). These two functions make it difficult to move to the new ticking system, and they aren't used anyway. If so required, they can be emulated by ForEachWorld / ForEachPlayer calls. --- source/Bindings.cpp | 78 +---------------------------------------- source/Bindings.h | 2 +- source/ClientHandle.cpp | 14 ++++---- source/Player.cpp | 22 ++++++++---- source/Server.cpp | 46 ++++++------------------ source/Server.h | 11 +++--- source/World.cpp | 2 +- 7 files changed, 42 insertions(+), 133 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index 46c5f0f07..9490f733e 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 20:59:51. +** Generated automatically by tolua++-1.0.92 on 08/12/13 08:16:46. */ #ifndef __cplusplus @@ -11435,80 +11435,6 @@ static int tolua_AllToLua_cServer_SetMaxPlayers00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE -/* method: BroadcastChat of class cServer */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_BroadcastChat00 -static int tolua_AllToLua_cServer_BroadcastChat00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cServer",0,&tolua_err) || - !tolua_iscppstring(tolua_S,2,0,&tolua_err) || - !tolua_isusertype(tolua_S,3,"const cClientHandle",1,&tolua_err) || - !tolua_isnoobj(tolua_S,4,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cServer* self = (cServer*) tolua_tousertype(tolua_S,1,0); - const AString a_Message = ((const AString) tolua_tocppstring(tolua_S,2,0)); - const cClientHandle* a_Exclude = ((const cClientHandle*) tolua_tousertype(tolua_S,3,NULL)); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'BroadcastChat'", NULL); -#endif - { - self->BroadcastChat(a_Message,a_Exclude); - tolua_pushcppstring(tolua_S,(const char*)a_Message); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'BroadcastChat'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - -/* method: SendMessage of class cServer */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_SendMessage00 -static int tolua_AllToLua_cServer_SendMessage00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cServer",0,&tolua_err) || - !tolua_iscppstring(tolua_S,2,0,&tolua_err) || - !tolua_isusertype(tolua_S,3,"cPlayer",1,&tolua_err) || - !tolua_isboolean(tolua_S,4,1,&tolua_err) || - !tolua_isnoobj(tolua_S,5,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cServer* self = (cServer*) tolua_tousertype(tolua_S,1,0); - const AString a_Message = ((const AString) tolua_tocppstring(tolua_S,2,0)); - cPlayer* a_Player = ((cPlayer*) tolua_tousertype(tolua_S,3,NULL)); - bool a_bExclude = ((bool) tolua_toboolean(tolua_S,4,false)); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'SendMessage'", NULL); -#endif - { - self->SendMessage(a_Message,a_Player,a_bExclude); - tolua_pushcppstring(tolua_S,(const char*)a_Message); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'SendMessage'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - /* method: GetServerID of class cServer */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cServer_GetServerID00 static int tolua_AllToLua_cServer_GetServerID00(lua_State* tolua_S) @@ -29850,8 +29776,6 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"GetMaxPlayers",tolua_AllToLua_cServer_GetMaxPlayers00); tolua_function(tolua_S,"GetNumPlayers",tolua_AllToLua_cServer_GetNumPlayers00); tolua_function(tolua_S,"SetMaxPlayers",tolua_AllToLua_cServer_SetMaxPlayers00); - tolua_function(tolua_S,"BroadcastChat",tolua_AllToLua_cServer_BroadcastChat00); - tolua_function(tolua_S,"SendMessage",tolua_AllToLua_cServer_SendMessage00); tolua_function(tolua_S,"GetServerID",tolua_AllToLua_cServer_GetServerID00); tolua_endmodule(tolua_S); tolua_cclass(tolua_S,"cWorld","cWorld","",NULL); diff --git a/source/Bindings.h b/source/Bindings.h index 10f874e41..8bc484aa0 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/11/13 20:59:52. +** Generated automatically by tolua++-1.0.92 on 08/12/13 08:16:46. */ /* Exported function */ diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index 14b052652..54e191281 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -1355,12 +1355,14 @@ void cClientHandle::Tick(float a_Dt) m_ShouldCheckDownloaded = false; } + if (m_Player == NULL) + { + return; + } + // Send a ping packet: cTimer t1; - if ( - (m_Player != NULL) && // Is logged in? - (m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime()) - ) + if ((m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime())) { m_PingID++; m_PingStartTime = t1.GetNowTime(); @@ -1369,7 +1371,7 @@ void cClientHandle::Tick(float a_Dt) } // Handle block break animation: - if ((m_Player != NULL) && (m_BlockDigAnimStage > -1)) + if (m_BlockDigAnimStage > -1) { int lastAnimVal = m_BlockDigAnimStage; m_BlockDigAnimStage += (int)(m_BlockDigAnimSpeed * a_Dt); @@ -1955,7 +1957,7 @@ void cClientHandle::SendConfirmPosition(void) if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player)) { // Broadcast that this player has joined the game! Yay~ - cRoot::Get()->GetServer()->BroadcastChat(m_Username + " joined the game!", this); + m_Player->GetWorld()->BroadcastChat(m_Username + " joined the game!", this); } SendPlayerMoveLook(); diff --git a/source/Player.cpp b/source/Player.cpp index 03c871736..df7e1d539 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -129,6 +129,12 @@ bool cPlayer::Initialize(cWorld * a_World) { if (super::Initialize(a_World)) { + // Remove the client handle from the server, it will be ticked from this object from now on + if (m_ClientHandle != NULL) + { + cRoot::Get()->GetServer()->ClientMovedToWorld(m_ClientHandle); + } + GetWorld()->AddPlayer(this); return true; } @@ -175,18 +181,22 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) { - if (!m_ClientHandle->IsPlaying()) + if (m_ClientHandle != NULL) { - // We're not yet in the game, ignore everything - return; + if (!m_ClientHandle->IsPlaying()) + { + // We're not yet in the game, ignore everything + return; + } + m_ClientHandle->Tick(a_Dt); } super::Tick(a_Dt, a_Chunk); - // set player swimming state - SetSwimState( a_Chunk); + // Set player swimming state + SetSwimState(a_Chunk); - // handle air drowning stuff + // Handle air drowning stuff HandleAir(); if (m_bDirtyPosition) diff --git a/source/Server.cpp b/source/Server.cpp index 0045d4808..82a4cb9f5 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -162,6 +162,16 @@ void cServer::RemoveClient(const cClientHandle * a_Client) +void cServer::ClientMovedToWorld(const cClientHandle * a_Client) +{ + cCSLock Lock(m_CSClients); + m_Clients.remove(const_cast(a_Client)); +} + + + + + bool cServer::InitServer(cIniFile & a_SettingsIni) { m_Description = a_SettingsIni.GetValue ("Server", "Description", "MCServer! - In C++!").c_str(); @@ -298,23 +308,6 @@ void cServer::OnConnectionAccepted(cSocket & a_Socket) -void cServer::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude) -{ - cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - if ((*itr == a_Exclude) || !(*itr)->IsLoggedIn()) - { - continue; - } - (*itr)->SendChat(a_Message); - } -} - - - - - bool cServer::Tick(float a_Dt) { cRoot::Get()->TickCommands(); @@ -452,25 +445,6 @@ void cServer::BindBuiltInConsoleCommands(void) -void cServer::SendMessage(const AString & a_Message, cPlayer * a_Player /* = NULL */, bool a_bExclude /* = false */ ) -{ - if ((a_Player != NULL) && !a_bExclude) - { - cClientHandle * Client = a_Player->GetClientHandle(); - if (Client != NULL) - { - Client->SendChat(a_Message); - } - return; - } - - BroadcastChat(a_Message, (a_Player != NULL) ? a_Player->GetClientHandle() : NULL); -} - - - - - void cServer::Shutdown(void) { m_ListenThreadIPv4.Stop(); diff --git a/source/Server.h b/source/Server.h index 176c82a40..fad7fc12a 100644 --- a/source/Server.h +++ b/source/Server.h @@ -46,8 +46,6 @@ public: // tolua_export int GetNumPlayers(void) const { return m_NumPlayers; } void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; } - void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); - // tolua_end bool Start(void); @@ -62,8 +60,6 @@ public: // tolua_export void Shutdown(void); - void SendMessage(const AString & a_Message, cPlayer * a_Player = NULL, bool a_bExclude = false ); // tolua_export - void KickUser(int a_ClientID, const AString & a_Reason); void AuthenticateUser(int a_ClientID); // Called by cAuthenticator to auth the specified user @@ -79,6 +75,9 @@ public: // tolua_export void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads + /// Don't tick a_Client anymore, it will be ticked from its cPlayer instead + void ClientMovedToWorld(const cClientHandle * a_Client); + CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; } CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; } @@ -132,8 +131,8 @@ private: cListenThread m_ListenThreadIPv4; cListenThread m_ListenThreadIPv6; - cCriticalSection m_CSClients; // Locks client list - cClientHandleList m_Clients; // Clients that are connected to the server + cCriticalSection m_CSClients; ///< Locks client list + cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld cSocketThreads m_SocketThreads; diff --git a/source/World.cpp b/source/World.cpp index 341682a2a..b29bc751f 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -578,7 +578,7 @@ void cWorld::Tick(float a_Dt) m_WorldAge = (Int64)(m_WorldAgeSecs * 20.0); m_TimeOfDay = (Int64)(m_TimeOfDaySecs * 20.0); - // Broadcase time update every 40 ticks (2 seconds) + // Broadcast time update every 40 ticks (2 seconds) if (m_LastTimeUpdate < m_WorldAge - 40) { BroadcastTimeUpdate(); From 27d0c9aef2d25b4d1f81f5b361f3f1f0ab9efc27 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Mon, 12 Aug 2013 08:42:18 +0200 Subject: [PATCH 09/21] Fixed logging into debug console. Was missing the LF at the end. --- source/Log.cpp | 2 +- source/MCLogger.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Log.cpp b/source/Log.cpp index 2b505de5d..c8937c380 100644 --- a/source/Log.cpp +++ b/source/Log.cpp @@ -138,7 +138,7 @@ void cLog::Log(const char * a_Format, va_list argList) #if defined (_WIN32) && defined(_DEBUG) // In a Windows Debug build, output the log to debug console as well: - OutputDebugStringA(Line.c_str()); + OutputDebugStringA((Line + "\n").c_str()); #endif // _WIN32 } diff --git a/source/MCLogger.cpp b/source/MCLogger.cpp index de0fcae2e..2870d8ba1 100644 --- a/source/MCLogger.cpp +++ b/source/MCLogger.cpp @@ -75,7 +75,9 @@ cMCLogger::~cMCLogger() m_Log->Log("--- Stopped Log ---\n"); delete m_Log; if (this == s_MCLogger) + { s_MCLogger = NULL; + } } From 9020dc993241a4a90e8e98b3435d9b2576f313ea Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 13 Aug 2013 22:45:29 +0200 Subject: [PATCH 10/21] Clients are now ticked in cServer first, then in cWorld once they get assigned a world. --- source/Bindings.cpp | 86 +---------------------------------------- source/Bindings.h | 2 +- source/ClientHandle.cpp | 63 +++++++++++++++--------------- source/ClientHandle.h | 25 +++++++----- source/Player.cpp | 34 +++++++++------- source/Player.h | 2 +- source/Server.cpp | 62 +++++++++++++++++++---------- source/Server.h | 8 +++- source/World.cpp | 66 +++++++++++++++++++++++++++---- source/World.h | 16 +++++++- 10 files changed, 189 insertions(+), 175 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index 9490f733e..0424d5f41 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/12/13 08:16:46. +** Generated automatically by tolua++-1.0.92 on 08/12/13 21:48:05. */ #ifndef __cplusplus @@ -8237,40 +8237,6 @@ static int tolua_AllToLua_Lua__cEntity_cEntity__IsRclking00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE -/* method: Initialize of class cPlayer */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_cPlayer_Initialize00 -static int tolua_AllToLua_cPlayer_Initialize00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"cPlayer",0,&tolua_err) || - !tolua_isusertype(tolua_S,2,"cWorld",0,&tolua_err) || - !tolua_isnoobj(tolua_S,3,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - cPlayer* self = (cPlayer*) tolua_tousertype(tolua_S,1,0); - cWorld* a_World = ((cWorld*) tolua_tousertype(tolua_S,2,0)); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'Initialize'", NULL); -#endif - { - bool tolua_ret = (bool) self->Initialize(a_World); - tolua_pushboolean(tolua_S,(bool)tolua_ret); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'Initialize'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - /* method: GetEyeHeight of class cPlayer */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cPlayer_GetEyeHeight00 static int tolua_AllToLua_cPlayer_GetEyeHeight00(lua_State* tolua_S) @@ -10186,17 +10152,6 @@ static int tolua_AllToLua_cPlayer_IsSubmerged00(lua_State* tolua_S) class Lua__cPlayer : public cPlayer, public ToluaBase { public: - bool Initialize( cWorld* a_World) { - if (push_method("Initialize", tolua_AllToLua_cPlayer_Initialize00)) { - tolua_pushusertype(lua_state, (void*)a_World, "cWorld"); - ToluaBase::dbcall(lua_state, 2, 1); - bool tolua_ret = ( bool )tolua_toboolean(lua_state, -1, 0); - lua_pop(lua_state, 1); - return tolua_ret; - } else { - return ( bool ) cPlayer:: Initialize(a_World); - }; - }; void MoveTo( const Vector3d& a_NewPos) { if (push_method("MoveTo", tolua_AllToLua_cPlayer_MoveTo00)) { tolua_pushusertype(lua_state, (void*)&a_NewPos, "const Vector3d"); @@ -10418,9 +10373,6 @@ public: }; }; - bool cPlayer__Initialize( cWorld* a_World) { - return ( bool )cPlayer::Initialize(a_World); - }; void cPlayer__MoveTo( const Vector3d& a_NewPos) { return ( void )cPlayer::MoveTo(a_NewPos); }; @@ -10522,40 +10474,6 @@ static int tolua_AllToLua_Lua__cPlayer_tolua__set_instance00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE -/* method: cPlayer__Initialize of class Lua__cPlayer */ -#ifndef TOLUA_DISABLE_tolua_AllToLua_Lua__cPlayer_cPlayer__Initialize00 -static int tolua_AllToLua_Lua__cPlayer_cPlayer__Initialize00(lua_State* tolua_S) -{ -#ifndef TOLUA_RELEASE - tolua_Error tolua_err; - if ( - !tolua_isusertype(tolua_S,1,"Lua__cPlayer",0,&tolua_err) || - !tolua_isusertype(tolua_S,2,"cWorld",0,&tolua_err) || - !tolua_isnoobj(tolua_S,3,&tolua_err) - ) - goto tolua_lerror; - else -#endif - { - Lua__cPlayer* self = (Lua__cPlayer*) tolua_tousertype(tolua_S,1,0); - cWorld* a_World = ((cWorld*) tolua_tousertype(tolua_S,2,0)); -#ifndef TOLUA_RELEASE - if (!self) tolua_error(tolua_S,"invalid 'self' in function 'cPlayer__Initialize'", NULL); -#endif - { - bool tolua_ret = (bool) self->cPlayer__Initialize(a_World); - tolua_pushboolean(tolua_S,(bool)tolua_ret); - } - } - return 1; -#ifndef TOLUA_RELEASE - tolua_lerror: - tolua_error(tolua_S,"#ferror in function 'cPlayer__Initialize'.",&tolua_err); - return 0; -#endif -} -#endif //#ifndef TOLUA_DISABLE - /* method: cPlayer__MoveTo of class Lua__cPlayer */ #ifndef TOLUA_DISABLE_tolua_AllToLua_Lua__cPlayer_cPlayer__MoveTo00 static int tolua_AllToLua_Lua__cPlayer_cPlayer__MoveTo00(lua_State* tolua_S) @@ -29625,7 +29543,6 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_constant(tolua_S,"EATING_TICKS",cPlayer::EATING_TICKS); tolua_constant(tolua_S,"MAX_AIR_LEVEL",cPlayer::MAX_AIR_LEVEL); tolua_constant(tolua_S,"DROWNING_TICKS",cPlayer::DROWNING_TICKS); - tolua_function(tolua_S,"Initialize",tolua_AllToLua_cPlayer_Initialize00); tolua_function(tolua_S,"GetEyeHeight",tolua_AllToLua_cPlayer_GetEyeHeight00); tolua_function(tolua_S,"GetEyePosition",tolua_AllToLua_cPlayer_GetEyePosition00); tolua_function(tolua_S,"IsOnGround",tolua_AllToLua_cPlayer_IsOnGround00); @@ -29688,7 +29605,6 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_cclass(tolua_S,"Lua__cPlayer","Lua__cPlayer","cPlayer",NULL); tolua_beginmodule(tolua_S,"Lua__cPlayer"); tolua_function(tolua_S,"tolua__set_instance",tolua_AllToLua_Lua__cPlayer_tolua__set_instance00); - tolua_function(tolua_S,"cPlayer__Initialize",tolua_AllToLua_Lua__cPlayer_cPlayer__Initialize00); tolua_function(tolua_S,"cPlayer__MoveTo",tolua_AllToLua_Lua__cPlayer_cPlayer__MoveTo00); tolua_function(tolua_S,"cPlayer__IsSwimming",tolua_AllToLua_Lua__cPlayer_cPlayer__IsSwimming00); tolua_function(tolua_S,"cPlayer__IsSubmerged",tolua_AllToLua_Lua__cPlayer_cPlayer__IsSubmerged00); diff --git a/source/Bindings.h b/source/Bindings.h index 8bc484aa0..90043e826 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/12/13 08:16:46. +** Generated automatically by tolua++-1.0.92 on 08/12/13 21:48:06. */ /* Exported function */ diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index 54e191281..fe78ef2b6 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -170,13 +170,21 @@ cClientHandle::~cClientHandle() -void cClientHandle::Destroy() +void cClientHandle::Destroy(void) { - // Setting m_bDestroyed was moved to the bottom of Destroy(), - // otherwise the destructor may be called within another thread before the client is removed from chunks - // http://forum.mc-server.org/showthread.php?tid=366 + { + cCSLock Lock(m_CSDestroyingState); + if (m_State >= csDestroying) + { + // Already called + return; + } + m_State = csDestroying; + } + + // DEBUG: + LOGD("%s: client %p, \"%s\"", __FUNCTION__, this, m_Username.c_str()); - m_State = csDestroying; if ((m_Player != NULL) && (m_Player->GetWorld() != NULL)) { RemoveFromAllChunks(); @@ -253,9 +261,8 @@ void cClientHandle::Authenticate(void) SendGameMode(m_Player->GetGameMode()); m_Player->Initialize(World); - StreamChunks(); - m_State = csDownloadingWorld; - + m_State = csAuthenticated; + // Broadcast this player's spawning to all other players in the same chunk m_Player->GetWorld()->BroadcastSpawnEntity(*m_Player, this); @@ -1342,6 +1349,20 @@ bool cClientHandle::CheckBlockInteractionsRate(void) void cClientHandle::Tick(float a_Dt) { + // Process received network data: + AString IncomingData; + { + cCSLock Lock(m_CSIncomingData); + std::swap(IncomingData, m_IncomingData); + } + m_Protocol->DataReceived(IncomingData.data(), IncomingData.size()); + + if (m_State == csAuthenticated) + { + StreamChunks(); + m_State = csDownloadingWorld; + } + m_TimeSinceLastPacket += a_Dt; if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out { @@ -2118,30 +2139,10 @@ void cClientHandle::PacketError(unsigned char a_PacketType) void cClientHandle::DataReceived(const char * a_Data, int a_Size) { - // Data is received from the client, hand it off to the protocol: - if ((m_Player != NULL) && (m_Player->GetWorld() != NULL)) - { - /* - _X: Lock the world, so that plugins reacting to protocol events have already the chunkmap locked. - There was a possibility of a deadlock between SocketThreads and TickThreads, resulting from each - holding one CS an requesting the other one (ChunkMap CS vs Plugin CS) (FS #375). To break this, it's - sufficient to break any of the four Coffman conditions for a deadlock. We'll solve this by requiring - the ChunkMap CS for all SocketThreads operations before they lock the PluginCS - thus creating a kind - of a lock hierarchy. However, this incurs a performance penalty, we're de facto locking the chunkmap - for each incoming packet. A better, but more involved solutin would be to lock the chunkmap only when - the incoming packet really has a plugin CS lock request. - Also, it is still possible for a packet to slip through - when a player still doesn't have their world - assigned and several packets arrive at once. - */ - cWorld::cLock(*m_Player->GetWorld()); - - m_Protocol->DataReceived(a_Data, a_Size); - } - else - { - m_Protocol->DataReceived(a_Data, a_Size); - } + // Data is received from the client, store it in the buffer to be processed by the Tick thread: m_TimeSinceLastPacket = 0; + cCSLock Lock(m_CSIncomingData); + m_IncomingData.append(a_Data, a_Size); } diff --git a/source/ClientHandle.h b/source/ClientHandle.h index 73edaf73c..1f40cc8d2 100644 --- a/source/ClientHandle.h +++ b/source/ClientHandle.h @@ -212,11 +212,12 @@ private: cProtocol * m_Protocol; + cCriticalSection m_CSIncomingData; + AString m_IncomingData; + cCriticalSection m_CSOutgoingData; cByteBuffer m_OutgoingData; - AString m_OutgoingDataOverflow; //< For data that didn't fit into the m_OutgoingData ringbuffer temporarily - - cCriticalSection m_CriticalSection; + AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily Vector3d m_ConfirmPosition; @@ -252,18 +253,22 @@ private: enum eState { - csConnected, // The client has just connected, waiting for their handshake / login - csAuthenticating, // The client has logged in, waiting for external authentication - csDownloadingWorld, // The client is waiting for chunks, we're waiting for the loader to provide and send them - csConfirmingPos, // The client has been sent the position packet, waiting for them to repeat the position back - csPlaying, // Normal gameplay - csDestroying, // The client is being destroyed, don't queue any more packets / don't add to chunks - csDestroyed, // The client has been destroyed, the destructor is to be called from the owner thread + csConnected, ///< The client has just connected, waiting for their handshake / login + csAuthenticating, ///< The client has logged in, waiting for external authentication + csAuthenticated, ///< The client has been authenticated, will start streaming chunks in the next tick + csDownloadingWorld, ///< The client is waiting for chunks, we're waiting for the loader to provide and send them + csConfirmingPos, ///< The client has been sent the position packet, waiting for them to repeat the position back + csPlaying, ///< Normal gameplay + csDestroying, ///< The client is being destroyed, don't queue any more packets / don't add to chunks + csDestroyed, ///< The client has been destroyed, the destructor is to be called from the owner thread // TODO: Add Kicking here as well } ; eState m_State; + + /// m_State needs to be locked in the Destroy() function so that the destruction code doesn't run twice on two different threads + cCriticalSection m_CSDestroyingState; bool m_bKeepThreadGoing; diff --git a/source/Player.cpp b/source/Player.cpp index df7e1d539..9ca619cf7 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -127,6 +127,8 @@ cPlayer::~cPlayer(void) bool cPlayer::Initialize(cWorld * a_World) { + ASSERT(a_World != NULL); + if (super::Initialize(a_World)) { // Remove the client handle from the server, it will be ticked from this object from now on @@ -148,6 +150,7 @@ bool cPlayer::Initialize(cWorld * a_World) void cPlayer::Destroyed() { CloseWindow(false); + m_ClientHandle = NULL; } @@ -157,22 +160,17 @@ void cPlayer::Destroyed() void cPlayer::SpawnOn(cClientHandle & a_Client) { - /* - LOGD("cPlayer::SpawnOn(%s) for \"%s\" at pos {%.2f, %.2f, %.2f}", - a_Client.GetUsername().c_str(), m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z - ); - */ - - if (m_bVisible && (m_ClientHandle != (&a_Client))) + if (!m_bVisible || (m_ClientHandle == (&a_Client))) { - a_Client.SendPlayerSpawn(*this); - a_Client.SendEntityHeadLook(*this); - a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem() ); - a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots() ); - a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings() ); - a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate() ); - a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet() ); + return; } + a_Client.SendPlayerSpawn(*this); + a_Client.SendEntityHeadLook(*this); + a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem() ); + a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots() ); + a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings() ); + a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate() ); + a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet() ); } @@ -183,12 +181,18 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) { if (m_ClientHandle != NULL) { + if (m_ClientHandle->IsDestroyed()) + { + // This should not happen, because destroying a client will remove it from the world, but just in case + m_ClientHandle = NULL; + return; + } + if (!m_ClientHandle->IsPlaying()) { // We're not yet in the game, ignore everything return; } - m_ClientHandle->Tick(a_Dt); } super::Tick(a_Dt, a_Chunk); diff --git a/source/Player.h b/source/Player.h index 105e7d0a1..2a1797c79 100644 --- a/source/Player.h +++ b/source/Player.h @@ -40,7 +40,7 @@ public: cPlayer(cClientHandle * a_Client, const AString & a_PlayerName); virtual ~cPlayer(); - virtual bool Initialize(cWorld * a_World); // tolua_export + virtual bool Initialize(cWorld * a_World) override; virtual void SpawnOn(cClientHandle & a_Client) override; diff --git a/source/Server.cpp b/source/Server.cpp index 82a4cb9f5..31a1925a8 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -165,7 +165,7 @@ void cServer::RemoveClient(const cClientHandle * a_Client) void cServer::ClientMovedToWorld(const cClientHandle * a_Client) { cCSLock Lock(m_CSClients); - m_Clients.remove(const_cast(a_Client)); + m_ClientsToRemove.push_back(const_cast(a_Client)); } @@ -312,25 +312,7 @@ bool cServer::Tick(float a_Dt) { cRoot::Get()->TickCommands(); - cClientHandleList RemoveClients; - { - cCSLock Lock(m_CSClients); - for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) - { - if ((*itr)->IsDestroyed()) - { - RemoveClients.push_back(*itr); // Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 ) - itr = m_Clients.erase(itr); - continue; - } - (*itr)->Tick(a_Dt); - ++itr; - } // for itr - m_Clients[] - } - for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) - { - delete *itr; - } // for itr - RemoveClients[] + TickClients(a_Dt); if (!m_bRestarting) { @@ -348,6 +330,45 @@ bool cServer::Tick(float a_Dt) +void cServer::TickClients(float a_Dt) +{ + cClientHandleList RemoveClients; + { + cCSLock Lock(m_CSClients); + + // Remove clients that have moved to a world (the world will be ticking them from now on) + for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) + { + m_Clients.remove(*itr); + } // for itr - m_ClientsToRemove[] + m_ClientsToRemove.clear(); + + // Tick the remaining clients, take out those that have been destroyed into RemoveClients + for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) + { + if ((*itr)->IsDestroyed()) + { + // Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 ) + RemoveClients.push_back(*itr); + itr = m_Clients.erase(itr); + continue; + } + (*itr)->Tick(a_Dt); + ++itr; + } // for itr - m_Clients[] + } + + // Delete the clients that have been destroyed + for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) + { + delete *itr; + } // for itr - RemoveClients[] +} + + + + + bool cServer::Start(void) { if (!m_ListenThreadIPv4.Start()) @@ -492,6 +513,7 @@ void cServer::AuthenticateUser(int a_ClientID) if ((*itr)->GetUniqueID() == a_ClientID) { (*itr)->Authenticate(); + return; } } // for itr - m_Clients[] } diff --git a/source/Server.h b/source/Server.h index fad7fc12a..6f8576ece 100644 --- a/source/Server.h +++ b/source/Server.h @@ -131,8 +131,9 @@ private: cListenThread m_ListenThreadIPv4; cListenThread m_ListenThreadIPv6; - cCriticalSection m_CSClients; ///< Locks client list - cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld + cCriticalSection m_CSClients; ///< Locks client lists + cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld + cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick() cSocketThreads m_SocketThreads; @@ -164,6 +165,9 @@ private: void PrepareKeys(void); bool Tick(float a_Dt); + + /// Ticks the clients in m_Clients, manages the list in respect to removing clients + void TickClients(float a_Dt); // cListenThread::cCallback overrides: virtual void OnConnectionAccepted(cSocket & a_Socket) override; diff --git a/source/World.cpp b/source/World.cpp index b29bc751f..7a1ecb82e 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -587,6 +587,7 @@ void cWorld::Tick(float a_Dt) m_ChunkMap->Tick(a_Dt); + TickClients(a_Dt); TickQueuedBlocks(a_Dt); TickQueuedTasks(); @@ -811,6 +812,37 @@ void cWorld::TickQueuedTasks(void) +void cWorld::TickClients(float a_Dt) +{ + cClientHandleList RemoveClients; + { + cCSLock Lock(m_CSClients); + // Tick the clients, take out those that have been destroyed into RemoveClients + for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) + { + if ((*itr)->IsDestroyed()) + { + // Remove the client later, when CS is not held, to avoid deadlock + RemoveClients.push_back(*itr); + itr = m_Clients.erase(itr); + continue; + } + (*itr)->Tick(a_Dt); + ++itr; + } // for itr - m_Clients[] + } + + // Delete the clients that have been destroyed + for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) + { + delete *itr; + } // for itr - RemoveClients[] +} + + + + + void cWorld::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) { return m_ChunkMap->WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ); @@ -1973,13 +2005,22 @@ void cWorld::CollectPickupsByPlayer(cPlayer * a_Player) void cWorld::AddPlayer(cPlayer * a_Player) { - cCSLock Lock(m_CSPlayers); - - ASSERT(std::find(m_Players.begin(), m_Players.end(), a_Player) == m_Players.end()); // Is it already in the list? HOW? - - m_Players.remove(a_Player); // Make sure the player is registered only once - m_Players.push_back(a_Player); + { + cCSLock Lock(m_CSPlayers); + + ASSERT(std::find(m_Players.begin(), m_Players.end(), a_Player) == m_Players.end()); // Is it already in the list? HOW? + + m_Players.remove(a_Player); // Make sure the player is registered only once + m_Players.push_back(a_Player); + } + // Add the player's client to the list of clients to be ticked: + if (a_Player->GetClientHandle() != NULL) + { + cCSLock Lock(m_CSClients); + m_Clients.push_back(a_Player->GetClientHandle()); + } + // The player has already been added to the chunkmap as the entity, do NOT add again! } @@ -1990,8 +2031,17 @@ void cWorld::AddPlayer(cPlayer * a_Player) void cWorld::RemovePlayer(cPlayer * a_Player) { m_ChunkMap->RemoveEntity(a_Player); - cCSLock Lock(m_CSPlayers); - m_Players.remove(a_Player); + { + cCSLock Lock(m_CSPlayers); + m_Players.remove(a_Player); + } + + // Remove the player's client from the list of clients to be ticked: + if (a_Player->GetClientHandle() != NULL) + { + cCSLock Lock(m_CSClients); + m_Clients.remove(a_Player->GetClientHandle()); + } } diff --git a/source/World.h b/source/World.h index d84920470..da59b12f6 100644 --- a/source/World.h +++ b/source/World.h @@ -660,6 +660,12 @@ private: /// Tasks that have been queued onto the tick thread; guarded by m_CSTasks cTasks m_Tasks; + + /// Guards m_Clients + cCriticalSection m_CSClients; + + /// List of clients in this world, these will be ticked by this world + cClientHandleList m_Clients; cWorld(const AString & a_WorldName); @@ -667,12 +673,18 @@ private: void Tick(float a_Dt); - void TickWeather(float a_Dt); // Handles weather each tick - void TickSpawnMobs(float a_Dt); // Handles mob spawning each tick + /// Handles the weather in each tick + void TickWeather(float a_Dt); + + /// Handles the mob spawning each tick + void TickSpawnMobs(float a_Dt); /// Executes all tasks queued onto the tick thread void TickQueuedTasks(void); + /// Ticks all clients that are in this world + void TickClients(float a_Dt); + /// Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section) cFluidSimulator * InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock); }; // tolua_export From 7b190f5044d019e6f6c849aac325517d54372b51 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 13 Aug 2013 23:06:23 +0200 Subject: [PATCH 11/21] Updated the Core Lua files in MCServer project externals. --- VC2008/MCServer.vcproj | 72 ++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index b39cf2769..4d850626d 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -2449,7 +2449,15 @@ Name="Core" > + + + + + + + + + + + + + + - - + + @@ -2580,6 +2608,10 @@ RelativePath="..\MCServer\Plugins\Core\web_whitelist.lua" > + + Date: Tue, 13 Aug 2013 23:10:59 +0200 Subject: [PATCH 12/21] Exported cWorld:BroadcastChat() to the Lua API; used in the Core. --- MCServer/Plugins/Core/onjoinleave.lua | 37 +++++++++++++------------ source/Bindings.cpp | 39 ++++++++++++++++++++++++++- source/Bindings.h | 2 +- source/World.h | 4 +-- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/MCServer/Plugins/Core/onjoinleave.lua b/MCServer/Plugins/Core/onjoinleave.lua index c794aaf94..cd0ead4fc 100644 --- a/MCServer/Plugins/Core/onjoinleave.lua +++ b/MCServer/Plugins/Core/onjoinleave.lua @@ -1,24 +1,23 @@ + + + + + function OnPlayerJoined(Player) - --if( BannedPlayersIni:GetValueB("Banned", Player:GetName(), false) == true ) then - -- LOGINFO( Player:GetName() .. " tried to join, but is banned!" ) - -- KickPlayer(Player:GetName(), cChatColor.Red .. "You are banned!" ) - -- return true - --elseif( WhiteListIni:GetValueB("WhiteListSettings", "WhiteListOn", false ) == true ) then - -- if( WhiteListIni:GetValueB("WhiteList", Player:GetName(), false ) == false ) then - -- LOGINFO( Player:GetName() .. " tried to join, but is not whitelisted!" ) - -- KickPlayer(Player:GetName(), cChatColor.Red .. "You are not whitelisted!" ) - -- end - --else - ShowMOTDTo( Player ) - local Server = cRoot:Get():GetServer() - Server:SendMessage(cChatColor.Yellow .. "[JOIN] " .. cChatColor.White .. Player:GetName() .. " has joined the game" ) - return false - --end + ShowMOTDTo(Player) + Player:GetWorld():BroadcastChat(cChatColor.Yellow .. "[JOIN] " .. cChatColor.White .. Player:GetName() .. " has joined the game") + return false end + + + + function OnDisconnect(Player, Reason) - local Server = cRoot:Get():GetServer() - Server:SendMessage(cChatColor.Yellow .. "[LEAVE] " .. cChatColor.White .. Player:GetName() .. " has left the game" ) - LOG("Player " .. Player:GetName() .. " has left the game.") + Player:GetWorld():BroadcastChat(cChatColor.Yellow .. "[LEAVE] " .. cChatColor.White .. Player:GetName() .. " has left the game") return true -end \ No newline at end of file +end + + + + diff --git a/source/Bindings.cpp b/source/Bindings.cpp index e7a71ce76..8feb070e1 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/13/13 23:00:22. +** Generated automatically by tolua++-1.0.92 on 08/13/13 23:07:52. */ #ifndef __cplusplus @@ -11896,6 +11896,42 @@ static int tolua_AllToLua_cWorld_GetHeight00(lua_State* tolua_S) } #endif //#ifndef TOLUA_DISABLE +/* method: BroadcastChat of class cWorld */ +#ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_BroadcastChat00 +static int tolua_AllToLua_cWorld_BroadcastChat00(lua_State* tolua_S) +{ +#ifndef TOLUA_RELEASE + tolua_Error tolua_err; + if ( + !tolua_isusertype(tolua_S,1,"cWorld",0,&tolua_err) || + !tolua_iscppstring(tolua_S,2,0,&tolua_err) || + !tolua_isusertype(tolua_S,3,"const cClientHandle",1,&tolua_err) || + !tolua_isnoobj(tolua_S,4,&tolua_err) + ) + goto tolua_lerror; + else +#endif + { + cWorld* self = (cWorld*) tolua_tousertype(tolua_S,1,0); + const AString a_Message = ((const AString) tolua_tocppstring(tolua_S,2,0)); + const cClientHandle* a_Exclude = ((const cClientHandle*) tolua_tousertype(tolua_S,3,NULL)); +#ifndef TOLUA_RELEASE + if (!self) tolua_error(tolua_S,"invalid 'self' in function 'BroadcastChat'", NULL); +#endif + { + self->BroadcastChat(a_Message,a_Exclude); + tolua_pushcppstring(tolua_S,(const char*)a_Message); + } + } + return 1; +#ifndef TOLUA_RELEASE + tolua_lerror: + tolua_error(tolua_S,"#ferror in function 'BroadcastChat'.",&tolua_err); + return 0; +#endif +} +#endif //#ifndef TOLUA_DISABLE + /* method: UnloadUnusedChunks of class cWorld */ #ifndef TOLUA_DISABLE_tolua_AllToLua_cWorld_UnloadUnusedChunks00 static int tolua_AllToLua_cWorld_UnloadUnusedChunks00(lua_State* tolua_S) @@ -29713,6 +29749,7 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_function(tolua_S,"IsDeepSnowEnabled",tolua_AllToLua_cWorld_IsDeepSnowEnabled00); tolua_function(tolua_S,"GetDimension",tolua_AllToLua_cWorld_GetDimension00); tolua_function(tolua_S,"GetHeight",tolua_AllToLua_cWorld_GetHeight00); + tolua_function(tolua_S,"BroadcastChat",tolua_AllToLua_cWorld_BroadcastChat00); tolua_function(tolua_S,"UnloadUnusedChunks",tolua_AllToLua_cWorld_UnloadUnusedChunks00); tolua_function(tolua_S,"RegenerateChunk",tolua_AllToLua_cWorld_RegenerateChunk00); tolua_function(tolua_S,"GenerateChunk",tolua_AllToLua_cWorld_GenerateChunk00); diff --git a/source/Bindings.h b/source/Bindings.h index 1e5ad7e84..a8d39e32f 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/13/13 23:00:22. +** Generated automatically by tolua++-1.0.92 on 08/13/13 23:07:53. */ /* Exported function */ diff --git a/source/World.h b/source/World.h index da59b12f6..2c04c4cc6 100644 --- a/source/World.h +++ b/source/World.h @@ -154,8 +154,8 @@ public: void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle); void BroadcastBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude = NULL); void BroadcastBlockBreakAnimation(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude = NULL); - void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = NULL); ///< If there is a block entity at the specified coods, sends it to all clients except a_Exclude - void BroadcastChat (const AString & a_Message, const cClientHandle * a_Exclude = NULL); + void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = NULL); ///< If there is a block entity at the specified coods, sends it to all clients except a_Exclude + void BroadcastChat (const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export void BroadcastChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer, const cClientHandle * a_Exclude = NULL); void BroadcastCollectPickup (const cPickup & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = NULL); void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = NULL); From 8c3837987bd5f74563790c15a1d52755383135ae Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 10:24:34 +0200 Subject: [PATCH 13/21] Player counts are now properly handled. Fixes #80 --- source/Bindings.cpp | 6 +++--- source/Bindings.h | 2 +- source/ClientHandle.cpp | 38 +++++++++++++++++++++++++++++++++- source/ClientHandle.h | 4 ++++ source/Player.cpp | 15 ++++++-------- source/Server.cpp | 45 +++++++++++++++++++++++++++++++++++++++++ source/Server.h | 14 +++++++++++-- 7 files changed, 108 insertions(+), 16 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index 8feb070e1..a0ad85156 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/13/13 23:07:52. +** Generated automatically by tolua++-1.0.92 on 08/14/13 08:14:03. */ #ifndef __cplusplus @@ -11295,14 +11295,14 @@ static int tolua_AllToLua_cServer_GetNumPlayers00(lua_State* tolua_S) #ifndef TOLUA_RELEASE tolua_Error tolua_err; if ( - !tolua_isusertype(tolua_S,1,"const cServer",0,&tolua_err) || + !tolua_isusertype(tolua_S,1,"cServer",0,&tolua_err) || !tolua_isnoobj(tolua_S,2,&tolua_err) ) goto tolua_lerror; else #endif { - const cServer* self = (const cServer*) tolua_tousertype(tolua_S,1,0); + cServer* self = (cServer*) tolua_tousertype(tolua_S,1,0); #ifndef TOLUA_RELEASE if (!self) tolua_error(tolua_S,"invalid 'self' in function 'GetNumPlayers'", NULL); #endif diff --git a/source/Bindings.h b/source/Bindings.h index a8d39e32f..0211e0eeb 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/13/13 23:07:53. +** Generated automatically by tolua++-1.0.92 on 08/14/13 08:14:04. */ /* Exported function */ diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index fe78ef2b6..32bfb00f5 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -275,7 +275,7 @@ void cClientHandle::Authenticate(void) void cClientHandle::StreamChunks(void) { - if ((m_State < csAuthenticating) || (m_State >= csDestroying)) + if ((m_State < csAuthenticated) || (m_State >= csDestroying)) { return; } @@ -1314,6 +1314,42 @@ void cClientHandle::SendData(const char * a_Data, int a_Size) +void cClientHandle::MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket) +{ + ASSERT(m_Player != NULL); + + if (a_SendRespawnPacket) + { + SendRespawn(); + } + + cWorld * World = m_Player->GetWorld(); + + // Remove all associated chunks: + cChunkCoordsList Chunks; + { + cCSLock Lock(m_CSChunkLists); + std::swap(Chunks, m_LoadedChunks); + m_ChunksToSend.clear(); + } + for (cChunkCoordsList::iterator itr = Chunks.begin(), end = Chunks.end(); itr != end; ++itr) + { + World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this); + m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ); + } // for itr - Chunks[] + + // Do NOT stream new chunks, the new world runs its own tick thread and may deadlock + // Instead, the chunks will be streamed when the client is moved to the new world's Tick list, + // by setting state to csAuthenticated + m_State = csAuthenticated; + m_LastStreamedChunkX = 0x7fffffff; + m_LastStreamedChunkZ = 0x7fffffff; +} + + + + + bool cClientHandle::CheckBlockInteractionsRate(void) { ASSERT(m_Player != NULL); diff --git a/source/ClientHandle.h b/source/ClientHandle.h index 1f40cc8d2..4dcb188b3 100644 --- a/source/ClientHandle.h +++ b/source/ClientHandle.h @@ -32,6 +32,7 @@ class cRedstone; class cWindow; class cFallingBlock; class cItemHandler; +class cWorld; @@ -194,6 +195,9 @@ public: void SendData(const char * a_Data, int a_Size); + /// Called when the player moves into a different world; queues sreaming the new chunks + void MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket); + private: int m_ViewDistance; // Number of chunks the player can see in each direction; 4 is the minimum ( http://wiki.vg/Protocol_FAQ#.E2.80.A6all_connecting_clients_spasm_and_jerk_uncontrollably.21 ) diff --git a/source/Player.cpp b/source/Player.cpp index 365a0396f..34980d2f6 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -100,6 +100,8 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) m_LastJumpHeight = (float)(GetPosY()); m_LastGroundHeight = (float)(GetPosY()); m_Stance = GetPosY() + 1.62; + + cRoot::Get()->GetServer()->PlayerCreated(this); } @@ -1120,20 +1122,15 @@ bool cPlayer::MoveToWorld(const char * a_WorldName) m_ClientHandle->RemoveFromAllChunks(); m_World->RemoveEntity(this); + // If the dimension is different, we can send the respawn packet + // http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02 + m_ClientHandle->MoveToWorld(*World, (OldDimension != World->GetDimension())); + // Add player to all the necessary parts of the new world SetWorld(World); World->AddEntity(this); World->AddPlayer(this); - // If the dimension is different, we can send the respawn packet - // http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02 - if (OldDimension != World->GetDimension()) - { - m_ClientHandle->SendRespawn(); - } - - // Stream the new chunks: - m_ClientHandle->StreamChunks(); return true; } diff --git a/source/Server.cpp b/source/Server.cpp index 31a1925a8..c01222e5a 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -172,10 +172,34 @@ void cServer::ClientMovedToWorld(const cClientHandle * a_Client) +void cServer::PlayerCreated(const cPlayer * a_Player) +{ + // To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread + cCSLock Lock(m_CSPlayerCountDiff); + m_PlayerCountDiff += 1; +} + + + + + +void cServer::PlayerDestroyed(const cPlayer * a_Player) +{ + // To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread + cCSLock Lock(m_CSPlayerCountDiff); + m_PlayerCountDiff -= 1; +} + + + + + bool cServer::InitServer(cIniFile & a_SettingsIni) { m_Description = a_SettingsIni.GetValue ("Server", "Description", "MCServer! - In C++!").c_str(); m_MaxPlayers = a_SettingsIni.GetValueI("Server", "MaxPlayers", 100); + m_PlayerCount = 0; + m_PlayerCountDiff = 0; if (m_bIsConnected) { @@ -254,6 +278,16 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) +int cServer::GetNumPlayers(void) +{ + cCSLock Lock(m_CSPlayerCount); + return m_PlayerCount; +} + + + + + void cServer::PrepareKeys(void) { // TODO: Save and load key for persistence across sessions @@ -310,6 +344,17 @@ void cServer::OnConnectionAccepted(cSocket & a_Socket) bool cServer::Tick(float a_Dt) { + // Apply the queued playercount adjustments (postponed to avoid deadlocks) + int PlayerCountDiff = 0; + { + cCSLock Lock(m_CSPlayerCountDiff); + std::swap(PlayerCountDiff, m_PlayerCountDiff); + } + { + cCSLock Lock(m_CSPlayerCount); + m_PlayerCount += PlayerCountDiff; + } + cRoot::Get()->TickCommands(); TickClients(a_Dt); diff --git a/source/Server.h b/source/Server.h index 6f8576ece..87e472465 100644 --- a/source/Server.h +++ b/source/Server.h @@ -43,7 +43,7 @@ public: // tolua_export // Player counts: int GetMaxPlayers(void) const {return m_MaxPlayers; } - int GetNumPlayers(void) const { return m_NumPlayers; } + int GetNumPlayers(void); void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; } // tolua_end @@ -78,6 +78,12 @@ public: // tolua_export /// Don't tick a_Client anymore, it will be ticked from its cPlayer instead void ClientMovedToWorld(const cClientHandle * a_Client); + /// Notifies the server that a player was created; the server uses this to adjust the number of players + void PlayerCreated(const cPlayer * a_Player); + + /// Notifies the server that a player was destroyed; the server uses this to adjust the number of players + void PlayerDestroyed(const cPlayer * a_Player); + CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; } CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; } @@ -135,6 +141,11 @@ private: cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick() + cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount + int m_PlayerCount; ///< Number of players currently playing in the server + cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff + int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread + cSocketThreads m_SocketThreads; int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini @@ -150,7 +161,6 @@ private: AString m_Description; int m_MaxPlayers; - int m_NumPlayers; cTickThread m_TickThread; cEvent m_RestartEvent; From f8757d3606a9cf14a353e5b7a61b8e660a4cce6d Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 13:43:55 +0200 Subject: [PATCH 14/21] Fixed crashes in world's clientlist manipulators --- source/World.cpp | 24 ++++++++++++++++++++++-- source/World.h | 6 ++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/source/World.cpp b/source/World.cpp index 7a1ecb82e..59240c7da 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -817,6 +817,26 @@ void cWorld::TickClients(float a_Dt) cClientHandleList RemoveClients; { cCSLock Lock(m_CSClients); + + // Remove clients scheduled for removal: + for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) + { + m_Clients.remove(*itr); + } // for itr - m_ClientsToRemove[] + m_ClientsToRemove.clear(); + + // Add clients scheduled for adding: + for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr) + { + if (std::find(m_Clients.begin(), m_Clients.end(), *itr) != m_Clients.end()) + { + ASSERT(!"Adding a client that is already in the clientlist"); + continue; + } + m_Clients.push_back(*itr); + } // for itr - m_ClientsToRemove[] + m_ClientsToAdd.clear(); + // Tick the clients, take out those that have been destroyed into RemoveClients for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();) { @@ -2018,7 +2038,7 @@ void cWorld::AddPlayer(cPlayer * a_Player) if (a_Player->GetClientHandle() != NULL) { cCSLock Lock(m_CSClients); - m_Clients.push_back(a_Player->GetClientHandle()); + m_ClientsToAdd.push_back(a_Player->GetClientHandle()); } // The player has already been added to the chunkmap as the entity, do NOT add again! @@ -2040,7 +2060,7 @@ void cWorld::RemovePlayer(cPlayer * a_Player) if (a_Player->GetClientHandle() != NULL) { cCSLock Lock(m_CSClients); - m_Clients.remove(a_Player->GetClientHandle()); + m_ClientsToRemove.push_back(a_Player->GetClientHandle()); } } diff --git a/source/World.h b/source/World.h index 2c04c4cc6..4f1e942e4 100644 --- a/source/World.h +++ b/source/World.h @@ -666,6 +666,12 @@ private: /// List of clients in this world, these will be ticked by this world cClientHandleList m_Clients; + + /// Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them + cClientHandleList m_ClientsToRemove; + + /// Clients that are scheduled for adding, waiting for TickClients to add them + cClientHandleList m_ClientsToAdd; cWorld(const AString & a_WorldName); From 17b2353d71c405bc626de22e5467d13be792c317 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 19:11:54 +0200 Subject: [PATCH 15/21] Server counts the players correctly. Was missing the PlayerDestroying() call, so players weren't removed from the playercount. --- source/Player.cpp | 3 +++ source/Server.cpp | 2 +- source/Server.h | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/source/Player.cpp b/source/Player.cpp index 34980d2f6..a1ea631aa 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -112,6 +112,9 @@ cPlayer::~cPlayer(void) { LOGD("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID()); + // Notify the server that the player is being destroyed + cRoot::Get()->GetServer()->PlayerDestroying(this); + SaveToDisk(); m_World->RemovePlayer( this ); diff --git a/source/Server.cpp b/source/Server.cpp index c01222e5a..fad562973 100644 --- a/source/Server.cpp +++ b/source/Server.cpp @@ -183,7 +183,7 @@ void cServer::PlayerCreated(const cPlayer * a_Player) -void cServer::PlayerDestroyed(const cPlayer * a_Player) +void cServer::PlayerDestroying(const cPlayer * a_Player) { // To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread cCSLock Lock(m_CSPlayerCountDiff); diff --git a/source/Server.h b/source/Server.h index 87e472465..b4fe81d8f 100644 --- a/source/Server.h +++ b/source/Server.h @@ -81,8 +81,8 @@ public: // tolua_export /// Notifies the server that a player was created; the server uses this to adjust the number of players void PlayerCreated(const cPlayer * a_Player); - /// Notifies the server that a player was destroyed; the server uses this to adjust the number of players - void PlayerDestroyed(const cPlayer * a_Player); + /// Notifies the server that a player is being destroyed; the server uses this to adjust the number of players + void PlayerDestroying(const cPlayer * a_Player); CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; } CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; } From cce9642c7b3a28f6457b264c11db92f54cf143ff Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 19:49:53 +0200 Subject: [PATCH 16/21] Fixed wrong names for some metas. They were E_BLOCK_ instead of E_META_. --- source/Bindings.cpp | 18 +++++++++--------- source/Bindings.h | 2 +- source/BlockID.h | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/source/Bindings.cpp b/source/Bindings.cpp index a0ad85156..1ebd858e3 100644 --- a/source/Bindings.cpp +++ b/source/Bindings.cpp @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/14/13 08:14:03. +** Generated automatically by tolua++-1.0.92 on 08/14/13 19:48:00. */ #ifndef __cplusplus @@ -29138,14 +29138,14 @@ TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S) tolua_constant(tolua_S,"E_META_TORCH_XP",E_META_TORCH_XP); tolua_constant(tolua_S,"E_META_TORCH_ZM",E_META_TORCH_ZM); tolua_constant(tolua_S,"E_META_TORCH_ZP",E_META_TORCH_ZP); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_DOUBLE_STEP_APPLE",E_BLOCK_WOODEN_DOUBLE_STEP_APPLE); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_DOUBLE_STEP_CONIFER",E_BLOCK_WOODEN_DOUBLE_STEP_CONIFER); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_DOUBLE_STEP_BIRCH",E_BLOCK_WOODEN_DOUBLE_STEP_BIRCH); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_DOUBLE_STEP_JUNGLE",E_BLOCK_WOODEN_DOUBLE_STEP_JUNGLE); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_STEP_APPLE",E_BLOCK_WOODEN_STEP_APPLE); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_STEP_CONIFER",E_BLOCK_WOODEN_STEP_CONIFER); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_STEP_BIRCH",E_BLOCK_WOODEN_STEP_BIRCH); - tolua_constant(tolua_S,"E_BLOCK_WOODEN_STEP_JUNGLE",E_BLOCK_WOODEN_STEP_JUNGLE); + tolua_constant(tolua_S,"E_META_WOODEN_DOUBLE_STEP_APPLE",E_META_WOODEN_DOUBLE_STEP_APPLE); + tolua_constant(tolua_S,"E_META_WOODEN_DOUBLE_STEP_CONIFER",E_META_WOODEN_DOUBLE_STEP_CONIFER); + tolua_constant(tolua_S,"E_META_WOODEN_DOUBLE_STEP_BIRCH",E_META_WOODEN_DOUBLE_STEP_BIRCH); + tolua_constant(tolua_S,"E_META_WOODEN_DOUBLE_STEP_JUNGLE",E_META_WOODEN_DOUBLE_STEP_JUNGLE); + tolua_constant(tolua_S,"E_META_WOODEN_STEP_APPLE",E_META_WOODEN_STEP_APPLE); + tolua_constant(tolua_S,"E_META_WOODEN_STEP_CONIFER",E_META_WOODEN_STEP_CONIFER); + tolua_constant(tolua_S,"E_META_WOODEN_STEP_BIRCH",E_META_WOODEN_STEP_BIRCH); + tolua_constant(tolua_S,"E_META_WOODEN_STEP_JUNGLE",E_META_WOODEN_STEP_JUNGLE); tolua_constant(tolua_S,"E_META_WOOL_WHITE",E_META_WOOL_WHITE); tolua_constant(tolua_S,"E_META_WOOL_ORANGE",E_META_WOOL_ORANGE); tolua_constant(tolua_S,"E_META_WOOL_MAGENTA",E_META_WOOL_MAGENTA); diff --git a/source/Bindings.h b/source/Bindings.h index 0211e0eeb..583017a88 100644 --- a/source/Bindings.h +++ b/source/Bindings.h @@ -1,6 +1,6 @@ /* ** Lua binding: AllToLua -** Generated automatically by tolua++-1.0.92 on 08/14/13 08:14:04. +** Generated automatically by tolua++-1.0.92 on 08/14/13 19:48:00. */ /* Exported function */ diff --git a/source/BlockID.h b/source/BlockID.h index 571087ab7..58919e1aa 100644 --- a/source/BlockID.h +++ b/source/BlockID.h @@ -478,16 +478,16 @@ enum E_META_TORCH_ZP = 4, // Torch attached to the ZP side of its block // E_BLOCK_WOODEN_DOUBLE_STEP metas: - E_BLOCK_WOODEN_DOUBLE_STEP_APPLE = 0, - E_BLOCK_WOODEN_DOUBLE_STEP_CONIFER = 1, - E_BLOCK_WOODEN_DOUBLE_STEP_BIRCH = 2, - E_BLOCK_WOODEN_DOUBLE_STEP_JUNGLE = 3, + E_META_WOODEN_DOUBLE_STEP_APPLE = 0, + E_META_WOODEN_DOUBLE_STEP_CONIFER = 1, + E_META_WOODEN_DOUBLE_STEP_BIRCH = 2, + E_META_WOODEN_DOUBLE_STEP_JUNGLE = 3, // E_BLOCK_WOODEN_STEP metas: - E_BLOCK_WOODEN_STEP_APPLE = 0, - E_BLOCK_WOODEN_STEP_CONIFER = 1, - E_BLOCK_WOODEN_STEP_BIRCH = 2, - E_BLOCK_WOODEN_STEP_JUNGLE = 3, + E_META_WOODEN_STEP_APPLE = 0, + E_META_WOODEN_STEP_CONIFER = 1, + E_META_WOODEN_STEP_BIRCH = 2, + E_META_WOODEN_STEP_JUNGLE = 3, // E_BLOCK_WOOL metas: E_META_WOOL_WHITE = 0, From e2ff4a2e5c654e1c4c33f5a9098bef35e0755897 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 19:56:29 +0200 Subject: [PATCH 17/21] Clients are deleted when the world is stopped. This fixes #92. --- source/World.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/World.cpp b/source/World.cpp index 59240c7da..5c3a24177 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -552,6 +552,17 @@ void cWorld::Start(void) void cWorld::Stop(void) { + // Delete the clients that have been in this world: + { + cCSLock Lock(m_CSClients); + for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + (*itr)->Destroy(); + delete *itr; + } // for itr - m_Clients[] + m_Clients.clear(); + } + m_TickThread.Stop(); m_Lighting.Stop(); m_Generator.Stop(); From 0af2a0b08c1570199631530db066e442701e7357 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 20:08:05 +0200 Subject: [PATCH 18/21] ClientHandle no longer queues chat messages. It is no longer needed to queue chat messages, because the protocol is parsed within the Tick thread itself, without holding any SocketThread CS. --- source/ClientHandle.cpp | 64 +++++++++++------------------------------ source/ClientHandle.h | 9 ------ 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/source/ClientHandle.cpp b/source/ClientHandle.cpp index 32bfb00f5..dce2bbd25 100644 --- a/source/ClientHandle.cpp +++ b/source/ClientHandle.cpp @@ -921,11 +921,24 @@ void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, c void cClientHandle::HandleChat(const AString & a_Message) { - // We need to process messages in the Tick thread, to avoid deadlocks resulting from player-commands being processed - // in the SocketThread and waiting for acquiring the ChunkMap CS with Plugin CS locked + // We no longer need to postpone message processing, because the messages already arrive in the Tick thread - cCSLock Lock(m_CSMessages); - m_PendingMessages.push_back(a_Message); + // If a command, perform it: + AString Message(a_Message); + if (cRoot::Get()->GetServer()->Command(*this, Message)) + { + return; + } + + // Not a command, broadcast as a simple message: + AString Msg; + Printf(Msg, "<%s%s%s> %s", + m_Player->GetColor().c_str(), + m_Player->GetName().c_str(), + cChatColor::White.c_str(), + Message.c_str() + ); + m_Player->GetWorld()->BroadcastChat(Msg); } @@ -1446,9 +1459,6 @@ void cClientHandle::Tick(float a_Dt) m_CurrentExplosionTick = (m_CurrentExplosionTick + 1) % ARRAYCOUNT(m_NumExplosionsPerTick); m_RunningSumExplosions -= m_NumExplosionsPerTick[m_CurrentExplosionTick]; m_NumExplosionsPerTick[m_CurrentExplosionTick] = 0; - - // Process the queued messages: - ProcessPendingMessages(); } @@ -2096,46 +2106,6 @@ void cClientHandle::AddWantedChunk(int a_ChunkX, int a_ChunkZ) -void cClientHandle::ProcessPendingMessages(void) -{ - while (true) - { - AString Message; - - // Extract one message from the PendingMessages buffer: - { - cCSLock Lock(m_CSMessages); - if (m_PendingMessages.empty()) - { - // No more messages in the buffer, bail out - return; - } - Message = m_PendingMessages.front(); - m_PendingMessages.pop_front(); - } // Lock(m_CSMessages) - - // If a command, perform it: - if (cRoot::Get()->GetServer()->Command(*this, Message)) - { - continue; - } - - // Not a command, broadcast as a simple message: - AString Msg; - Printf(Msg, "<%s%s%s> %s", - m_Player->GetColor().c_str(), - m_Player->GetName().c_str(), - cChatColor::White.c_str(), - Message.c_str() - ); - m_Player->GetWorld()->BroadcastChat(Msg); - } // while (true) -} - - - - - void cClientHandle::PacketBufferFull(void) { // Too much data in the incoming queue, the server is probably too busy, kick the client: diff --git a/source/ClientHandle.h b/source/ClientHandle.h index 4dcb188b3..9454b9b7a 100644 --- a/source/ClientHandle.h +++ b/source/ClientHandle.h @@ -288,12 +288,6 @@ private: /// Running sum of m_NumExplosionsPerTick[] int m_RunningSumExplosions; - /// Lock for the m_PendingMessages buffer - cCriticalSection m_CSMessages; - - /// Buffer for received messages to be processed in the Tick thread - AStringList m_PendingMessages; - static int s_ClientCount; int m_UniqueID; @@ -320,9 +314,6 @@ private: /// Handles the block placing packet when it is a real block placement (not block-using, item-using or eating) void HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler); - /// Processes the messages in m_PendingMessages; called from the Tick thread - void ProcessPendingMessages(void); - // cSocketThreads::cCallback overrides: virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client From 259cf9f027adb41925e075799c7e46ccbcaec881 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 22:27:49 +0200 Subject: [PATCH 19/21] cEvent can now wait for the event with a specified timeout. --- source/OSSupport/Event.cpp | 104 +++++++++++++++++++++++++++---------- source/OSSupport/Event.h | 10 ++++ 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/source/OSSupport/Event.cpp b/source/OSSupport/Event.cpp index 13b5c1d3f..f3c9053b1 100644 --- a/source/OSSupport/Event.cpp +++ b/source/OSSupport/Event.cpp @@ -15,7 +15,7 @@ cEvent::cEvent(void) { #ifdef _WIN32 - m_Event = CreateEvent( 0, FALSE, FALSE, 0 ); + m_Event = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_Event == NULL) { LOGERROR("cEvent: cannot create event, GLE = %d. Aborting server.", GetLastError()); @@ -78,19 +78,19 @@ cEvent::~cEvent() void cEvent::Wait(void) { -#ifdef _WIN32 - DWORD res = WaitForSingleObject(m_Event, INFINITE); - if (res != WAIT_OBJECT_0) - { - LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError()); - } -#else - int res = sem_wait(m_Event); - if (res != 0 ) - { - LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno); - } -#endif + #ifdef _WIN32 + DWORD res = WaitForSingleObject(m_Event, INFINITE); + if (res != WAIT_OBJECT_0) + { + LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError()); + } + #else + int res = sem_wait(m_Event); + if (res != 0 ) + { + LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno); + } + #endif } @@ -99,18 +99,70 @@ void cEvent::Wait(void) void cEvent::Set(void) { -#ifdef _WIN32 - if (!SetEvent(m_Event)) - { - LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError()); - } -#else - int res = sem_post(m_Event); - if (res != 0) - { - LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno); - } -#endif + #ifdef _WIN32 + if (!SetEvent(m_Event)) + { + LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError()); + } + #else + int res = sem_post(m_Event); + if (res != 0) + { + LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno); + } + #endif +} + + + + + +cEvent::eWaitResult cEvent::Wait(int a_TimeoutMilliSec) +{ + #ifdef _WIN32 + DWORD res = WaitForSingleObject(m_Event, (DWORD)a_TimeoutMilliSec); + switch (res) + { + case WAIT_OBJECT_0: + { + // The semaphore was signalled + return wrSignalled; + } + case WAIT_TIMEOUT: + { + // The timeout was hit + return wrTimeout; + } + default: + { + LOGWARNING("cEvent: timed-waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError()); + return wrError; + } + } + #else + timespec timeout; + timeout.tv_sec = a_TimeoutMilliSec / 1000; + timeout.tv_nsec = (a_TimeoutMilliSec % 1000) * 1000000; + int res = sem_timedwait(m_Event, &timeout); + switch (res) + { + case 0: + { + // The semaphore was signalled + return wrSignalled; + } + case ETIMEDOUT: + { + // The timeout was hit + return wrTimeout; + } + default: + { + LOGWARNING("cEvent: timed-waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno); + return wrError; + } + } + #endif } diff --git a/source/OSSupport/Event.h b/source/OSSupport/Event.h index 71f418c0c..803d73b7e 100644 --- a/source/OSSupport/Event.h +++ b/source/OSSupport/Event.h @@ -19,12 +19,22 @@ class cEvent { public: + enum eWaitResult + { + wrSignalled, + wrTimeout, + wrError, + } ; + cEvent(void); ~cEvent(); void Wait(void); void Set (void); + /// Waits for the semaphore with a timeout + eWaitResult Wait(int a_TimeoutMilliSec); + private: #ifdef _WIN32 From f93d13c41993ba86cad8303fe0b07ec2098dac39 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 22:36:34 +0200 Subject: [PATCH 20/21] Fixed world's a_Dt parameter getting time values in wrong units. --- source/World.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/World.cpp b/source/World.cpp index 5c3a24177..4cc130811 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -211,7 +211,7 @@ void cWorld::cTickThread::Execute(void) while (!m_ShouldTerminate) { clock_t Start = clock(); - m_World.Tick((float)(LastTime - Start) / CLOCKS_PER_SEC); + m_World.Tick((float)(1000 * (Start - LastTime)) / CLOCKS_PER_SEC); clock_t Now = clock(); if (Now - Start < ClocksPerTick) { From 50205bc4df3c272b88a5edd81a35ac0aca8213d5 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Wed, 14 Aug 2013 22:39:12 +0200 Subject: [PATCH 21/21] Added simple deadlock detection code. This will assert and then deliberately crash the server once a deadlock is detected. For detection, only the world tick threads are considered, cWorld's m_WorldAge is checked periodically and if it doesn't increment for several seconds, a deadlock is reported. --- VC2008/MCServer.vcproj | 8 ++ source/DeadlockDetect.cpp | 155 ++++++++++++++++++++++++++++++++++++++ source/DeadlockDetect.h | 70 +++++++++++++++++ source/Root.cpp | 17 ++++- 4 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 source/DeadlockDetect.cpp create mode 100644 source/DeadlockDetect.h diff --git a/VC2008/MCServer.vcproj b/VC2008/MCServer.vcproj index 4d850626d..0dcdeb3db 100644 --- a/VC2008/MCServer.vcproj +++ b/VC2008/MCServer.vcproj @@ -390,6 +390,14 @@ RelativePath="..\source\Cuboid.h" > + + + + diff --git a/source/DeadlockDetect.cpp b/source/DeadlockDetect.cpp new file mode 100644 index 000000000..960038f81 --- /dev/null +++ b/source/DeadlockDetect.cpp @@ -0,0 +1,155 @@ + +// DeadlockDetect.cpp + +// Declares the cDeadlockDetect class that tries to detect deadlocks and aborts the server when it detects one + +#include "Globals.h" +#include "DeadlockDetect.h" +#include "Root.h" +#include "World.h" + + + + + +/// Number of milliseconds per cycle +const int CYCLE_MILLISECONDS = 500; + +/// When the number of cycles for the same world age hits this value, it is considered a deadlock +const int NUM_CYCLES_LIMIT = 40; // 40 = twenty seconds + + + + + +cDeadlockDetect::cDeadlockDetect(void) : + super("DeadlockDetect") +{ +} + + + + + +bool cDeadlockDetect::Start(void) +{ + // Read the initial world data: + class cFillIn : + public cWorldListCallback + { + public: + cFillIn(cDeadlockDetect * a_Detect) : + m_Detect(a_Detect) + { + } + + virtual bool Item(cWorld * a_World) override + { + m_Detect->SetWorldAge(a_World->GetName(), a_World->GetWorldAge()); + return false; + } + + protected: + cDeadlockDetect * m_Detect; + } FillIn(this); + cRoot::Get()->ForEachWorld(FillIn); + return super::Start(); +} + + + + + +void cDeadlockDetect::Stop(void) +{ + m_EvtTerminate.Set(); + super::Stop(); +} + + + + + +void cDeadlockDetect::Execute(void) +{ + // Loop until the event is signalled + while (m_EvtTerminate.Wait(CYCLE_MILLISECONDS) == cEvent::wrTimeout) + { + // Check the world ages: + class cChecker : + public cWorldListCallback + { + public: + cChecker(cDeadlockDetect * a_Detect) : + m_Detect(a_Detect) + { + } + + protected: + cDeadlockDetect * m_Detect; + + virtual bool Item(cWorld * a_World) override + { + m_Detect->CheckWorldAge(a_World->GetName(), a_World->GetWorldAge()); + return false; + } + } Checker(this); + cRoot::Get()->ForEachWorld(Checker); + } // while (should run) +} + + + + + +void cDeadlockDetect::SetWorldAge(const AString & a_WorldName, Int64 a_Age) +{ + m_WorldAges[a_WorldName].m_Age = a_Age; + m_WorldAges[a_WorldName].m_NumCyclesSame = 0; +} + + + + + +void cDeadlockDetect::CheckWorldAge(const AString & a_WorldName, Int64 a_Age) +{ + WorldAges::iterator itr = m_WorldAges.find(a_WorldName); + if (itr == m_WorldAges.end()) + { + ASSERT(!"Unknown world in cDeadlockDetect"); + return; + } + if (itr->second.m_Age == a_Age) + { + itr->second.m_NumCyclesSame += 1; + if (itr->second.m_NumCyclesSame > NUM_CYCLES_LIMIT) + { + DeadlockDetected(); + return; + } + } + else + { + itr->second.m_Age = a_Age; + itr->second.m_NumCyclesSame = 0; + } +} + + + + + +void cDeadlockDetect::DeadlockDetected(void) +{ + ASSERT(!"Deadlock detected"); + + // TODO: Make a crashdump / coredump + + // Crash the server intentionally: + *((int *)0) = 0; +} + + + + diff --git a/source/DeadlockDetect.h b/source/DeadlockDetect.h new file mode 100644 index 000000000..bbd76826a --- /dev/null +++ b/source/DeadlockDetect.h @@ -0,0 +1,70 @@ + +// DeadlockDetect.h + +// Declares the cDeadlockDetect class that tries to detect deadlocks and aborts the server when it detects one + +/* +This class simply monitors each world's m_WorldAge, which is expected to grow on each tick. +If the world age doesn't grow for several seconds, it's either because the server is super-overloaded, +or because the world tick thread hangs in a deadlock. We presume the latter and therefore kill the server. +Once we learn to write crashdumps programmatically, we should do so just before killing, to enable debugging. +*/ + + + +#pragma once + +#include "OSSupport/IsThread.h" + + + + + +class cDeadlockDetect : + public cIsThread +{ + typedef cIsThread super; + +public: + cDeadlockDetect(void); + + /// Starts the detection. Hides cIsThread's Start, because we need some initialization + bool Start(void); + + /// Stops the detection. Hides cIsThread's Stop, because we need to signal m_EvtTerminate + void Stop(void); + +protected: + struct sWorldAge + { + /// Last m_WorldAge that has been detected in this world + Int64 m_Age; + + /// Number of cycles for which the age has been the same + int m_NumCyclesSame; + } ; + + /// Maps world name -> sWorldAge + typedef std::map WorldAges; + + WorldAges m_WorldAges; + + cEvent m_EvtTerminate; + + + // cIsThread overrides: + virtual void Execute(void) override; + + /// Sets the initial world age + void SetWorldAge(const AString & a_WorldName, Int64 a_Age); + + /// Checks if the world's age has changed, updates the world's stats; calls DeadlockDetected() if deadlock detected + void CheckWorldAge(const AString & a_WorldName, Int64 a_Age); + + /// Called when a deadlock is detected. Aborts the server. + void DeadlockDetected(void); +} ; + + + + diff --git a/source/Root.cpp b/source/Root.cpp index 166932cf2..07de0775c 100644 --- a/source/Root.cpp +++ b/source/Root.cpp @@ -16,6 +16,7 @@ #include "Chunk.h" #include "Protocol/ProtocolRecognizer.h" // for protocol version constants #include "CommandOutput.h" +#include "DeadlockDetect.h" #include "../iniFile/iniFile.h" @@ -90,6 +91,7 @@ void cRoot::InputThread(void * a_Params) void cRoot::Start(void) { + cDeadlockDetect dd; delete m_Log; m_Log = new cMCLogger(); @@ -162,6 +164,9 @@ void cRoot::Start(void) LOG("Starting worlds..."); StartWorlds(); + LOG("Starting deadlock detector..."); + dd.Start(); + LOG("Starting server..."); m_Server->Start(); @@ -183,17 +188,21 @@ void cRoot::Start(void) // Deallocate stuffs LOG("Shutting down server..."); - m_Server->Shutdown(); // This waits for threads to stop and d/c clients + m_Server->Shutdown(); + + LOG("Shutting down deadlock detector..."); + dd.Stop(); + LOG("Stopping world threads..."); StopWorlds(); + LOG("Stopping authenticator..."); m_Authenticator.Stop(); - LOG("Freeing MonsterConfig..."); - delete m_MonsterConfig; m_MonsterConfig = 0; + delete m_MonsterConfig; m_MonsterConfig = NULL; LOG("Stopping WebAdmin..."); - delete m_WebAdmin; m_WebAdmin = 0; + delete m_WebAdmin; m_WebAdmin = NULL; LOG("Unloading recipes..."); delete m_FurnaceRecipe; m_FurnaceRecipe = NULL; delete m_CraftingRecipes; m_CraftingRecipes = NULL;