1
0

Merge pull request #93 from mc-server/PerWorldThreads

Per world threads
This commit is contained in:
Mattes D 2013-08-15 01:56:52 -07:00
commit 1a52e89177
25 changed files with 31749 additions and 31150 deletions

View File

@ -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
end

View File

@ -390,6 +390,14 @@
RelativePath="..\source\Cuboid.h"
>
</File>
<File
RelativePath="..\source\DeadlockDetect.cpp"
>
</File>
<File
RelativePath="..\source\DeadlockDetect.h"
>
</File>
<File
RelativePath="..\source\Defines.h"
>
@ -2449,7 +2457,15 @@
Name="Core"
>
<File
RelativePath="..\MCServer\Plugins\Core\ban.lua"
RelativePath="..\MCServer\Plugins\Core\back.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\ban-unban.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\clear.lua"
>
</File>
<File
@ -2457,15 +2473,19 @@
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\coords.lua"
RelativePath="..\MCServer\Plugins\Core\do.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\gamemode.lua"
RelativePath="..\MCServer\Plugins\Core\functions.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\gotoworld.lua"
RelativePath="..\MCServer\Plugins\Core\give.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\gm.lua"
>
</File>
<File
@ -2476,32 +2496,44 @@
RelativePath="..\MCServer\Plugins\Core\item.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\itemrepair.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\kick.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\kill.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\locate.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\main.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\me.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\motd.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\onblockdig.lua"
RelativePath="..\MCServer\Plugins\Core\onbreakplaceblock.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\onblockplace.lua"
RelativePath="..\MCServer\Plugins\Core\ondeath.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\oncraftingnorecipe.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\onkilled.lua"
RelativePath="..\MCServer\Plugins\Core\onjoinleave.lua"
>
</File>
<File
@ -2509,23 +2541,23 @@
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\onplayerjoin.lua"
RelativePath="..\MCServer\Plugins\Core\plugins.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\playerlist.lua"
RelativePath="..\MCServer\Plugins\Core\portal-worlds.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\pluginlist.lua"
RelativePath="..\MCServer\Plugins\Core\rank-groups.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\regeneratechunk.lua"
RelativePath="..\MCServer\Plugins\Core\regen.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\reload.lua"
RelativePath="..\MCServer\Plugins\Core\save-reload-stop.lua"
>
</File>
<File
@ -2533,11 +2565,11 @@
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\stop.lua"
RelativePath="..\MCServer\Plugins\Core\teleport.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\teleport.lua"
RelativePath="..\MCServer\Plugins\Core\tell.lua"
>
</File>
<File
@ -2549,11 +2581,11 @@
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\unban.lua"
RelativePath="..\MCServer\Plugins\Core\viewdistance.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\viewdistance.lua"
RelativePath="..\MCServer\Plugins\Core\weather.lua"
>
</File>
<File
@ -2564,6 +2596,10 @@
RelativePath="..\MCServer\Plugins\Core\web_manageplugins.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\web_manageserver.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\web_permissions.lua"
>
@ -2580,6 +2616,10 @@
RelativePath="..\MCServer\Plugins\Core\web_whitelist.lua"
>
</File>
<File
RelativePath="..\MCServer\Plugins\Core\worldlimiter.lua"
>
</File>
</Filter>
<Filter
Name="ChatLog"

View File

@ -8,8 +8,10 @@
:: If there was a Git conflict, resolve it by resetting to HEAD; we're regenerating the files from scratch anyway
git checkout -- Bindings.cpp
git checkout -- Bindings.h
git checkout --ours Bindings.cpp
git add -u Bindings.cpp
git checkout --ours Bindings.h
git add -u Bindings.h

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
/*
** Lua binding: AllToLua
** Generated automatically by tolua++-1.0.92 on 08/12/13 11:15:34.
*/
/* Exported function */
TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S);
/*
** Lua binding: AllToLua
** Generated automatically by tolua++-1.0.92 on 08/14/13 19:48:00.
*/
/* Exported function */
TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S);

View File

@ -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,

View File

@ -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);
@ -268,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;
}
@ -421,11 +428,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());
}
@ -914,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);
}
@ -1176,7 +1196,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 +1211,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;
@ -1307,6 +1327,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);
@ -1342,6 +1398,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
{
@ -1355,12 +1425,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 +1441,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);
@ -1387,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();
}
@ -1955,7 +2024,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();
@ -2037,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:
@ -2116,30 +2145,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);
}

View File

@ -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 )
@ -212,11 +216,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 +257,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;
@ -279,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;
@ -311,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

155
source/DeadlockDetect.cpp Normal file
View File

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

70
source/DeadlockDetect.h Normal file
View File

@ -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<AString, sWorldAge> 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);
} ;

View File

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

View File

@ -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

View File

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

View File

@ -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);
@ -54,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;
}
@ -70,7 +75,6 @@ private:
}
#endif // else _WIN32
} ;

View File

@ -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);
}
@ -110,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 );
@ -127,8 +132,16 @@ 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
if (m_ClientHandle != NULL)
{
cRoot::Get()->GetServer()->ClientMovedToWorld(m_ClientHandle);
}
GetWorld()->AddPlayer(this);
return true;
}
@ -142,6 +155,7 @@ bool cPlayer::Initialize(cWorld * a_World)
void cPlayer::Destroyed()
{
CloseWindow(false);
m_ClientHandle = NULL;
}
@ -151,22 +165,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() );
}
@ -175,18 +184,28 @@ 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->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;
}
}
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)
@ -1106,20 +1125,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;
}

View File

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

View File

@ -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);

View File

@ -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;
@ -270,8 +279,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 +292,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 +354,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 +366,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);
}
}

View File

@ -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

View File

@ -59,18 +59,59 @@ 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;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cServer:
cServer::cServer(void) :
m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"),
m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"),
m_bIsConnected(false),
m_bRestarting(false),
m_RCONServer(*this),
m_TickThread(*this)
{
}
@ -121,8 +162,45 @@ void cServer::RemoveClient(const cClientHandle * a_Client)
void cServer::ClientMovedToWorld(const cClientHandle * a_Client)
{
cCSLock Lock(m_CSClients);
m_ClientsToRemove.push_back(const_cast<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::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);
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)
{
LOGERROR("ERROR: Trying to initialize server while server is already running!");
@ -164,18 +242,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);
@ -201,29 +278,10 @@ 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)
int cServer::GetNumPlayers(void)
{
}
cServer::~cServer()
{
// TODO: Shut down the server gracefully
m_pState->bStopTickThread = true;
delete m_pState->pTickThread; m_pState->pTickThread = NULL;
delete m_pState;
cCSLock Lock(m_CSPlayerCount);
return m_PlayerCount;
}
@ -284,55 +342,22 @@ 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)
{
m_Millisecondsf += a_Dt;
if (m_Millisecondsf > 1.f)
// Apply the queued playercount adjustments (postponed to avoid deadlocks)
int PlayerCountDiff = 0;
{
m_Milliseconds += (int)m_Millisecondsf;
m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf;
cCSLock Lock(m_CSPlayerCountDiff);
std::swap(PlayerCountDiff, m_PlayerCountDiff);
}
cRoot::Get()->TickWorlds(a_Dt); // TODO - Maybe give all worlds their own thread?
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[]
cCSLock Lock(m_CSPlayerCount);
m_PlayerCount += PlayerCountDiff;
}
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
{
delete *itr;
} // for itr - RemoveClients[]
cRoot::Get()->GetPluginManager()->Tick(a_Dt);
cRoot::Get()->TickCommands();
TickClients(a_Dt);
if (!m_bRestarting)
{
@ -341,7 +366,7 @@ bool cServer::Tick(float a_Dt)
else
{
m_bRestarting = false;
m_pState->RestartEvent.Set();
m_RestartEvent.Set();
return false;
}
}
@ -350,33 +375,39 @@ bool cServer::Tick(float a_Dt)
void ServerTickThread( void * a_Param )
void cServer::TickClients(float a_Dt)
{
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 )
cClientHandleList RemoveClients;
{
long long NowTime = Timer.GetNowTime();
float DeltaTime = (float)(NowTime-LastTime);
bKeepGoing = CServerObj->Tick( DeltaTime );
long long TickTime = Timer.GetNowTime() - NowTime;
cCSLock Lock(m_CSClients);
if( TickTime < msPerTick ) // Stretch tick time until it's at least msPerTick
// 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)
{
cSleep::MilliSleep( (unsigned int)( msPerTick - TickTime ) );
}
LastTime = NowTime;
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[]
}
LOG("TICK THREAD STOPPED");
// Delete the clients that have been destroyed
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
{
delete *itr;
} // for itr - RemoveClients[]
}
@ -385,7 +416,6 @@ void ServerTickThread( void * a_Param )
bool cServer::Start(void)
{
m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" );
if (!m_ListenThreadIPv4.Start())
{
return false;
@ -394,7 +424,10 @@ bool cServer::Start(void)
{
return false;
}
m_pState->pTickThread->Start( true );
if (!m_TickThread.Start())
{
return false;
}
return true;
}
@ -478,32 +511,13 @@ 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 cServer::Shutdown(void)
{
m_ListenThreadIPv4.Stop();
m_ListenThreadIPv6.Stop();
m_bRestarting = true;
m_pState->RestartEvent.Wait();
m_RestartEvent.Wait();
cRoot::Get()->SaveAllChunks();
@ -520,15 +534,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);
@ -553,6 +558,7 @@ void cServer::AuthenticateUser(int a_ClientID)
if ((*itr)->GetUniqueID() == a_ClientID)
{
(*itr)->Authenticate();
return;
}
} // for itr - m_Clients[]
}
@ -562,7 +568,7 @@ void cServer::AuthenticateUser(int a_ClientID)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cServer::cClientPacketThread:
// cServer::cNotifyWriteThread:
cServer::cNotifyWriteThread::cNotifyWriteThread(void) :
super("ClientPacketThread"),

View File

@ -37,11 +37,16 @@ class cServer // tolua_export
public: // tolua_export
bool InitServer(cIniFile & a_SettingsIni);
bool IsConnected(void) const { return m_bIsConnected;} // returns connection status
// tolua_begin
void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export
const AString & GetDescription(void) const {return m_Description; }
bool Tick(float a_Dt);
// Player counts:
int GetMaxPlayers(void) const {return m_MaxPlayers; }
int GetNumPlayers(void);
void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; }
// tolua_end
bool Start(void);
@ -53,14 +58,12 @@ 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
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
@ -72,6 +75,15 @@ 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);
/// 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 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; }
@ -103,25 +115,41 @@ 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;
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 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()
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
// Time since server was started
float m_Millisecondsf;
unsigned int m_Milliseconds;
bool m_bIsConnected; // true - connected false - not connected
bool m_bRestarting;
@ -131,13 +159,26 @@ private:
cRCONServer m_RCONServer;
AString m_Description;
int m_MaxPlayers;
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);
/// 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;
}; // tolua_export

View File

@ -1,3 +1,4 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "BlockID.h"
@ -128,6 +129,9 @@ protected:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWorldLightingProgress:
/// A simple thread that displays the progress of world lighting in cWorld::InitializeSpawn()
class cWorldLightingProgress :
public cIsThread
@ -187,10 +191,47 @@ 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)(1000 * (Start - LastTime)) / 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_StorageSchema("Default"),
m_WorldAgeSecs(0),
m_TimeOfDaySecs(0),
m_WorldAge(0),
@ -199,109 +240,12 @@ 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";
LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str());
cMakeDir::MakeDir(m_WorldName.c_str());
MTRand r1;
m_SpawnX = (double)((r1.randInt() % 1000) - 500);
m_SpawnY = cChunkDef::Height;
m_SpawnZ = (double)((r1.randInt() % 1000) - 500);
m_GameMode = eGameMode_Creative;
AString StorageSchema("Default");
cIniFile IniFile(m_IniFileName);
IniFile.ReadFile();
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
// 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);
m_ChunkSender.Start(this);
m_LastSave = 0;
m_LastUnload = 0;
// preallocate some memory for ticking blocks so we don<6F>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());
}
}
@ -504,10 +448,126 @@ 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<6F>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)
{
// 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();
m_ChunkSender.Stop();
m_Storage.Stop();
}
@ -529,7 +589,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();
@ -538,7 +598,9 @@ void cWorld::Tick(float a_Dt)
m_ChunkMap->Tick(a_Dt);
TickClients(a_Dt);
TickQueuedBlocks(a_Dt);
TickQueuedTasks();
GetSimulatorManager()->Simulate(a_Dt);
@ -740,6 +802,78 @@ 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::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();)
{
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);
@ -1900,28 +2034,24 @@ 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);
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_ClientsToAdd.push_back(a_Player->GetClientHandle());
}
// The player has already been added to the chunkmap as the entity, do NOT add again!
}
@ -1932,8 +2062,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_ClientsToRemove.push_back(a_Player->GetClientHandle());
}
}
@ -2279,6 +2418,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);
@ -2306,11 +2464,13 @@ void cWorld::RemoveEntity(cEntity * a_Entity)
/*
unsigned int cWorld::GetNumPlayers(void)
{
cCSLock Lock(m_CSPlayers);
return m_Players.size();
}
*/
@ -2522,3 +2682,15 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cWorld::cTaskSaveAllChunks:
void cWorld::cTaskSaveAllChunks::Run(cWorld & a_World)
{
a_World.SaveAllChunks();
}

View File

@ -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<cTask *> cTasks;
class cTaskSaveAllChunks :
public cTask
{
protected:
// cTask overrides:
virtual void Run(cWorld & a_World) override;
} ;
// tolua_begin
@ -136,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);
@ -204,13 +222,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 +234,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);
@ -470,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);
@ -484,12 +500,14 @@ 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);
/// 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
@ -540,7 +558,28 @@ 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;
/// 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;
@ -583,8 +622,6 @@ private:
cWorldStorage m_Storage;
AString m_Description;
unsigned int m_MaxPlayers;
cChunkMap * m_ChunkMap;
@ -616,15 +653,43 @@ private:
cChunkSender m_ChunkSender;
cLightingThread m_Lighting;
AString m_WorldName;
AString m_IniFileName;
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;
/// Guards m_Clients
cCriticalSection m_CSClients;
/// 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);
~cWorld();
void TickWeather(float a_Dt); // Handles weather each tick
void TickSpawnMobs(float a_Dt); // Handles mob spawning each tick
void Tick(float a_Dt);
/// 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);

View File

@ -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");

View File

@ -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);