From 9020dc993241a4a90e8e98b3435d9b2576f313ea Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 13 Aug 2013 22:45:29 +0200 Subject: 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 | 48 +++++++++++++++++++-------- source/Server.h | 8 +++-- source/World.cpp | 66 ++++++++++++++++++++++++++++++++----- source/World.h | 16 +++++++-- 10 files changed, 182 insertions(+), 168 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,14 +312,44 @@ bool cServer::Tick(float a_Dt) { cRoot::Get()->TickCommands(); + TickClients(a_Dt); + + if (!m_bRestarting) + { + return true; + } + else + { + m_bRestarting = false; + m_RestartEvent.Set(); + return false; + } +} + + + + + +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()) { - 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 ) + // 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; } @@ -327,21 +357,12 @@ bool cServer::Tick(float 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[] - - if (!m_bRestarting) - { - return true; - } - else - { - m_bRestarting = false; - m_RestartEvent.Set(); - return false; - } } @@ -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 -- cgit v1.2.3