Run network server in a separate thread, to minimise latency.
Make some parts of logger and console thread-safe. Simplify console: remove special commands; remove prefix for script commands. Remove LOG_ONCE, to simplify logger. Change some code to use the new logger interface. Fix OOS when revealing map. Clean up some formatting. This was SVN commit r8511.
This commit is contained in:
parent
034504f536
commit
5d764f1435
@ -31,26 +31,27 @@ function init(data)
|
||||
var ldTitleBar = getGUIObjectByName ("ldTitleBar");
|
||||
var ldText = getGUIObjectByName ("ldText");
|
||||
|
||||
switch(data.attribs.mapType)
|
||||
if (data)
|
||||
{
|
||||
case "scenario":
|
||||
ldTitleBar.caption = "Loading Scenario";
|
||||
ldText.caption = "Loading " + mapName + "\nPlease wait...";
|
||||
break;
|
||||
|
||||
case "random":
|
||||
ldTitleBar.caption = "Loading Random Map";
|
||||
ldText.caption = "Generating " + mapName + "\nPlease wait...";
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unkown map type: "+data.attribs.mapType);
|
||||
switch (data.attribs.mapType)
|
||||
{
|
||||
case "scenario":
|
||||
ldTitleBar.caption = "Loading Scenario";
|
||||
ldText.caption = "Loading " + mapName + "\nPlease wait...";
|
||||
break;
|
||||
|
||||
case "random":
|
||||
ldTitleBar.caption = "Loading Random Map";
|
||||
ldText.caption = "Generating " + mapName + "\nPlease wait...";
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unkown map type: "+data.attribs.mapType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getGUIObjectByName ("ldProgressBarText").caption = "";
|
||||
getGUIObjectByName ("ldProgressBar").caption = 0;
|
||||
|
||||
|
||||
getGUIObjectByName("ldProgressBarText").caption = "";
|
||||
getGUIObjectByName("ldProgressBar").caption = 0;
|
||||
|
||||
// Pick a random tip of the day (each line is a separate tip).
|
||||
var tipArray = readFileLines("gui/text/tips.txt");
|
||||
@ -73,5 +74,5 @@ function reallyStartGame()
|
||||
Engine.SwitchGuiPage("page_session_new.xml", g_Data);
|
||||
|
||||
// Restore default cursor.
|
||||
setCursor ("arrow-default");
|
||||
setCursor("arrow-default");
|
||||
}
|
||||
|
@ -137,7 +137,7 @@
|
||||
|
||||
<object size="0 112 100%-18 128" type="text" style="devCommandsText">Reveal map</object>
|
||||
<object size="100%-16 112 100% 128" type="checkbox" style="wheatCrossBox">
|
||||
<action on="Press">Engine.SetRevealMap(this.checked);</action>
|
||||
<action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
@ -263,7 +263,6 @@ GuiInterface.prototype.SetStatusBars = function(player, cmd)
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* Displays the rally point of a building
|
||||
*/
|
||||
|
@ -11,6 +11,11 @@ function ProcessCommand(player, cmd)
|
||||
print(cmd.message);
|
||||
break;
|
||||
|
||||
case "reveal-map":
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
cmpRangeManager.SetLosRevealAll(cmd.enable);
|
||||
break;
|
||||
|
||||
case "walk":
|
||||
var cmpUnitAI = GetFormationUnitAI(cmd.entities);
|
||||
if (cmpUnitAI)
|
||||
|
@ -26,8 +26,6 @@
|
||||
#include "ps/DllLoader.h"
|
||||
#include "ps/Filesystem.h"
|
||||
|
||||
#define LOG_CATEGORY L"collada"
|
||||
|
||||
namespace Collada
|
||||
{
|
||||
#include "collada/DLL.h"
|
||||
@ -37,8 +35,12 @@ namespace
|
||||
{
|
||||
void ColladaLog(int severity, const char* text)
|
||||
{
|
||||
const CLogger::ELogMethod method = severity == LOG_INFO ? CLogger::Normal : severity == LOG_WARNING ? CLogger::Warning : CLogger::Error;
|
||||
LOG(method, LOG_CATEGORY, L"%hs", text);
|
||||
if (severity == LOG_INFO)
|
||||
LOGMESSAGE(L"Collada message: %hs", text);
|
||||
else if (severity == LOG_WARNING)
|
||||
LOGWARNING(L"Collada warning: %hs", text);
|
||||
else
|
||||
LOGERROR(L"Collada error: %hs", text);
|
||||
}
|
||||
|
||||
void ColladaOutput(void* cb_data, const char* data, unsigned int length)
|
||||
@ -81,7 +83,7 @@ public:
|
||||
{
|
||||
if (! dll.LoadDLL())
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Failed to load COLLADA conversion DLL");
|
||||
LOGERROR(L"Failed to load COLLADA conversion DLL");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -94,7 +96,7 @@ public:
|
||||
}
|
||||
catch (PSERROR_DllLoader&)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to load symbols from COLLADA conversion DLL");
|
||||
LOGERROR(L"Failed to load symbols from COLLADA conversion DLL");
|
||||
dll.Unload();
|
||||
return false;
|
||||
}
|
||||
@ -104,7 +106,7 @@ public:
|
||||
CVFSFile skeletonFile;
|
||||
if (skeletonFile.Load(g_VFS, L"art/skeletons/skeletons.xml") != PSRETURN_OK)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to read skeleton definitions");
|
||||
LOGERROR(L"Failed to read skeleton definitions");
|
||||
dll.Unload();
|
||||
return false;
|
||||
}
|
||||
@ -112,7 +114,7 @@ public:
|
||||
int ok = set_skeleton_definitions((const char*)skeletonFile.GetBuffer(), (int)skeletonFile.GetBufferSize());
|
||||
if (ok < 0)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to load skeleton definitions");
|
||||
LOGERROR(L"Failed to load skeleton definitions");
|
||||
dll.Unload();
|
||||
return false;
|
||||
}
|
||||
@ -214,7 +216,7 @@ VfsPath CColladaManager::GetLoadableFilename(const VfsPath& pathnameNoExtension,
|
||||
if (g_VFS->GetFileInfo(dae, &fileInfo) < 0)
|
||||
{
|
||||
// This shouldn't occur for any sensible reasons
|
||||
LOG(CLogger::Error, LOG_CATEGORY, L"Failed to stat DAE file '%ls'", dae.string().c_str());
|
||||
LOGERROR(L"Failed to stat DAE file '%ls'", dae.string().c_str());
|
||||
return VfsPath();
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,6 @@
|
||||
* If now > target time, switch to 'STATIONARY, NO TOOLTIP'
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY L"gui"
|
||||
|
||||
enum
|
||||
{
|
||||
ST_IN_MOTION,
|
||||
@ -87,9 +85,11 @@ const double CooldownTime = 0.25; // TODO: Don't hard-code this value
|
||||
bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style)
|
||||
{
|
||||
if (obj && obj->SettingExists("_icon_tooltip_style") && obj->MouseOverIcon())
|
||||
{ //Special code to handle icon tooltips in text objects
|
||||
if(GUI<CStr>::GetSetting(obj, "_icon_tooltip_style", style) == PSRETURN_OK)
|
||||
{ //Check if icon tooltip text exists
|
||||
{
|
||||
// Special code to handle icon tooltips in text objects
|
||||
if (GUI<CStr>::GetSetting(obj, "_icon_tooltip_style", style) == PSRETURN_OK)
|
||||
{
|
||||
// Check if icon tooltip text exists
|
||||
CStr text;
|
||||
if (GUI<CStr>::GetSetting(obj, "_icon_tooltip", text) == PSRETURN_OK)
|
||||
{
|
||||
@ -100,15 +100,17 @@ bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style)
|
||||
}
|
||||
|
||||
if (style.empty())
|
||||
{
|
||||
// Text, but no style - use default
|
||||
style = "default";
|
||||
}
|
||||
|
||||
m_IsIconTooltip = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (obj && obj->SettingExists("tooltip_style")
|
||||
}
|
||||
else if (obj && obj->SettingExists("tooltip_style")
|
||||
&& GUI<CStr>::GetSetting(obj, "tooltip_style", style) == PSRETURN_OK)
|
||||
{
|
||||
CStr text;
|
||||
@ -121,13 +123,16 @@ bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style)
|
||||
}
|
||||
|
||||
if (style.empty())
|
||||
{
|
||||
// Text, but no style - use default
|
||||
style = "default";
|
||||
}
|
||||
|
||||
m_IsIconTooltip = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Failed while retrieving tooltip_style or tooltip
|
||||
return false;
|
||||
}
|
||||
@ -144,7 +149,7 @@ void GUITooltip::ShowTooltip(IGUIObject* obj, CPos pos, const CStr& style, CGUI*
|
||||
IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
|
||||
if (! tooltipobj)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Cannot find tooltip named '%hs'", style.c_str());
|
||||
LOGERROR(L"Cannot find tooltip named '%hs'", style.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -157,7 +162,7 @@ void GUITooltip::ShowTooltip(IGUIObject* obj, CPos pos, const CStr& style, CGUI*
|
||||
usedobj = gui->FindObjectByName(usedObjectName);
|
||||
if (! usedobj)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Cannot find object named '%hs' used by tooltip '%hs'", usedObjectName.c_str(), style.c_str());
|
||||
LOGERROR(L"Cannot find object named '%hs' used by tooltip '%hs'", usedObjectName.c_str(), style.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -178,12 +183,14 @@ void GUITooltip::ShowTooltip(IGUIObject* obj, CPos pos, const CStr& style, CGUI*
|
||||
// Retrieve object's 'tooltip' setting
|
||||
CStr text;
|
||||
if (m_IsIconTooltip)
|
||||
{ //Use icon tooltip property
|
||||
{
|
||||
// Use icon tooltip property
|
||||
if (GUI<CStr>::GetSetting(obj, "_icon_tooltip", text) != PSRETURN_OK)
|
||||
debug_warn(L"Failed to retrieve icon tooltip text"); // shouldn't fail
|
||||
}
|
||||
else
|
||||
{ //use normal tooltip property
|
||||
{
|
||||
// Use normal tooltip property
|
||||
if (GUI<CStr>::GetSetting(obj, "tooltip", text) != PSRETURN_OK)
|
||||
debug_warn(L"Failed to retrieve tooltip text"); // shouldn't fail
|
||||
}
|
||||
@ -208,7 +215,7 @@ void GUITooltip::HideTooltip(const CStr& style, CGUI* gui)
|
||||
IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
|
||||
if (! tooltipobj)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Cannot find tooltip named '%hs'", style.c_str());
|
||||
LOGERROR(L"Cannot find tooltip named '%hs'", style.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -219,7 +226,7 @@ void GUITooltip::HideTooltip(const CStr& style, CGUI* gui)
|
||||
IGUIObject* usedobj = gui->FindObjectByName(usedObjectName);
|
||||
if (! usedobj)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Cannot find object named '%hs' used by tooltip '%hs'", usedObjectName.c_str(), style.c_str());
|
||||
LOGERROR(L"Cannot find object named '%hs' used by tooltip '%hs'", usedObjectName.c_str(), style.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -249,7 +256,7 @@ static int GetTooltipDelay(CStr& style, CGUI* gui)
|
||||
IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
|
||||
if (! tooltipobj)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Cannot find tooltip object named '%hs'", style.c_str());
|
||||
LOGERROR(L"Cannot find tooltip object named '%hs'", style.c_str());
|
||||
return delay;
|
||||
}
|
||||
GUI<int>::GetSetting(tooltipobj, "delay", delay);
|
||||
@ -386,8 +393,6 @@ void GUITooltip::Update(IGUIObject* Nearest, CPos MousePos, CGUI* GUI)
|
||||
m_State = nextstate;
|
||||
}
|
||||
|
||||
|
||||
m_PreviousMousePos = MousePos;
|
||||
m_PreviousObject = Nearest;
|
||||
|
||||
}
|
||||
|
@ -188,11 +188,7 @@ void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs)
|
||||
|
||||
debug_assert(g_NetServer);
|
||||
|
||||
// Convert from GUI script context to net server script context
|
||||
CScriptValRooted gameAttribs (g_NetServer->GetScriptInterface().GetContext(),
|
||||
g_NetServer->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get()));
|
||||
|
||||
g_NetServer->UpdateGameAttributes(gameAttribs);
|
||||
g_NetServer->UpdateGameAttributes(attribs, guiManager->GetScriptInterface());
|
||||
}
|
||||
|
||||
void StartNetworkHost(void* cbdata, std::wstring playerName)
|
||||
@ -305,18 +301,6 @@ CScriptVal LoadMapSettings(void* cbdata, std::wstring pathname)
|
||||
return reader.GetMapSettings(guiManager->GetScriptInterface()).get();
|
||||
}
|
||||
|
||||
void SetRevealMap(void* UNUSED(cbdata), bool enabled)
|
||||
{
|
||||
if (!g_Game)
|
||||
return;
|
||||
|
||||
CSimulation2* sim = g_Game->GetSimulation2();
|
||||
debug_assert(sim);
|
||||
CmpPtr<ICmpRangeManager> cmpRangeManager(*sim, SYSTEM_ENTITY);
|
||||
if (!cmpRangeManager.null())
|
||||
cmpRangeManager->SetLosRevealAll(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start / stop camera following mode
|
||||
* @param entityid unit id to follow. If zero, stop following mode
|
||||
@ -406,7 +390,6 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<void, &RestartInAtlas>("RestartInAtlas");
|
||||
scriptInterface.RegisterFunction<bool, &AtlasIsAvailable>("AtlasIsAvailable");
|
||||
scriptInterface.RegisterFunction<CScriptVal, std::wstring, &LoadMapSettings>("LoadMapSettings");
|
||||
scriptInterface.RegisterFunction<void, bool, &SetRevealMap>("SetRevealMap");
|
||||
scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow");
|
||||
|
||||
// Development/debugging functions
|
||||
|
@ -305,8 +305,6 @@ static void Frame()
|
||||
}
|
||||
|
||||
PROFILE_START("network poll");
|
||||
if (g_NetServer)
|
||||
g_NetServer->Poll();
|
||||
if (g_NetClient)
|
||||
g_NetClient->Poll();
|
||||
PROFILE_END("network poll");
|
||||
@ -354,8 +352,6 @@ static void Frame()
|
||||
|
||||
// Immediately flush any messages produced by simulation code
|
||||
PROFILE_START("network flush");
|
||||
if (g_NetServer)
|
||||
g_NetServer->Flush();
|
||||
if (g_NetClient)
|
||||
g_NetClient->Flush();
|
||||
PROFILE_END("network flush");
|
||||
|
@ -27,121 +27,81 @@
|
||||
#define ALLNETMSGS_IMPLEMENT
|
||||
#include "NetMessages.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetMessage()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage::CNetMessage( void )
|
||||
CNetMessage::CNetMessage()
|
||||
{
|
||||
m_Type = NMT_INVALID;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetMessage()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage::CNetMessage( NetMessageType type )
|
||||
CNetMessage::CNetMessage(NetMessageType type)
|
||||
{
|
||||
m_Type = type;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetMessage()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage::~CNetMessage( void )
|
||||
CNetMessage::~CNetMessage()
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Serialize()
|
||||
// Desc: Serializes the message into the buffer parameter
|
||||
//-----------------------------------------------------------------------------
|
||||
u8* CNetMessage::Serialize( u8* pBuffer ) const
|
||||
u8* CNetMessage::Serialize(u8* pBuffer) const
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pBuffer ) return NULL;
|
||||
|
||||
size_t size = GetSerializedLength();
|
||||
Serialize_int_1( pBuffer, m_Type );
|
||||
Serialize_int_2( pBuffer, size );
|
||||
Serialize_int_1(pBuffer, m_Type);
|
||||
Serialize_int_2(pBuffer, size);
|
||||
|
||||
return pBuffer;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Deserialize()
|
||||
// Desc: Loads this message from the specified buffer parameter
|
||||
//-----------------------------------------------------------------------------
|
||||
const u8* CNetMessage::Deserialize( const u8* pStart, const u8* pEnd )
|
||||
const u8* CNetMessage::Deserialize(const u8* pStart, const u8* pEnd)
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pStart || !pEnd ) return NULL;
|
||||
|
||||
debug_assert( pStart + 3 <= pEnd );
|
||||
if (pStart + 3 > pEnd)
|
||||
{
|
||||
LOGERROR(L"CNetMessage: Corrupt packet (smaller than header)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const u8* pBuffer = pStart;
|
||||
|
||||
int type;
|
||||
size_t size;
|
||||
Deserialize_int_1( pBuffer, type );
|
||||
Deserialize_int_2( pBuffer, size );
|
||||
Deserialize_int_1(pBuffer, type);
|
||||
Deserialize_int_2(pBuffer, size);
|
||||
m_Type = (NetMessageType)type;
|
||||
|
||||
debug_assert( pStart + size == pEnd );
|
||||
if (pStart + size != pEnd)
|
||||
{
|
||||
LOGERROR(L"CNetMessage: Corrupt packet (incorrect size)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pBuffer;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetSerializedLength()
|
||||
// Desc: Returns the size of the serialized message
|
||||
//-----------------------------------------------------------------------------
|
||||
size_t CNetMessage::GetSerializedLength( void ) const
|
||||
size_t CNetMessage::GetSerializedLength() const
|
||||
{
|
||||
// By default, return header size
|
||||
return 3;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ToString()
|
||||
// Desc: Returns a string representation for the message
|
||||
//-----------------------------------------------------------------------------
|
||||
CStr CNetMessage::ToString( void ) const
|
||||
CStr CNetMessage::ToString() const
|
||||
{
|
||||
CStr ret;
|
||||
// This is called only when the subclass doesn't override it
|
||||
|
||||
// Not defined yet?
|
||||
if ( GetType() == NMT_INVALID )
|
||||
{
|
||||
ret = "MESSAGE_TYPE_NONE { Undefined Message }";
|
||||
}
|
||||
if (GetType() == NMT_INVALID)
|
||||
return "MESSAGE_TYPE_NONE { Undefined Message }";
|
||||
else
|
||||
{
|
||||
ret = " Unknown Message " + CStr( GetType() );
|
||||
}
|
||||
|
||||
return ret;
|
||||
return "Unknown Message " + CStr(GetType());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CreateMessage()
|
||||
// Desc: Creates the appropriate message based on the given data
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
|
||||
size_t dataSize,
|
||||
ScriptInterface& scriptInterface)
|
||||
{
|
||||
CNetMessage* pNewMessage = NULL;
|
||||
CNetMessage header;
|
||||
|
||||
// Validate parameters
|
||||
if ( !pData ) return NULL;
|
||||
CNetMessage* pNewMessage = NULL;
|
||||
CNetMessage header;
|
||||
|
||||
// Figure out message type
|
||||
header.Deserialize( ( const u8* )pData, ( const u8* )pData + dataSize );
|
||||
header.Deserialize((const u8*)pData, (const u8*)pData + dataSize);
|
||||
|
||||
switch ( header.GetType() )
|
||||
switch (header.GetType())
|
||||
{
|
||||
case NMT_GAME_SETUP:
|
||||
pNewMessage = new CGameSetupMessage(scriptInterface);
|
||||
@ -204,34 +164,8 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
|
||||
break;
|
||||
}
|
||||
|
||||
if ( pNewMessage )
|
||||
pNewMessage->Deserialize( ( const u8* )pData, ( const u8* )pData + dataSize );
|
||||
if (pNewMessage)
|
||||
pNewMessage->Deserialize((const u8*)pData, (const u8*)pData + dataSize);
|
||||
|
||||
return pNewMessage;
|
||||
}
|
||||
|
||||
CNetMessage* CNetMessageFactory::CloneMessage( const CNetMessage* message, ScriptInterface& scriptInterface )
|
||||
{
|
||||
// TODO: maybe this could be implemented more efficiently,
|
||||
// particularly for script messages where serialisation is
|
||||
// relatively expensive
|
||||
|
||||
size_t len = message->GetSerializedLength();
|
||||
u8* buffer = new u8[len];
|
||||
|
||||
u8* bufend = message->Serialize(buffer);
|
||||
|
||||
if (!bufend)
|
||||
{
|
||||
delete[] buffer;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
debug_assert(bufend == buffer+len);
|
||||
|
||||
CNetMessage* ret = CreateMessage(buffer, len, scriptInterface);
|
||||
|
||||
delete[] buffer;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -37,15 +37,15 @@ class CNetMessage : public ISerializable
|
||||
|
||||
public:
|
||||
|
||||
CNetMessage( void );
|
||||
CNetMessage( NetMessageType type );
|
||||
virtual ~CNetMessage( void );
|
||||
CNetMessage();
|
||||
CNetMessage(NetMessageType type);
|
||||
virtual ~CNetMessage();
|
||||
|
||||
/**
|
||||
* Retrieves the message type.
|
||||
* @return Message type
|
||||
*/
|
||||
NetMessageType GetType( void ) const { return m_Type; }
|
||||
NetMessageType GetType() const { return m_Type; }
|
||||
|
||||
/**
|
||||
* Serialize the message into the specified buffer parameter. The size
|
||||
@ -58,7 +58,7 @@ public:
|
||||
* @return The position in the buffer right after the
|
||||
* serialized message
|
||||
*/
|
||||
virtual u8* Serialize( u8* pBuffer ) const;
|
||||
virtual u8* Serialize(u8* pBuffer) const;
|
||||
|
||||
/**
|
||||
* Deserializes the message from the specified buffer.
|
||||
@ -68,7 +68,7 @@ public:
|
||||
* @return The position in the buffer right after the
|
||||
* message or NULL if an error occured
|
||||
*/
|
||||
virtual const u8* Deserialize( const u8* pStart, const u8* pEnd );
|
||||
virtual const u8* Deserialize(const u8* pStart, const u8* pEnd);
|
||||
|
||||
/**
|
||||
* Retrieves the size in bytes of the serialized message. Before calling
|
||||
@ -84,17 +84,15 @@ public:
|
||||
*
|
||||
* @return The message as a string
|
||||
*/
|
||||
virtual CStr ToString( void ) const;
|
||||
virtual CStr ToString() const;
|
||||
|
||||
private:
|
||||
NetMessageType m_Type; // Message type
|
||||
};
|
||||
|
||||
/*
|
||||
CLASS : CNetMessageFactory
|
||||
DESCRIPTION : Creates messages from data received through the network
|
||||
NOTES : It follows the factory method pattern implementation
|
||||
*/
|
||||
/**
|
||||
* Creates messages from data received through the network.
|
||||
*/
|
||||
class CNetMessageFactory
|
||||
{
|
||||
public:
|
||||
@ -107,15 +105,7 @@ public:
|
||||
* @param scriptInterface Script instance to use when constructing scripted messages
|
||||
* @return The new message created
|
||||
*/
|
||||
static CNetMessage* CreateMessage( const void* pData, size_t dataSize, ScriptInterface& scriptInterface );
|
||||
|
||||
/**
|
||||
* Clone a message object into a new scripting context.
|
||||
* @param message message to clone (can come from any script context)
|
||||
* @param scriptInterface script context to use for the new message
|
||||
* @return new message, or NULL on failure
|
||||
*/
|
||||
static CNetMessage* CloneMessage( const CNetMessage* message, ScriptInterface& scriptInterface );
|
||||
static CNetMessage* CreateMessage(const void* pData, size_t dataSize, ScriptInterface& scriptInterface);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "NetStats.h"
|
||||
#include "NetTurnManager.h"
|
||||
|
||||
#include "lib/utf8.h"
|
||||
#include "lib/external_libraries/enet.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
@ -34,6 +35,13 @@
|
||||
#define DEFAULT_WELCOME_MESSAGE L"Welcome"
|
||||
#define MAX_CLIENTS 8
|
||||
|
||||
/**
|
||||
* enet_host_service timeout (msecs).
|
||||
* Smaller numbers may hurt performance; larger numbers will
|
||||
* hurt latency responding to messages from game thread.
|
||||
*/
|
||||
static const int HOST_SERVICE_TIMEOUT = 50;
|
||||
|
||||
CNetServer* g_NetServer = NULL;
|
||||
|
||||
static CStr DebugName(CNetServerSession* session)
|
||||
@ -45,8 +53,17 @@ static CStr DebugName(CNetServerSession* session)
|
||||
return "[" + session->GetGUID().substr(0, 8) + "...]";
|
||||
}
|
||||
|
||||
CNetServer::CNetServer() :
|
||||
m_ScriptInterface(new ScriptInterface("Engine", "Net server")), m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
|
||||
/*
|
||||
* XXX: We use some non-threadsafe functions from the worker thread.
|
||||
* See http://trac.wildfiregames.com/ticket/654
|
||||
* and http://trac.wildfiregames.com/ticket/653
|
||||
*/
|
||||
|
||||
CNetServerWorker::CNetServerWorker(int autostartPlayers) :
|
||||
m_AutostartPlayers(autostartPlayers),
|
||||
m_Shutdown(false),
|
||||
m_ScriptInterface(NULL),
|
||||
m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
|
||||
{
|
||||
m_State = SERVER_STATE_UNCONNECTED;
|
||||
|
||||
@ -56,8 +73,22 @@ CNetServer::CNetServer() :
|
||||
m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE;
|
||||
}
|
||||
|
||||
CNetServer::~CNetServer()
|
||||
CNetServerWorker::~CNetServerWorker()
|
||||
{
|
||||
if (m_State != SERVER_STATE_UNCONNECTED)
|
||||
{
|
||||
// Tell the thread to shut down
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_Shutdown = true;
|
||||
}
|
||||
|
||||
// Wait for it to shut down cleanly
|
||||
pthread_join(m_WorkerThread, NULL);
|
||||
}
|
||||
|
||||
// Clean up resources
|
||||
|
||||
delete m_Stats;
|
||||
|
||||
for (size_t i = 0; i < m_Sessions.size(); ++i)
|
||||
@ -72,12 +103,9 @@ CNetServer::~CNetServer()
|
||||
}
|
||||
|
||||
delete m_ServerTurnManager;
|
||||
|
||||
m_GameAttributes = CScriptValRooted(); // clear root before deleting its context
|
||||
delete m_ScriptInterface;
|
||||
}
|
||||
|
||||
bool CNetServer::SetupConnection()
|
||||
bool CNetServerWorker::SetupConnection()
|
||||
{
|
||||
debug_assert(m_State == SERVER_STATE_UNCONNECTED);
|
||||
debug_assert(!m_Host);
|
||||
@ -95,16 +123,20 @@ bool CNetServer::SetupConnection()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Stats = new CNetStatsTable(m_Host);
|
||||
m_Stats = new CNetStatsTable();
|
||||
if (CProfileViewer::IsInitialised())
|
||||
g_ProfileViewer.AddRootTable(m_Stats);
|
||||
|
||||
m_State = SERVER_STATE_PREGAME;
|
||||
|
||||
// Launch the worker thread
|
||||
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
|
||||
debug_assert(ret == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::SendMessage(ENetPeer* peer, const CNetMessage* message)
|
||||
bool CNetServerWorker::SendMessage(ENetPeer* peer, const CNetMessage* message)
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
|
||||
@ -113,7 +145,7 @@ bool CNetServer::SendMessage(ENetPeer* peer, const CNetMessage* message)
|
||||
return CNetHost::SendMessage(message, peer, DebugName(session).c_str());
|
||||
}
|
||||
|
||||
bool CNetServer::Broadcast(const CNetMessage* message)
|
||||
bool CNetServerWorker::Broadcast(const CNetMessage* message)
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
|
||||
@ -135,103 +167,178 @@ bool CNetServer::Broadcast(const CNetMessage* message)
|
||||
return ok;
|
||||
}
|
||||
|
||||
void CNetServer::Poll()
|
||||
void* CNetServerWorker::RunThread(void* data)
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
debug_SetThreadName("NetServer");
|
||||
|
||||
// Poll host for events
|
||||
ENetEvent event;
|
||||
while (enet_host_service(m_Host, &event, 0) > 0)
|
||||
static_cast<CNetServerWorker*>(data)->Run();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CNetServerWorker::Run()
|
||||
{
|
||||
// To avoid the need for JS_SetContextThread, we create and use and destroy
|
||||
// the script interface entirely within this network thread
|
||||
m_ScriptInterface = new ScriptInterface("Engine", "Net server");
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
{
|
||||
// Report the client address
|
||||
char hostname[256] = "(error)";
|
||||
enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
|
||||
LOGMESSAGE(L"Net server: Received connection from %hs:%u", hostname, event.peer->address.port);
|
||||
|
||||
if (m_State != SERVER_STATE_PREGAME)
|
||||
{
|
||||
enet_peer_disconnect(event.peer, NDR_SERVER_ALREADY_IN_GAME);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up a session object for this peer
|
||||
|
||||
CNetServerSession* session = new CNetServerSession(*this, event.peer);
|
||||
|
||||
m_Sessions.push_back(session);
|
||||
|
||||
SetupSession(session);
|
||||
|
||||
debug_assert(event.peer->data == NULL);
|
||||
event.peer->data = session;
|
||||
|
||||
HandleConnect(session);
|
||||
|
||||
if (!RunStep())
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
{
|
||||
// If there is an active session with this peer, then reset and delete it
|
||||
// Implement autostart mode
|
||||
if (m_State == SERVER_STATE_PREGAME && (int)m_PlayerAssignments.size() == m_AutostartPlayers)
|
||||
StartGame();
|
||||
|
||||
CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
|
||||
if (session)
|
||||
{
|
||||
LOGMESSAGE(L"Net server: Disconnected %hs", DebugName(session).c_str());
|
||||
|
||||
// Remove the session first, so we won't send player-update messages to it
|
||||
// when updating the FSM
|
||||
m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());
|
||||
|
||||
session->Update((uint)NMT_CONNECTION_LOST, NULL);
|
||||
|
||||
delete session;
|
||||
event.peer->data = NULL;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
{
|
||||
// If there is an active session with this peer, then process the message
|
||||
|
||||
CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
|
||||
if (session)
|
||||
{
|
||||
// Create message from raw data
|
||||
CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
|
||||
if (msg)
|
||||
{
|
||||
LOGMESSAGE(L"Net server: Received message %hs of size %lu from %hs", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());
|
||||
|
||||
HandleMessageReceive(msg, session);
|
||||
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
// Done using the packet
|
||||
enet_packet_destroy(event.packet);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Update profiler stats
|
||||
m_Stats->LatchHostState(m_Host);
|
||||
}
|
||||
|
||||
// Clear root before deleting its context
|
||||
m_GameAttributes = CScriptValRooted();
|
||||
|
||||
SAFE_DELETE(m_ScriptInterface);
|
||||
}
|
||||
|
||||
void CNetServer::Flush()
|
||||
bool CNetServerWorker::RunStep()
|
||||
{
|
||||
debug_assert(m_Host);
|
||||
// Check for messages from the game thread.
|
||||
// (Do as little work as possible while the mutex is held open,
|
||||
// to avoid performance problems and deadlocks.)
|
||||
|
||||
enet_host_flush(m_Host);
|
||||
std::vector<std::pair<int, CStr> > newAssignPlayer;
|
||||
std::vector<bool> newStartGame;
|
||||
std::vector<std::string> newGameAttributes;
|
||||
std::vector<u32> newTurnLength;
|
||||
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
|
||||
if (m_Shutdown)
|
||||
return false;
|
||||
|
||||
newStartGame.swap(m_StartGameQueue);
|
||||
newAssignPlayer.swap(m_AssignPlayerQueue);
|
||||
newGameAttributes.swap(m_GameAttributesQueue);
|
||||
newTurnLength.swap(m_TurnLengthQueue);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < newAssignPlayer.size(); ++i)
|
||||
AssignPlayer(newAssignPlayer[i].first, newAssignPlayer[i].second);
|
||||
|
||||
if (!newGameAttributes.empty())
|
||||
UpdateGameAttributes(GetScriptInterface().ParseJSON(newGameAttributes.back()));
|
||||
|
||||
if (!newTurnLength.empty())
|
||||
SetTurnLength(newTurnLength.back());
|
||||
|
||||
// Do StartGame last, so we have the most up-to-date game attributes when we start
|
||||
if (!newStartGame.empty())
|
||||
StartGame();
|
||||
|
||||
// Process network events:
|
||||
|
||||
ENetEvent event;
|
||||
int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
|
||||
if (status < 0)
|
||||
{
|
||||
LOGERROR(L"CNetServerWorker: enet_host_service failed (%d)", status);
|
||||
// TODO: notify game that the server has shut down
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
// Reached timeout with no events - try again
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process the event:
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
{
|
||||
// Report the client address
|
||||
char hostname[256] = "(error)";
|
||||
enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
|
||||
LOGMESSAGE(L"Net server: Received connection from %hs:%u", hostname, event.peer->address.port);
|
||||
|
||||
if (m_State != SERVER_STATE_PREGAME)
|
||||
{
|
||||
enet_peer_disconnect(event.peer, NDR_SERVER_ALREADY_IN_GAME);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up a session object for this peer
|
||||
|
||||
CNetServerSession* session = new CNetServerSession(*this, event.peer);
|
||||
|
||||
m_Sessions.push_back(session);
|
||||
|
||||
SetupSession(session);
|
||||
|
||||
debug_assert(event.peer->data == NULL);
|
||||
event.peer->data = session;
|
||||
|
||||
HandleConnect(session);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
{
|
||||
// If there is an active session with this peer, then reset and delete it
|
||||
|
||||
CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
|
||||
if (session)
|
||||
{
|
||||
LOGMESSAGE(L"Net server: Disconnected %hs", DebugName(session).c_str());
|
||||
|
||||
// Remove the session first, so we won't send player-update messages to it
|
||||
// when updating the FSM
|
||||
m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());
|
||||
|
||||
session->Update((uint)NMT_CONNECTION_LOST, NULL);
|
||||
|
||||
delete session;
|
||||
event.peer->data = NULL;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
{
|
||||
// If there is an active session with this peer, then process the message
|
||||
|
||||
CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
|
||||
if (session)
|
||||
{
|
||||
// Create message from raw data
|
||||
CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
|
||||
if (msg)
|
||||
{
|
||||
LOGMESSAGE(L"Net server: Received message %ls of size %lu from %hs", CStrW(msg->ToString()).c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());
|
||||
|
||||
HandleMessageReceive(msg, session);
|
||||
|
||||
delete msg;
|
||||
}
|
||||
}
|
||||
|
||||
// Done using the packet
|
||||
enet_packet_destroy(event.packet);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetServer::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
|
||||
void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
|
||||
{
|
||||
// Update FSM
|
||||
bool ok = session->Update(message->GetType(), (void*)message);
|
||||
@ -239,7 +346,7 @@ void CNetServer::HandleMessageReceive(const CNetMessage* message, CNetServerSess
|
||||
LOGERROR(L"Net server: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)session->GetCurrState());
|
||||
}
|
||||
|
||||
void CNetServer::SetupSession(CNetServerSession* session)
|
||||
void CNetServerWorker::SetupSession(CNetServerSession* session)
|
||||
{
|
||||
void* context = session;
|
||||
|
||||
@ -267,7 +374,7 @@ void CNetServer::SetupSession(CNetServerSession* session)
|
||||
session->SetFirstState(NSS_HANDSHAKE);
|
||||
}
|
||||
|
||||
bool CNetServer::HandleConnect(CNetServerSession* session)
|
||||
bool CNetServerWorker::HandleConnect(CNetServerSession* session)
|
||||
{
|
||||
CSrvHandshakeMessage handshake;
|
||||
handshake.m_Magic = PS_PROTOCOL_MAGIC;
|
||||
@ -276,7 +383,7 @@ bool CNetServer::HandleConnect(CNetServerSession* session)
|
||||
return session->SendMessage(&handshake);
|
||||
}
|
||||
|
||||
void CNetServer::OnUserJoin(CNetServerSession* session)
|
||||
void CNetServerWorker::OnUserJoin(CNetServerSession* session)
|
||||
{
|
||||
AddPlayer(session->GetGUID(), session->GetUserName());
|
||||
|
||||
@ -287,18 +394,14 @@ void CNetServer::OnUserJoin(CNetServerSession* session)
|
||||
CPlayerAssignmentMessage assignMessage;
|
||||
ConstructPlayerAssignmentMessage(assignMessage);
|
||||
session->SendMessage(&assignMessage);
|
||||
|
||||
OnAddPlayer();
|
||||
}
|
||||
|
||||
void CNetServer::OnUserLeave(CNetServerSession* session)
|
||||
void CNetServerWorker::OnUserLeave(CNetServerSession* session)
|
||||
{
|
||||
RemovePlayer(session->GetGUID());
|
||||
|
||||
OnRemovePlayer();
|
||||
}
|
||||
|
||||
void CNetServer::AddPlayer(const CStr& guid, const CStrW& name)
|
||||
void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
|
||||
{
|
||||
// Find the first free player ID
|
||||
|
||||
@ -322,16 +425,14 @@ void CNetServer::AddPlayer(const CStr& guid, const CStrW& name)
|
||||
SendPlayerAssignments();
|
||||
}
|
||||
|
||||
void CNetServer::RemovePlayer(const CStr& guid)
|
||||
void CNetServerWorker::RemovePlayer(const CStr& guid)
|
||||
{
|
||||
m_PlayerAssignments.erase(guid);
|
||||
|
||||
SendPlayerAssignments();
|
||||
|
||||
OnRemovePlayer();
|
||||
}
|
||||
|
||||
void CNetServer::AssignPlayer(int playerID, const CStr& guid)
|
||||
void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
|
||||
{
|
||||
// Remove anyone who's already assigned to this player
|
||||
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
|
||||
@ -347,7 +448,7 @@ void CNetServer::AssignPlayer(int playerID, const CStr& guid)
|
||||
SendPlayerAssignments();
|
||||
}
|
||||
|
||||
void CNetServer::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
|
||||
void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
|
||||
{
|
||||
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
|
||||
{
|
||||
@ -359,30 +460,30 @@ void CNetServer::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& mess
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServer::SendPlayerAssignments()
|
||||
void CNetServerWorker::SendPlayerAssignments()
|
||||
{
|
||||
CPlayerAssignmentMessage message;
|
||||
ConstructPlayerAssignmentMessage(message);
|
||||
Broadcast(&message);
|
||||
}
|
||||
|
||||
ScriptInterface& CNetServer::GetScriptInterface()
|
||||
ScriptInterface& CNetServerWorker::GetScriptInterface()
|
||||
{
|
||||
return *m_ScriptInterface;
|
||||
}
|
||||
|
||||
void CNetServer::SetTurnLength(u32 msecs)
|
||||
void CNetServerWorker::SetTurnLength(u32 msecs)
|
||||
{
|
||||
if (m_ServerTurnManager)
|
||||
m_ServerTurnManager->SetTurnLength(msecs);
|
||||
}
|
||||
|
||||
bool CNetServer::OnClientHandshake(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
|
||||
{
|
||||
debug_assert(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
if (server.m_State != SERVER_STATE_PREGAME)
|
||||
{
|
||||
@ -406,12 +507,12 @@ bool CNetServer::OnClientHandshake(void* context, CFsmEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::OnAuthenticate(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
|
||||
{
|
||||
debug_assert(event->GetType() == (uint)NMT_AUTHENTICATE);
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
if (server.m_State != SERVER_STATE_PREGAME)
|
||||
{
|
||||
@ -442,12 +543,12 @@ bool CNetServer::OnAuthenticate(void* context, CFsmEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::OnInGame(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnInGame(void* context, CFsmEvent* event)
|
||||
{
|
||||
// TODO: should split each of these cases into a separate method
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
CNetMessage* message = (CNetMessage*)event->GetParamRef();
|
||||
if (message->GetType() == (uint)NMT_SIMULATION_COMMAND)
|
||||
@ -475,12 +576,12 @@ bool CNetServer::OnInGame(void* context, CFsmEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::OnChat(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnChat(void* context, CFsmEvent* event)
|
||||
{
|
||||
debug_assert(event->GetType() == (uint)NMT_CHAT);
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
CChatMessage* message = (CChatMessage*)event->GetParamRef();
|
||||
|
||||
@ -491,31 +592,31 @@ bool CNetServer::OnChat(void* context, CFsmEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::OnLoadedGame(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
|
||||
{
|
||||
debug_assert(event->GetType() == (uint)NMT_LOADED_GAME);
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
server.CheckGameLoadStatus(session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetServer::OnDisconnect(void* context, CFsmEvent* event)
|
||||
bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
|
||||
{
|
||||
debug_assert(event->GetType() == (uint)NMT_CONNECTION_LOST);
|
||||
|
||||
CNetServerSession* session = (CNetServerSession*)context;
|
||||
CNetServer& server = session->GetServer();
|
||||
CNetServerWorker& server = session->GetServer();
|
||||
|
||||
server.OnUserLeave(session);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetServer::CheckGameLoadStatus(CNetServerSession* changedSession)
|
||||
void CNetServerWorker::CheckGameLoadStatus(CNetServerSession* changedSession)
|
||||
{
|
||||
for (size_t i = 0; i < m_Sessions.size(); ++i)
|
||||
{
|
||||
@ -529,7 +630,7 @@ void CNetServer::CheckGameLoadStatus(CNetServerSession* changedSession)
|
||||
m_State = SERVER_STATE_INGAME;
|
||||
}
|
||||
|
||||
void CNetServer::StartGame()
|
||||
void CNetServerWorker::StartGame()
|
||||
{
|
||||
m_ServerTurnManager = new CNetServerTurnManager(*this);
|
||||
|
||||
@ -546,7 +647,7 @@ void CNetServer::StartGame()
|
||||
Broadcast(&gameStart);
|
||||
}
|
||||
|
||||
void CNetServer::UpdateGameAttributes(const CScriptValRooted& attrs)
|
||||
void CNetServerWorker::UpdateGameAttributes(const CScriptValRooted& attrs)
|
||||
{
|
||||
m_GameAttributes = attrs;
|
||||
|
||||
@ -558,7 +659,7 @@ void CNetServer::UpdateGameAttributes(const CScriptValRooted& attrs)
|
||||
Broadcast(&gameSetupMessage);
|
||||
}
|
||||
|
||||
CStrW CNetServer::SanitisePlayerName(const CStrW& original)
|
||||
CStrW CNetServerWorker::SanitisePlayerName(const CStrW& original)
|
||||
{
|
||||
const size_t MAX_LENGTH = 32;
|
||||
|
||||
@ -580,7 +681,7 @@ CStrW CNetServer::SanitisePlayerName(const CStrW& original)
|
||||
return name;
|
||||
}
|
||||
|
||||
CStrW CNetServer::DeduplicatePlayerName(const CStrW& original)
|
||||
CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
|
||||
{
|
||||
CStrW name = original;
|
||||
|
||||
@ -604,3 +705,49 @@ CStrW CNetServer::DeduplicatePlayerName(const CStrW& original)
|
||||
name = original + L" (" + CStrW((unsigned long)id++) + L")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
CNetServer::CNetServer(int autostartPlayers) :
|
||||
m_Worker(new CNetServerWorker(autostartPlayers))
|
||||
{
|
||||
}
|
||||
|
||||
CNetServer::~CNetServer()
|
||||
{
|
||||
delete m_Worker;
|
||||
}
|
||||
|
||||
bool CNetServer::SetupConnection()
|
||||
{
|
||||
return m_Worker->SetupConnection();
|
||||
}
|
||||
|
||||
void CNetServer::AssignPlayer(int playerID, const CStr& guid)
|
||||
{
|
||||
CScopeLock lock(m_Worker->m_WorkerMutex);
|
||||
m_Worker->m_AssignPlayerQueue.push_back(std::make_pair(playerID, guid));
|
||||
}
|
||||
|
||||
void CNetServer::StartGame()
|
||||
{
|
||||
CScopeLock lock(m_Worker->m_WorkerMutex);
|
||||
m_Worker->m_StartGameQueue.push_back(true);
|
||||
}
|
||||
|
||||
void CNetServer::UpdateGameAttributes(const CScriptVal& attrs, ScriptInterface& scriptInterface)
|
||||
{
|
||||
// Pass the attributes as JSON, since that's the easiest safe
|
||||
// cross-thread way of passing script data
|
||||
std::string attrsJSON = scriptInterface.StringifyJSON(attrs.get(), false);
|
||||
|
||||
CScopeLock lock(m_Worker->m_WorkerMutex);
|
||||
m_Worker->m_GameAttributesQueue.push_back(attrsJSON);
|
||||
}
|
||||
|
||||
void CNetServer::SetTurnLength(u32 msecs)
|
||||
{
|
||||
CScopeLock lock(m_Worker->m_WorkerMutex);
|
||||
m_Worker->m_TurnLengthQueue.push_back(msecs);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "NetHost.h"
|
||||
|
||||
#include "ps/ThreadUtil.h"
|
||||
#include "scriptinterface/ScriptVal.h"
|
||||
|
||||
#include <vector>
|
||||
@ -31,10 +32,12 @@ class ScriptInterface;
|
||||
class CPlayerAssignmentMessage;
|
||||
class CNetStatsTable;
|
||||
|
||||
class CNetServerWorker;
|
||||
|
||||
enum NetServerState
|
||||
{
|
||||
// We haven't opened the port yet, we're just setting some stuff up.
|
||||
// This is probably equivalent to the first "Start Network Game" screen
|
||||
// The worker thread has not been started.
|
||||
SERVER_STATE_UNCONNECTED,
|
||||
|
||||
// The server is open and accepting connections. This is the screen where
|
||||
@ -80,43 +83,80 @@ enum NetServerSessionState
|
||||
};
|
||||
|
||||
/**
|
||||
* Network server.
|
||||
* Handles all the coordination between players.
|
||||
* Network server interface. Handles all the coordination between players.
|
||||
* One person runs this object, and every player (including the host) connects their CNetClient to it.
|
||||
*
|
||||
* TODO: ideally the ENet server would run in a separate thread so it can receive
|
||||
* and forward messages with minimal latency. But that's not supported now.
|
||||
* The actual work is performed by CNetServerWorker in a separate thread.
|
||||
*/
|
||||
class CNetServer
|
||||
{
|
||||
NONCOPYABLE(CNetServer);
|
||||
public:
|
||||
CNetServer();
|
||||
virtual ~CNetServer();
|
||||
|
||||
/**
|
||||
* Get the current server's connection/game state.
|
||||
* Construct a new network server.
|
||||
* @param autostartPlayers if positive then StartGame will be called automatically
|
||||
* once this many players are connected (intended for the command-line testing mode).
|
||||
*/
|
||||
NetServerState GetState() const { return m_State; }
|
||||
CNetServer(int autostartPlayers = -1);
|
||||
|
||||
~CNetServer();
|
||||
|
||||
/**
|
||||
* Begin listening for network connections.
|
||||
* This function is synchronous (it won't return until the connection is established).
|
||||
* @return true on success, false on error (e.g. port already in use)
|
||||
*/
|
||||
bool SetupConnection();
|
||||
|
||||
/**
|
||||
* Poll the connections for messages from clients and process them, and send
|
||||
* any queued messages.
|
||||
* This must be called frequently (i.e. once per frame).
|
||||
* Call from the GUI to update the player assignments.
|
||||
* The given GUID will be (re)assigned to the given player ID.
|
||||
* Any player currently using that ID will be unassigned.
|
||||
* The changes will be asynchronously propagated to all clients.
|
||||
*/
|
||||
virtual void Poll();
|
||||
void AssignPlayer(int playerID, const CStr& guid);
|
||||
|
||||
/**
|
||||
* Flush any queued outgoing network messages.
|
||||
* This should be called soon after sending a group of messages that may be batched together.
|
||||
* Call from the GUI to asynchronously notify all clients that they should start loading the game.
|
||||
*/
|
||||
void Flush();
|
||||
void StartGame();
|
||||
|
||||
/**
|
||||
* Call from the GUI to update the game setup attributes.
|
||||
* This must be called at least once before starting the game.
|
||||
* The changes will be asynchronously propagated to all clients.
|
||||
* @param attrs game attributes, in the script context of scriptInterface
|
||||
*/
|
||||
void UpdateGameAttributes(const CScriptVal& attrs, ScriptInterface& scriptInterface);
|
||||
|
||||
/**
|
||||
* Set the turn length to a fixed value.
|
||||
* TODO: we should replace this with some adapative lag-dependent computation.
|
||||
*/
|
||||
void SetTurnLength(u32 msecs);
|
||||
|
||||
private:
|
||||
CNetServerWorker* m_Worker;
|
||||
};
|
||||
|
||||
/**
|
||||
* Network server worker thread.
|
||||
* (This is run in a thread so that client/server communication is not delayed
|
||||
* by the host player's framerate - the only delay should be the network latency.)
|
||||
*
|
||||
* Thread-safety:
|
||||
* - SetupConnection and constructor/destructor must be called from the main thread.
|
||||
* - The main thread may push commands onto the Queue members,
|
||||
* while holding the m_WorkerMutex lock.
|
||||
* - Public functions (SendMessage, Broadcast) must be called from the network
|
||||
* server thread.
|
||||
*/
|
||||
class CNetServerWorker
|
||||
{
|
||||
NONCOPYABLE(CNetServerWorker);
|
||||
|
||||
public:
|
||||
// Public functions for CNetSession/CNetServerTurnManager to use:
|
||||
|
||||
/**
|
||||
* Send a message to the given network peer.
|
||||
@ -129,6 +169,18 @@ public:
|
||||
*/
|
||||
bool Broadcast(const CNetMessage* message);
|
||||
|
||||
private:
|
||||
friend class CNetServer;
|
||||
|
||||
CNetServerWorker(int autostartPlayers);
|
||||
~CNetServerWorker();
|
||||
|
||||
/**
|
||||
* Begin listening for network connections.
|
||||
* @return true on success, false on error (e.g. port already in use)
|
||||
*/
|
||||
bool SetupConnection();
|
||||
|
||||
/**
|
||||
* Call from the GUI to update the player assignments.
|
||||
* The given GUID will be (re)assigned to the given player ID.
|
||||
@ -171,19 +223,9 @@ public:
|
||||
*/
|
||||
void SetTurnLength(u32 msecs);
|
||||
|
||||
protected:
|
||||
/// Callback for autostart; called when a player has finished connecting
|
||||
virtual void OnAddPlayer() { }
|
||||
/// Callback for autostart; called when a player has left the game
|
||||
virtual void OnRemovePlayer() { }
|
||||
|
||||
private:
|
||||
void AddPlayer(const CStr& guid, const CStrW& name);
|
||||
void RemovePlayer(const CStr& guid);
|
||||
void SendPlayerAssignments();
|
||||
PlayerAssignmentMap m_PlayerAssignments;
|
||||
|
||||
CScriptValRooted m_GameAttributes;
|
||||
|
||||
void SetupSession(CNetServerSession* session);
|
||||
bool HandleConnect(CNetServerSession* session);
|
||||
@ -204,13 +246,21 @@ private:
|
||||
|
||||
void HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
|
||||
|
||||
|
||||
/**
|
||||
* Internal script context for (de)serializing script messages.
|
||||
* Internal script context for (de)serializing script messages,
|
||||
* and for storing game attributes.
|
||||
* (TODO: we shouldn't bother deserializing (except for debug printing of messages),
|
||||
* we should just forward messages blindly and efficiently.)
|
||||
*/
|
||||
ScriptInterface* m_ScriptInterface;
|
||||
|
||||
PlayerAssignmentMap m_PlayerAssignments;
|
||||
|
||||
CScriptValRooted m_GameAttributes;
|
||||
|
||||
int m_AutostartPlayers;
|
||||
|
||||
ENetHost* m_Host;
|
||||
std::vector<CNetServerSession*> m_Sessions;
|
||||
|
||||
@ -224,6 +274,24 @@ private:
|
||||
u32 m_NextHostID;
|
||||
|
||||
CNetServerTurnManager* m_ServerTurnManager;
|
||||
|
||||
private:
|
||||
// Thread-related stuff:
|
||||
|
||||
static void* RunThread(void* data);
|
||||
void Run();
|
||||
bool RunStep();
|
||||
|
||||
pthread_t m_WorkerThread;
|
||||
CMutex m_WorkerMutex;
|
||||
|
||||
bool m_Shutdown; // protected by m_WorkerMutex
|
||||
|
||||
// Queues for messages sent by the game thread:
|
||||
std::vector<std::pair<int, CStr> > m_AssignPlayerQueue; // protected by m_WorkerMutex
|
||||
std::vector<bool> m_StartGameQueue; // protected by m_WorkerMutex
|
||||
std::vector<std::string> m_GameAttributesQueue; // protected by m_WorkerMutex
|
||||
std::vector<u32> m_TurnLengthQueue; // protected by m_WorkerMutex
|
||||
};
|
||||
|
||||
/// Global network server for the standard game
|
||||
|
@ -161,7 +161,7 @@ bool CNetClientSession::SendMessage(const CNetMessage* message)
|
||||
|
||||
|
||||
|
||||
CNetServerSession::CNetServerSession(CNetServer& server, ENetPeer* peer) :
|
||||
CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) :
|
||||
m_Server(server), m_Peer(peer)
|
||||
{
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
#include "scriptinterface/ScriptVal.h"
|
||||
|
||||
class CNetClient;
|
||||
class CNetServer;
|
||||
class CNetServerWorker;
|
||||
|
||||
class CNetStatsTable;
|
||||
|
||||
@ -84,15 +84,18 @@ private:
|
||||
* The server's end of a network session.
|
||||
* Represents an abstraction of the state of the client, storing all the per-client data
|
||||
* needed by the server.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - This is constructed and used by CNetServerWorker in the network server thread.
|
||||
*/
|
||||
class CNetServerSession : public CFsm
|
||||
{
|
||||
NONCOPYABLE(CNetServerSession);
|
||||
|
||||
public:
|
||||
CNetServerSession(CNetServer& server, ENetPeer* peer);
|
||||
CNetServerSession(CNetServerWorker& server, ENetPeer* peer);
|
||||
|
||||
CNetServer& GetServer() { return m_Server; }
|
||||
CNetServerWorker& GetServer() { return m_Server; }
|
||||
|
||||
const CStr& GetGUID() const { return m_GUID; }
|
||||
void SetGUID(const CStr& guid) { m_GUID = guid; }
|
||||
@ -124,7 +127,7 @@ public:
|
||||
bool SendMessage(const CNetMessage* message);
|
||||
|
||||
private:
|
||||
CNetServer& m_Server;
|
||||
CNetServerWorker& m_Server;
|
||||
|
||||
ENetPeer* m_Peer;
|
||||
|
||||
|
@ -38,12 +38,12 @@ enum
|
||||
};
|
||||
|
||||
CNetStatsTable::CNetStatsTable(const ENetPeer* peer)
|
||||
: m_Host(NULL), m_Peer(peer)
|
||||
: m_Peer(peer)
|
||||
{
|
||||
}
|
||||
|
||||
CNetStatsTable::CNetStatsTable(const ENetHost* host)
|
||||
: m_Host(host), m_Peer(NULL)
|
||||
CNetStatsTable::CNetStatsTable()
|
||||
: m_Peer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
@ -54,10 +54,10 @@ CStr CNetStatsTable::GetName()
|
||||
|
||||
CStr CNetStatsTable::GetTitle()
|
||||
{
|
||||
if (m_Host)
|
||||
return "Network host statistics";
|
||||
else
|
||||
if (m_Peer)
|
||||
return "Network client statistics";
|
||||
else
|
||||
return "Network host statistics";
|
||||
}
|
||||
|
||||
size_t CNetStatsTable::GetNumberRows()
|
||||
@ -69,25 +69,33 @@ const std::vector<ProfileColumn>& CNetStatsTable::GetColumns()
|
||||
{
|
||||
m_ColumnDescriptions.clear();
|
||||
m_ColumnDescriptions.push_back(ProfileColumn("Name", 200));
|
||||
if (m_Host)
|
||||
{
|
||||
for (size_t i = 0; i < m_Host->peerCount; ++i)
|
||||
m_ColumnDescriptions.push_back(ProfileColumn("Peer "+CStr((unsigned long)i), 80));
|
||||
}
|
||||
else if (m_Peer)
|
||||
if (m_Peer)
|
||||
{
|
||||
m_ColumnDescriptions.push_back(ProfileColumn("Value", 80));
|
||||
}
|
||||
else
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
for (size_t i = 0; i < m_LatchedData.size(); ++i)
|
||||
m_ColumnDescriptions.push_back(ProfileColumn("Peer "+CStr((unsigned long)i), 80));
|
||||
}
|
||||
return m_ColumnDescriptions;
|
||||
}
|
||||
|
||||
CStr CNetStatsTable::GetCellText(size_t row, size_t col)
|
||||
{
|
||||
#define ROW(id, title, member) \
|
||||
// Return latched data, if we have any
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
if (col > 0 && m_LatchedData.size() > col-1 && m_LatchedData[col-1].size() > row)
|
||||
return m_LatchedData[col-1][row];
|
||||
}
|
||||
|
||||
#define ROW(id, title, member) \
|
||||
case id: \
|
||||
if (col == 0) return title; \
|
||||
if (m_Peer) return CStr(m_Peer->member); \
|
||||
if (m_Host && col <= m_Host->peerCount) return CStr(m_Host->peers[col-1].member); \
|
||||
return "???"
|
||||
|
||||
switch(row)
|
||||
@ -107,9 +115,38 @@ CStr CNetStatsTable::GetCellText(size_t row, size_t col)
|
||||
default:
|
||||
return "???";
|
||||
}
|
||||
|
||||
#undef ROW
|
||||
}
|
||||
|
||||
AbstractProfileTable* CNetStatsTable::GetChild(size_t UNUSED(row))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CNetStatsTable::LatchHostState(const ENetHost* host)
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
#define ROW(id, title, member) \
|
||||
m_LatchedData[i].push_back(CStr(host->peers[i].member));
|
||||
|
||||
m_LatchedData.clear();
|
||||
m_LatchedData.resize(host->peerCount);
|
||||
|
||||
for (size_t i = 0; i < host->peerCount; ++i)
|
||||
{
|
||||
ROW(Row_InData, "incoming bytes", incomingDataTotal);
|
||||
ROW(Row_OutData, "outgoing bytes", outgoingDataTotal);
|
||||
ROW(Row_LastSendTime, "last send time", lastSendTime);
|
||||
ROW(Row_LastRecvTime, "last receive time", lastReceiveTime);
|
||||
ROW(Row_NextTimeout, "next timeout", nextTimeout);
|
||||
ROW(Row_PacketsSent, "packets sent", packetsSent);
|
||||
ROW(Row_PacketsLost, "packets lost", packetsLost);
|
||||
ROW(Row_LastRTT, "last RTT", lastRoundTripTime);
|
||||
ROW(Row_RTT, "mean RTT", roundTripTime);
|
||||
ROW(Row_MTU, "MTU", mtu);
|
||||
ROW(Row_ReliableInTransit, "reliable data in transit", reliableDataInTransit);
|
||||
}
|
||||
#undef ROW
|
||||
}
|
||||
|
@ -19,15 +19,25 @@
|
||||
#define INCLUDED_NETSTATS
|
||||
|
||||
#include "ps/ProfileViewer.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
typedef struct _ENetPeer ENetPeer;
|
||||
typedef struct _ENetHost ENetHost;
|
||||
|
||||
/**
|
||||
* ENet connection statistics profiler table.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - Must be constructed in the main thread (to match the profiler).
|
||||
* - In host mode, the host can be running in a separate thread;
|
||||
* call LatchHostState from that thread periodically to safely
|
||||
* update our displayed copy of the data.
|
||||
*/
|
||||
class CNetStatsTable : public AbstractProfileTable
|
||||
{
|
||||
NONCOPYABLE(CNetStatsTable);
|
||||
public:
|
||||
CNetStatsTable(const ENetHost* host);
|
||||
CNetStatsTable();
|
||||
CNetStatsTable(const ENetPeer* peer);
|
||||
|
||||
virtual CStr GetName();
|
||||
@ -37,10 +47,14 @@ public:
|
||||
virtual CStr GetCellText(size_t row, size_t col);
|
||||
virtual AbstractProfileTable* GetChild(size_t row);
|
||||
|
||||
void LatchHostState(const ENetHost* host);
|
||||
|
||||
private:
|
||||
const ENetHost* m_Host;
|
||||
const ENetPeer* m_Peer;
|
||||
std::vector<ProfileColumn> m_ColumnDescriptions;
|
||||
|
||||
CMutex m_Mutex;
|
||||
std::vector<std::vector<CStr> > m_LatchedData; // protected by m_Mutex
|
||||
};
|
||||
|
||||
#endif // INCLUDED_NETSTATS
|
||||
|
@ -294,7 +294,7 @@ void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
|
||||
|
||||
|
||||
|
||||
CNetServerTurnManager::CNetServerTurnManager(CNetServer& server) :
|
||||
CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
|
||||
m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
|
||||
{
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
class CNetServer;
|
||||
class CNetServerWorker;
|
||||
class CNetClient;
|
||||
class CSimulationMessage;
|
||||
class CSimulation2;
|
||||
@ -183,12 +183,15 @@ protected:
|
||||
* The server-side counterpart to CNetClientTurnManager.
|
||||
* Records the turn state of each client, and sends turn advancement messages
|
||||
* when all clients are ready.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - This is constructed and used by CNetServerWorker in the network server thread.
|
||||
*/
|
||||
class CNetServerTurnManager
|
||||
{
|
||||
NONCOPYABLE(CNetServerTurnManager);
|
||||
public:
|
||||
CNetServerTurnManager(CNetServer& server);
|
||||
CNetServerTurnManager(CNetServerWorker& server);
|
||||
|
||||
void NotifyFinishedClientCommands(int client, u32 turn);
|
||||
|
||||
@ -215,7 +218,7 @@ protected:
|
||||
// Current turn length
|
||||
u32 m_TurnLength;
|
||||
|
||||
CNetServer& m_NetServer;
|
||||
CNetServerWorker& m_NetServer;
|
||||
};
|
||||
|
||||
#endif // INCLUDED_NETTURNMANAGER
|
||||
|
@ -17,8 +17,10 @@
|
||||
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "graphics/TerrainTextureManager.h"
|
||||
#include "lib/external_libraries/enet.h"
|
||||
#include "lib/external_libraries/sdl.h"
|
||||
#include "lib/tex/tex.h"
|
||||
#include "network/NetServer.h"
|
||||
#include "network/NetClient.h"
|
||||
#include "network/NetTurnManager.h"
|
||||
@ -40,6 +42,12 @@ public:
|
||||
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/L"_testcache"));
|
||||
CXeromyces::Startup();
|
||||
|
||||
// Need some stuff for terrain movement costs:
|
||||
// (TODO: this ought to be independent of any graphics code)
|
||||
tex_codec_register_all();
|
||||
new CTerrainTextureManager;
|
||||
g_TexMan.LoadTerrainTextures();
|
||||
|
||||
enet_initialize();
|
||||
}
|
||||
|
||||
@ -47,6 +55,9 @@ public:
|
||||
{
|
||||
enet_deinitialize();
|
||||
|
||||
delete &g_TexMan;
|
||||
tex_codec_unregister_all();
|
||||
|
||||
CXeromyces::Terminate();
|
||||
g_VFS.reset();
|
||||
DeleteDirectory(DataDir()/L"_testcache");
|
||||
@ -69,11 +80,10 @@ public:
|
||||
for (size_t i = 0; ; ++i)
|
||||
{
|
||||
// debug_printf(L".");
|
||||
server.Poll();
|
||||
for (size_t j = 0; j < clients.size(); ++j)
|
||||
clients[j]->Poll();
|
||||
|
||||
if (server.GetState() == SERVER_STATE_PREGAME && clients_are_all(clients, NCS_PREGAME))
|
||||
if (clients_are_all(clients, NCS_PREGAME))
|
||||
break;
|
||||
|
||||
if (i > 20)
|
||||
@ -86,6 +96,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void disconnect(CNetServer& server, const std::vector<CNetClient*>& clients)
|
||||
{
|
||||
for (size_t i = 0; ; ++i)
|
||||
@ -107,12 +118,12 @@ public:
|
||||
SDL_Delay(100);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void wait(CNetServer& server, const std::vector<CNetClient*>& clients, size_t msecs)
|
||||
void wait(const std::vector<CNetClient*>& clients, size_t msecs)
|
||||
{
|
||||
for (size_t i = 0; i < msecs/10; ++i)
|
||||
{
|
||||
server.Poll();
|
||||
for (size_t j = 0; j < clients.size(); ++j)
|
||||
clients[j]->Poll();
|
||||
|
||||
@ -125,6 +136,7 @@ public:
|
||||
// This doesn't actually test much, it just runs a very quick multiplayer game
|
||||
// and prints a load of debug output so you can see if anything funny's going on
|
||||
|
||||
ScriptInterface scriptInterface("Engine");
|
||||
TestStdoutLogger logger;
|
||||
|
||||
std::vector<CNetClient*> clients;
|
||||
@ -136,8 +148,8 @@ public:
|
||||
CNetServer server;
|
||||
|
||||
CScriptValRooted attrs;
|
||||
server.GetScriptInterface().Eval("({map:'_default',thing:'example'})", attrs);
|
||||
server.UpdateGameAttributes(attrs);
|
||||
scriptInterface.Eval("({mapType:'scenario',map:'_default',thing:'example'})", attrs);
|
||||
server.UpdateGameAttributes(attrs.get(), scriptInterface);
|
||||
|
||||
CNetClient client1(&client1Game);
|
||||
CNetClient client2(&client2Game);
|
||||
@ -151,7 +163,6 @@ public:
|
||||
debug_printf(L"%ls", client1.TestReadGuiMessages().c_str());
|
||||
|
||||
server.StartGame();
|
||||
server.Poll();
|
||||
SDL_Delay(100);
|
||||
for (size_t j = 0; j < clients.size(); ++j)
|
||||
{
|
||||
@ -160,7 +171,7 @@ public:
|
||||
clients[j]->LoadFinished();
|
||||
}
|
||||
|
||||
wait(server, clients, 100);
|
||||
wait(clients, 100);
|
||||
|
||||
{
|
||||
CScriptValRooted cmd;
|
||||
@ -174,14 +185,14 @@ public:
|
||||
client2Game.GetTurnManager()->PostCommand(cmd);
|
||||
}
|
||||
|
||||
wait(server, clients, 100);
|
||||
wait(clients, 100);
|
||||
client1Game.GetTurnManager()->Update(1.0f);
|
||||
client2Game.GetTurnManager()->Update(1.0f);
|
||||
client3Game.GetTurnManager()->Update(1.0f);
|
||||
wait(server, clients, 100);
|
||||
wait(clients, 100);
|
||||
client1Game.GetTurnManager()->Update(1.0f);
|
||||
client2Game.GetTurnManager()->Update(1.0f);
|
||||
client3Game.GetTurnManager()->Update(1.0f);
|
||||
wait(server, clients, 100);
|
||||
wait(clients, 100);
|
||||
}
|
||||
};
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
#include "scripting/ScriptingHost.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
#define LOG_CATEGORY L"Console"
|
||||
|
||||
@ -44,7 +45,6 @@ CConsole* g_Console = 0;
|
||||
|
||||
CConsole::CConsole()
|
||||
{
|
||||
|
||||
m_bToggle = false;
|
||||
m_bVisible = false;
|
||||
|
||||
@ -56,25 +56,8 @@ CConsole::CConsole()
|
||||
m_iMsgHistPos = 1;
|
||||
m_charsPerPage=0;
|
||||
|
||||
InsertMessage(L"[ 0 A.D. Console v0.12 ] type \"\\info\" for help");
|
||||
InsertMessage(L"[ 0 A.D. Console v0.14 ]");
|
||||
InsertMessage(L"");
|
||||
|
||||
if (FileExists(L"gui/text/help.txt"))
|
||||
{
|
||||
shared_ptr<u8> buf; size_t size;
|
||||
if ( g_VFS->LoadFile(L"gui/text/help.txt", buf, size) < 0 )
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CATEGORY, L"Help file not found for console");
|
||||
return;
|
||||
}
|
||||
// TODO: read in text mode, or at least get rid of the \r\n somehow
|
||||
// TODO: maybe the help file should be UTF-8 - we assume it's iso-8859-1 here
|
||||
m_helpText = CStrW((const char*)buf.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertMessage(L"No help file found.");
|
||||
}
|
||||
}
|
||||
|
||||
CConsole::~CConsole()
|
||||
@ -218,7 +201,7 @@ void CConsole::Render()
|
||||
}
|
||||
|
||||
|
||||
void CConsole::DrawWindow(void)
|
||||
void CConsole::DrawWindow()
|
||||
{
|
||||
// TODO: Add texturing
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
@ -255,11 +238,14 @@ void CConsole::DrawWindow(void)
|
||||
}
|
||||
|
||||
|
||||
void CConsole::DrawHistory(void) {
|
||||
void CConsole::DrawHistory()
|
||||
{
|
||||
int i = 1;
|
||||
|
||||
std::deque<std::wstring>::iterator Iter; //History iterator
|
||||
|
||||
CScopeLock lock(m_Mutex); // needed for safe access to m_deqMsgHistory
|
||||
|
||||
glPushMatrix();
|
||||
glColor3f(1.0f, 1.0f, 1.0f); //Set color of text
|
||||
glTranslatef(9.0f, (float)m_iFontOffset, 0.0f); //move away from the border
|
||||
@ -390,6 +376,8 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked )
|
||||
case SDLK_HOME:
|
||||
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
|
||||
{
|
||||
CScopeLock lock(m_Mutex); // needed for safe access to m_deqMsgHistory
|
||||
|
||||
int linesShown = (int)m_fHeight/m_iFontHeight - 4;
|
||||
m_iMsgHistPos = clamp((int)m_deqMsgHistory.size() - linesShown, 1, (int)m_deqMsgHistory.size());
|
||||
}
|
||||
@ -487,8 +475,12 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked )
|
||||
|
||||
// BEGIN: Message History Lookup
|
||||
case SDLK_PAGEUP:
|
||||
{
|
||||
CScopeLock lock(m_Mutex); // needed for safe access to m_deqMsgHistory
|
||||
|
||||
if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++;
|
||||
return;
|
||||
}
|
||||
|
||||
case SDLK_PAGEDOWN:
|
||||
if (m_iMsgHistPos != 1) m_iMsgHistPos--;
|
||||
@ -563,13 +555,17 @@ void CConsole::InsertMessageRaw(const CStrW& message)
|
||||
// Split into lines and add each one individually
|
||||
oldNewline = 0;
|
||||
|
||||
while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
|
||||
{
|
||||
distance -= oldNewline;
|
||||
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
|
||||
oldNewline += distance+1;
|
||||
CScopeLock lock(m_Mutex); // needed for safe access to m_deqMsgHistory
|
||||
|
||||
while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
|
||||
{
|
||||
distance -= oldNewline;
|
||||
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
|
||||
oldNewline += distance+1;
|
||||
}
|
||||
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
|
||||
}
|
||||
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
|
||||
}
|
||||
|
||||
const wchar_t* CConsole::GetBuffer()
|
||||
@ -608,64 +604,11 @@ void CConsole::ProcessBuffer(const wchar_t* szLine)
|
||||
SaveHistory(); // Do this each line for the moment; if a script causes
|
||||
// a crash it's a useful record.
|
||||
|
||||
wchar_t szCommand[CONSOLE_BUFFER_SIZE] = { 0 };
|
||||
// Process it as JavaScript
|
||||
|
||||
if (szLine[0] == '\\')
|
||||
{
|
||||
if (swscanf(szLine, L"\\%ls", szCommand) != 1)
|
||||
return;
|
||||
|
||||
Trim(szCommand);
|
||||
ToLower(szCommand);
|
||||
|
||||
if (!wcscmp(szCommand, L"info"))
|
||||
{
|
||||
InsertMessage(L"");
|
||||
InsertMessage(L"[Information]");
|
||||
InsertMessage(L" -View commands \"\\commands\"");
|
||||
InsertMessage(L" -Call command \"\\<command>\"");
|
||||
InsertMessage(L" -Say \"<string>\"");
|
||||
InsertMessage(L" -Help - Lists functions usable from console");
|
||||
InsertMessage(L"");
|
||||
}
|
||||
else if (!wcscmp(szCommand, L"commands"))
|
||||
{
|
||||
InsertMessage(L"");
|
||||
InsertMessage(L"[Commands]");
|
||||
|
||||
InsertMessage(L" (none registered)");
|
||||
|
||||
InsertMessage(L"");
|
||||
}
|
||||
else if (! (wcscmp(szCommand, L"Help") && wcscmp(szCommand, L"help")) )
|
||||
{
|
||||
InsertMessage(L"");
|
||||
InsertMessage(L"[Help]");
|
||||
InsertMessageRaw(m_helpText);
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertMessage(L"unknown command <%ls>", szCommand);
|
||||
}
|
||||
}
|
||||
else if (szLine[0] == ':' || szLine[0] == '?')
|
||||
{
|
||||
// Process it as JavaScript
|
||||
|
||||
jsval rval = g_ScriptingHost.ExecuteScript( szLine+1, L"Console" );
|
||||
if (szLine[0] == '?' && rval)
|
||||
{
|
||||
try {
|
||||
InsertMessage( L"%ls", g_ScriptingHost.ValueToUCString( rval ).c_str() );
|
||||
} catch (PSERROR_Scripting_ConversionFailed) {
|
||||
InsertMessage( L"%hs", "<error converting return value to string>" );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SendChatMessage(szLine);
|
||||
}
|
||||
jsval rval = g_ScriptingHost.ExecuteScript(szLine, L"Console");
|
||||
if (!JSVAL_IS_VOID(rval))
|
||||
InsertMessage(L"%ls", g_ScriptingHost.GetScriptInterface().ToString(rval).c_str());
|
||||
}
|
||||
|
||||
void CConsole::LoadHistory()
|
||||
@ -716,15 +659,6 @@ void CConsole::SaveHistory()
|
||||
g_VFS->CreateFile(m_sHistoryFile, buffer.Data(), buffer.Size());
|
||||
}
|
||||
|
||||
void CConsole::SendChatMessage(const wchar_t *pText)
|
||||
{
|
||||
if (g_NetClient)
|
||||
{
|
||||
// TODO
|
||||
// g_NetClient3->SendChatMessage(pText);
|
||||
}
|
||||
}
|
||||
|
||||
void CConsole::ReceivedChatMessage(const wchar_t *szSender, const wchar_t *szMessage)
|
||||
{
|
||||
InsertMessage(L"%ls: %ls", szSender, szMessage);
|
||||
|
@ -24,10 +24,10 @@
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
#include "CStr.h"
|
||||
|
||||
#include "lib/input.h"
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
#ifndef INCLUDED_CCONSOLE
|
||||
#define INCLUDED_CCONSOLE
|
||||
@ -37,6 +37,13 @@
|
||||
|
||||
#define CONSOLE_FONT L"mono-10"
|
||||
|
||||
/**
|
||||
* In-game console.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - Expected to be constructed/destructed in the main thread.
|
||||
* - InsertMessage may be called from any thread while the object is alive.
|
||||
*/
|
||||
class CConsole
|
||||
{
|
||||
public:
|
||||
@ -56,7 +63,6 @@ public:
|
||||
void InsertMessage(const wchar_t* szMessage, ...) WPRINTF_ARGS(2);
|
||||
void InsertChar(const int szChar, const wchar_t cooked);
|
||||
|
||||
void SendChatMessage(const wchar_t *szMessage);
|
||||
void ReceivedChatMessage(const wchar_t *pSender, const wchar_t *szMessage);
|
||||
|
||||
void SetBuffer(const wchar_t* szMessage);
|
||||
@ -75,6 +81,9 @@ public:
|
||||
size_t m_charsPerPage;
|
||||
|
||||
private:
|
||||
// Lock for all state modified by InsertMessage
|
||||
CMutex m_Mutex;
|
||||
|
||||
float m_fX;
|
||||
float m_fY;
|
||||
|
||||
@ -85,42 +94,38 @@ private:
|
||||
// allows implementing other animations than sliding, e.g. fading in/out.
|
||||
float m_fVisibleFrac;
|
||||
|
||||
CStrW m_helpText;
|
||||
|
||||
std::deque<std::wstring> m_deqMsgHistory;
|
||||
std::deque<std::wstring> m_deqMsgHistory; // protected by m_Mutex
|
||||
std::deque<std::wstring> m_deqBufHistory;
|
||||
|
||||
int m_iMsgHistPos;
|
||||
|
||||
wchar_t* m_szBuffer;
|
||||
wchar_t* m_szBuffer;
|
||||
int m_iBufferPos;
|
||||
int m_iBufferLength;
|
||||
|
||||
VfsPath m_sHistoryFile;
|
||||
int m_MaxHistoryLines;
|
||||
|
||||
bool m_bFocus;
|
||||
bool m_bFocus;
|
||||
bool m_bVisible; // console is to be drawn
|
||||
bool m_bToggle; // show/hide animation is currently active
|
||||
|
||||
void ToLower(wchar_t* szMessage, size_t iSize = 0);
|
||||
void Trim(wchar_t* szMessage, const wchar_t cChar = 32, size_t iSize = 0);
|
||||
|
||||
void DrawHistory(void);
|
||||
void DrawWindow(void);
|
||||
void DrawBuffer(void);
|
||||
void DrawCursor(void);
|
||||
void DrawHistory();
|
||||
void DrawWindow();
|
||||
void DrawBuffer();
|
||||
void DrawCursor();
|
||||
|
||||
bool IsEOB(void) {return (m_iBufferPos == m_iBufferLength);}; //Is end of Buffer?
|
||||
bool IsBOB(void) {return (m_iBufferPos == 0);}; //Is beginning of Buffer?
|
||||
bool IsFull(void) {return (m_iBufferLength == CONSOLE_BUFFER_SIZE);};
|
||||
bool IsEmpty(void) {return (m_iBufferLength == 0);};
|
||||
bool IsEOB() { return (m_iBufferPos == m_iBufferLength); } // Is end of Buffer?
|
||||
bool IsBOB() { return (m_iBufferPos == 0); } // Is beginning of Buffer?
|
||||
bool IsFull() { return (m_iBufferLength == CONSOLE_BUFFER_SIZE); }
|
||||
bool IsEmpty() { return (m_iBufferLength == 0); }
|
||||
|
||||
void InsertBuffer(void){InsertMessage(L"%ls", m_szBuffer);};
|
||||
void ProcessBuffer(const wchar_t* szLine);
|
||||
void ProcessBuffer(const wchar_t* szLine);
|
||||
|
||||
// Insert message without printf-style formatting, and without
|
||||
// length limits
|
||||
// Insert message without printf-style formatting, and without length limits
|
||||
void InsertMessageRaw(const CStrW& message);
|
||||
|
||||
void LoadHistory();
|
||||
|
@ -114,7 +114,7 @@ void CLogger::Init()
|
||||
*m_InterestingLog << html_header0 << "Main log (warnings and errors only)" << html_header1;
|
||||
}
|
||||
|
||||
CLogger::~CLogger ()
|
||||
CLogger::~CLogger()
|
||||
{
|
||||
char buffer[128];
|
||||
sprintf_s(buffer, ARRAY_SIZE(buffer), " with %d message(s), %d error(s) and %d warning(s).", m_NumberOfMessages,m_NumberOfErrors,m_NumberOfWarnings);
|
||||
@ -154,6 +154,8 @@ void CLogger::WriteMessage(const wchar_t* message)
|
||||
{
|
||||
std::string cmessage = ToHTML(message);
|
||||
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
++m_NumberOfMessages;
|
||||
// if (m_UseDebugPrintf)
|
||||
// debug_printf(L"MESSAGE: %ls\n", message);
|
||||
@ -169,6 +171,8 @@ void CLogger::WriteError(const wchar_t* message)
|
||||
{
|
||||
std::string cmessage = ToHTML(message);
|
||||
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
++m_NumberOfErrors;
|
||||
if (m_UseDebugPrintf)
|
||||
debug_printf(L"ERROR: %ls\n", message);
|
||||
@ -187,6 +191,8 @@ void CLogger::WriteWarning(const wchar_t* message)
|
||||
{
|
||||
std::string cmessage = ToHTML(message);
|
||||
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
++m_NumberOfWarnings;
|
||||
if (m_UseDebugPrintf)
|
||||
debug_printf(L"WARNING: %ls\n", message);
|
||||
@ -205,11 +211,11 @@ void CLogger::WriteWarning(const wchar_t* message)
|
||||
// -- This function has not been removed because the build would break.
|
||||
void CLogger::LogUsingMethod(ELogMethod method, const wchar_t* message)
|
||||
{
|
||||
if(method == Normal)
|
||||
if (method == Normal)
|
||||
WriteMessage(message);
|
||||
else if(method == Error)
|
||||
else if (method == Error)
|
||||
WriteError(message);
|
||||
else if(method == Warning)
|
||||
else if (method == Warning)
|
||||
WriteWarning(message);
|
||||
else
|
||||
WriteMessage(message);
|
||||
@ -232,31 +238,6 @@ void CLogger::Log(ELogMethod method, const wchar_t* UNUSED(category), const wcha
|
||||
LogUsingMethod(method, buffer);
|
||||
}
|
||||
|
||||
// -- This function has not been removed because the build would break.
|
||||
void CLogger::LogOnce(ELogMethod method, const wchar_t* UNUSED(category), const wchar_t* fmt, ...)
|
||||
{
|
||||
va_list argp;
|
||||
wchar_t buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
va_start(argp, fmt);
|
||||
if (sys_vswprintf(buffer, ARRAY_SIZE(buffer), fmt, argp) == -1)
|
||||
{
|
||||
// Buffer too small - ensure the string is nicely terminated
|
||||
wcscpy_s(buffer+ARRAY_SIZE(buffer)-4, 4, L"...");
|
||||
}
|
||||
va_end(argp);
|
||||
|
||||
std::wstring message(buffer);
|
||||
|
||||
// If this message has already been logged, ignore it
|
||||
if (m_LoggedOnce.find(message) != m_LoggedOnce.end())
|
||||
return;
|
||||
|
||||
// If not, mark it as having been logged and then log it
|
||||
m_LoggedOnce.insert(message);
|
||||
LogUsingMethod(method, buffer);
|
||||
}
|
||||
|
||||
void CLogger::LogMessage(const wchar_t* fmt, ...)
|
||||
{
|
||||
va_list argp;
|
||||
@ -321,6 +302,10 @@ void CLogger::Render()
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// (Lock must come after loading the CFont, since that might log error messages
|
||||
// and attempt to lock the mutex recursively which is forbidden)
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
for (std::deque<RenderedMessage>::iterator it = m_RenderMessages.begin(); it != m_RenderMessages.end(); ++it)
|
||||
{
|
||||
const wchar_t* type;
|
||||
@ -357,7 +342,7 @@ void CLogger::Render()
|
||||
|
||||
void CLogger::PushRenderMessage(ELogMethod method, const wchar_t* message)
|
||||
{
|
||||
double now = timer_Time();
|
||||
double now = timer_Time(); // XXX: this is not thread-safe (http://trac.wildfiregames.com/ticket/653)
|
||||
|
||||
// Add each message line separately
|
||||
const wchar_t* pos = message;
|
||||
@ -381,6 +366,8 @@ void CLogger::PushRenderMessage(ELogMethod method, const wchar_t* message)
|
||||
|
||||
void CLogger::CleanupRenderQueue()
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
if (m_RenderMessages.empty())
|
||||
return;
|
||||
|
||||
|
@ -23,11 +23,12 @@
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
class CLogger;
|
||||
extern CLogger* g_Logger;
|
||||
|
||||
#define LOG (g_Logger->Log)
|
||||
#define LOG_ONCE (g_Logger->LogOnce)
|
||||
|
||||
// Should become LOG_MESSAGE but this can only be changed once the LOG function is removed
|
||||
// from all of the files. LOG_INFO, LOG_WARNING and LOG_ERROR are currently existing macros.
|
||||
@ -36,6 +37,14 @@ extern CLogger* g_Logger;
|
||||
#define LOGWARNING g_Logger->LogWarning
|
||||
#define LOGERROR g_Logger->LogError
|
||||
|
||||
/**
|
||||
* Error/warning/message logging class.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - Expected to be constructed/destructed in the main thread.
|
||||
* - The message logging functions may be called from any thread
|
||||
* while the object is alive.
|
||||
*/
|
||||
class CLogger
|
||||
{
|
||||
NONCOPYABLE(CLogger);
|
||||
@ -65,10 +74,7 @@ public:
|
||||
// Function to log stuff to file
|
||||
// -- This function has not been removed because the build would break.
|
||||
void Log(ELogMethod method, const wchar_t* category, const wchar_t* fmt, ...) WPRINTF_ARGS(4);
|
||||
// Similar to Log, but only outputs each message once no matter how many times it's called
|
||||
// -- This function has not been removed because the build would break.
|
||||
void LogOnce(ELogMethod method, const wchar_t* category, const wchar_t* fmt, ...) WPRINTF_ARGS(4);
|
||||
|
||||
|
||||
// Functions to write a message, warning or error to file.
|
||||
void LogMessage(const wchar_t* fmt, ...) WPRINTF_ARGS(2);
|
||||
void LogWarning(const wchar_t* fmt, ...) WPRINTF_ARGS(2);
|
||||
@ -102,9 +108,6 @@ private:
|
||||
int m_NumberOfErrors;
|
||||
int m_NumberOfWarnings;
|
||||
|
||||
// Used to remember LogOnce messages
|
||||
std::set<std::wstring> m_LoggedOnce;
|
||||
|
||||
// Used for Render()
|
||||
struct RenderedMessage
|
||||
{
|
||||
@ -114,6 +117,9 @@ private:
|
||||
};
|
||||
std::deque<RenderedMessage> m_RenderMessages;
|
||||
double m_RenderLastEraseTime;
|
||||
|
||||
// Lock for all state modified by logging commands
|
||||
CMutex m_Mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -55,7 +55,7 @@ CFont::CFont(const wchar_t* name)
|
||||
return;
|
||||
|
||||
// Not found as a font -- give up and use the default.
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Failed to find font '%ls'", name);
|
||||
LOGERROR(L"Failed to find font '%ls'", name);
|
||||
h = unifont_load(g_VFS, DefaultFont);
|
||||
// Assume this worked
|
||||
}
|
||||
|
@ -711,6 +711,8 @@ void EarlyInit()
|
||||
// If you ever want to catch a particular allocation:
|
||||
//_CrtSetBreakAlloc(232647);
|
||||
|
||||
ThreadUtil::SetMainThread();
|
||||
|
||||
debug_SetThreadName("main");
|
||||
// add all debug_printf "tags" that we are interested in:
|
||||
debug_filter_add(L"TIMER");
|
||||
@ -895,54 +897,6 @@ void RenderGui(bool RenderingState)
|
||||
}
|
||||
|
||||
|
||||
// Network autostart:
|
||||
|
||||
class AutostartNetServer : public CNetServer
|
||||
{
|
||||
public:
|
||||
AutostartNetServer(const CStr& map, int maxPlayers) :
|
||||
CNetServer(), m_NeedsStart(false), m_NumPlayers(0), m_MaxPlayers(maxPlayers)
|
||||
{
|
||||
CScriptValRooted attrs;
|
||||
GetScriptInterface().Eval("({})", attrs);
|
||||
GetScriptInterface().SetProperty(attrs.get(), "map", std::string(map), false);
|
||||
UpdateGameAttributes(attrs);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void OnAddPlayer()
|
||||
{
|
||||
m_NumPlayers++;
|
||||
|
||||
debug_printf(L"# player joined (got %d, need %d)\n", (int)m_NumPlayers, (int)m_MaxPlayers);
|
||||
|
||||
if (m_NumPlayers >= m_MaxPlayers)
|
||||
m_NeedsStart = true; // delay until next Poll, so the new player has been fully processed
|
||||
}
|
||||
|
||||
virtual void OnRemovePlayer()
|
||||
{
|
||||
debug_warn(L"client left?!");
|
||||
m_NumPlayers--;
|
||||
}
|
||||
|
||||
virtual void Poll()
|
||||
{
|
||||
if (m_NeedsStart)
|
||||
{
|
||||
StartGame();
|
||||
m_NeedsStart = false;
|
||||
}
|
||||
|
||||
CNetServer::Poll();
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_NeedsStart;
|
||||
size_t m_NumPlayers;
|
||||
size_t m_MaxPlayers;
|
||||
};
|
||||
|
||||
static bool Autostart(const CmdLineArgs& args)
|
||||
{
|
||||
/*
|
||||
@ -958,6 +912,12 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
|
||||
g_Game = new CGame();
|
||||
|
||||
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
|
||||
CScriptValRooted attrs;
|
||||
scriptInterface.Eval("({})", attrs);
|
||||
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
|
||||
scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false);
|
||||
|
||||
if (args.Has("autostart-host"))
|
||||
{
|
||||
InitPs(true, L"page_loading.xml");
|
||||
@ -966,7 +926,10 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
if (args.Has("autostart-players"))
|
||||
maxPlayers = args.Get("autostart-players").ToUInt();
|
||||
|
||||
g_NetServer = new AutostartNetServer(autostartMap, maxPlayers);
|
||||
g_NetServer = new CNetServer(maxPlayers);
|
||||
|
||||
g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface);
|
||||
|
||||
bool ok = g_NetServer->SetupConnection();
|
||||
debug_assert(ok);
|
||||
|
||||
@ -980,17 +943,16 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
|
||||
g_NetClient = new CNetClient(g_Game);
|
||||
// TODO: player name, etc
|
||||
bool ok = g_NetClient->SetupConnection(args.Get("autostart-ip"));
|
||||
|
||||
CStr ip = "127.0.0.1";
|
||||
if (args.Has("autostart-ip"))
|
||||
ip = args.Get("autostart-ip");
|
||||
|
||||
bool ok = g_NetClient->SetupConnection(ip);
|
||||
debug_assert(ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
|
||||
CScriptValRooted attrs;
|
||||
scriptInterface.Eval("({})", attrs);
|
||||
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
|
||||
scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false);
|
||||
|
||||
g_Game->SetPlayerID(1);
|
||||
g_Game->StartGame(attrs);
|
||||
LDR_NonprogressiveLoad();
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Singleton.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
#define PROFILE_AMORTIZE
|
||||
#define PROFILE_AMORTIZE_FRAMES 50
|
||||
@ -158,7 +159,12 @@ public:
|
||||
CProfileSample( const char* name )
|
||||
{
|
||||
if (CProfileManager::IsInitialised())
|
||||
{
|
||||
// The profiler is only safe to use on the main thread
|
||||
debug_assert(ThreadUtil::IsMainThread());
|
||||
|
||||
g_Profiler.Start( name );
|
||||
}
|
||||
}
|
||||
~CProfileSample()
|
||||
{
|
||||
@ -173,7 +179,12 @@ public:
|
||||
CProfileSampleScript( const char* name )
|
||||
{
|
||||
if (CProfileManager::IsInitialised())
|
||||
{
|
||||
// The profiler is only safe to use on the main thread
|
||||
debug_assert(ThreadUtil::IsMainThread());
|
||||
|
||||
g_Profiler.StartScript( name );
|
||||
}
|
||||
}
|
||||
~CProfileSampleScript()
|
||||
{
|
||||
|
@ -360,9 +360,11 @@ namespace
|
||||
{
|
||||
std::vector<CStr> data; // 2d array of (rows+head)*columns elements
|
||||
|
||||
const std::vector<ProfileColumn>& columns = table->GetColumns();
|
||||
|
||||
// Add column headers to 'data'
|
||||
for (std::vector<ProfileColumn>::const_iterator col_it = table->GetColumns().begin();
|
||||
col_it != table->GetColumns().end(); ++col_it)
|
||||
for (std::vector<ProfileColumn>::const_iterator col_it = columns.begin();
|
||||
col_it != columns.end(); ++col_it)
|
||||
data.push_back(col_it->title);
|
||||
|
||||
// Recursively add all profile data to 'data'
|
||||
@ -371,7 +373,7 @@ namespace
|
||||
// Calculate the width of each column ( = the maximum width of
|
||||
// any value in that column)
|
||||
std::vector<size_t> columnWidths;
|
||||
size_t cols = table->GetColumns().size();
|
||||
size_t cols = columns.size();
|
||||
for (size_t c = 0; c < cols; ++c)
|
||||
{
|
||||
size_t max = 0;
|
||||
@ -401,6 +403,8 @@ namespace
|
||||
|
||||
void WriteRows(int indent, AbstractProfileTable* table, std::vector<CStr>& data)
|
||||
{
|
||||
const std::vector<ProfileColumn>& columns = table->GetColumns();
|
||||
|
||||
for (size_t r = 0; r < table->GetNumberRows(); ++r)
|
||||
{
|
||||
// Do pretty tree-structure indenting
|
||||
@ -410,7 +414,7 @@ namespace
|
||||
else
|
||||
indentation += "|-";
|
||||
|
||||
for (size_t c = 0; c < table->GetColumns().size(); ++c)
|
||||
for (size_t c = 0; c < columns.size(); ++c)
|
||||
if (c == 0)
|
||||
data.push_back(indentation + table->GetCellText(r, c));
|
||||
else
|
||||
|
@ -138,9 +138,7 @@ void CReplayPlayer::Replay()
|
||||
{
|
||||
std::string line;
|
||||
std::getline(*m_Stream, line);
|
||||
std::wstring linew = wstring_from_utf8(line);
|
||||
utf16string line16(linew.begin(), linew.end());
|
||||
CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line16);
|
||||
CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
|
||||
|
||||
game.StartGame(attribs);
|
||||
LDR_NonprogressiveLoad();
|
||||
@ -159,9 +157,7 @@ void CReplayPlayer::Replay()
|
||||
|
||||
std::string line;
|
||||
std::getline(*m_Stream, line);
|
||||
std::wstring linew = wstring_from_utf8(line);
|
||||
utf16string line16(linew.begin(), linew.end());
|
||||
CScriptValRooted data = game.GetSimulation2()->GetScriptInterface().ParseJSON(line16);
|
||||
CScriptValRooted data = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
|
||||
|
||||
SimulationCommand cmd = { player, data };
|
||||
commands.push_back(cmd);
|
||||
|
32
source/ps/ThreadUtil.cpp
Normal file
32
source/ps/ThreadUtil.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ThreadUtil.h"
|
||||
|
||||
static pthread_t g_MainThread;
|
||||
|
||||
bool ThreadUtil::IsMainThread()
|
||||
{
|
||||
return pthread_equal(pthread_self(), g_MainThread);
|
||||
}
|
||||
|
||||
void ThreadUtil::SetMainThread()
|
||||
{
|
||||
g_MainThread = pthread_self();
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -15,71 +15,8 @@
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
ThreadUtil.h - Thread Utility Functions
|
||||
|
||||
--Overview--
|
||||
|
||||
Contains three classes: CMutex, CRWLock and CLocker.
|
||||
CMutex is a Mutual Exclusion lock that can be locked and unlocked. The Lock
|
||||
function waits for the current lock holder (if any) to release the lock and
|
||||
then acquires it.
|
||||
|
||||
CRWLock is a lock where any number of readers *or* one writer can hold the lock.
|
||||
|
||||
CLocker is a generic wrapper class that can wrap any data class with any locker
|
||||
class.
|
||||
|
||||
--Example--
|
||||
|
||||
CMutex usage:
|
||||
|
||||
CMutex protectData;
|
||||
|
||||
void function()
|
||||
{
|
||||
protectData.Lock();
|
||||
.. do stuff with data ..
|
||||
protectData.Unlock();
|
||||
}
|
||||
|
||||
CLocker usage:
|
||||
|
||||
class CDataClass {};
|
||||
CLocker<CDataClass> instance;
|
||||
|
||||
void function()
|
||||
{
|
||||
instance.Lock();
|
||||
.. do stuff with instance ..
|
||||
instance.Unlock();
|
||||
}
|
||||
|
||||
CLocker usage 2:
|
||||
|
||||
class CDataClass {};
|
||||
class CCustomLockerClass { void MyOwnLock(); void MyOwnUnlock(); };
|
||||
|
||||
CLocker<CDataClass, CCustomLockerClass> instance;
|
||||
|
||||
void function()
|
||||
{
|
||||
instance.MyOwnLock();
|
||||
.. do stuff with instance ..
|
||||
instance.MyOwnUnlock();
|
||||
}
|
||||
|
||||
--More Info--
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _ThreadUtil_h
|
||||
#define _ThreadUtil_h
|
||||
|
||||
//--------------------------------------------------------
|
||||
// Includes / Compiler directives
|
||||
//--------------------------------------------------------
|
||||
#ifndef INCLUDED_THREADUTIL
|
||||
#define INCLUDED_THREADUTIL
|
||||
|
||||
#include "lib/posix/posix_pthread.h"
|
||||
|
||||
@ -102,98 +39,79 @@ CLocker usage 2:
|
||||
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------
|
||||
// Types
|
||||
//-------------------------------------------------
|
||||
|
||||
//-------------------------------------------------
|
||||
// Declarations
|
||||
//-------------------------------------------------
|
||||
|
||||
/**
|
||||
* A Mutual Exclusion lock.
|
||||
* A non-recursive mutual exclusion lock.
|
||||
*/
|
||||
class CMutex
|
||||
{
|
||||
NONCOPYABLE(CMutex);
|
||||
|
||||
friend class CScopeLock;
|
||||
|
||||
public:
|
||||
inline CMutex()
|
||||
CMutex()
|
||||
{
|
||||
pthread_mutex_init(&m_Mutex, NULL);
|
||||
int ret = pthread_mutex_init(&m_Mutex, NULL);
|
||||
debug_assert(ret == 0);
|
||||
}
|
||||
|
||||
inline ~CMutex()
|
||||
~CMutex()
|
||||
{
|
||||
if (pthread_mutex_destroy(&m_Mutex) != 0)
|
||||
{
|
||||
Unlock();
|
||||
pthread_mutex_destroy(&m_Mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically wait for the mutex to become unlocked, then lock it.
|
||||
*/
|
||||
inline void Lock()
|
||||
{
|
||||
LOCK_MUTEX(&m_Mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock the mutex.
|
||||
*/
|
||||
inline void Unlock()
|
||||
{
|
||||
UNLOCK_MUTEX(&m_Mutex);
|
||||
}
|
||||
|
||||
pthread_mutex_t m_Mutex;
|
||||
};
|
||||
|
||||
// CScopeLock
|
||||
// ---------------------------------------------------------------------| Class
|
||||
// Locks a CMutex over the objects lifetime
|
||||
class CScopeLock
|
||||
{
|
||||
NONCOPYABLE(CScopeLock);
|
||||
public:
|
||||
inline CScopeLock(pthread_mutex_t &mutex): m_Mutex(mutex)
|
||||
{
|
||||
LOCK_MUTEX(&m_Mutex);
|
||||
}
|
||||
|
||||
inline CScopeLock(CMutex &mutex): m_Mutex(mutex.m_Mutex)
|
||||
{
|
||||
LOCK_MUTEX(&m_Mutex);
|
||||
}
|
||||
|
||||
inline ~CScopeLock()
|
||||
{
|
||||
UNLOCK_MUTEX(&m_Mutex);
|
||||
int ret = pthread_mutex_destroy(&m_Mutex);
|
||||
debug_assert(ret == 0);
|
||||
}
|
||||
|
||||
private:
|
||||
pthread_mutex_t &m_Mutex;
|
||||
pthread_mutex_t m_Mutex;
|
||||
};
|
||||
|
||||
// CLocker
|
||||
// ---------------------------------------------------------------------| Class
|
||||
// This will not give access to the wrapped class constructors directly,
|
||||
// to use them you have to do CLocker(CWrappedClass(args))
|
||||
template <typename _T>
|
||||
struct CLocker: public CMutex, public _T
|
||||
/**
|
||||
* Locks a CMutex over this object's lifetime.
|
||||
* The mutexes are non-recursive - a single thread locking a mutex more than once
|
||||
* results in undefined behaviour.
|
||||
*/
|
||||
class CScopeLock
|
||||
{
|
||||
public:
|
||||
inline CLocker()
|
||||
{}
|
||||
NONCOPYABLE(CScopeLock);
|
||||
|
||||
/*
|
||||
GCC doesn't take these... I don't understand what the problem is! // Simon
|
||||
|
||||
inline CLocker(const _T &arg): _T(arg)
|
||||
{}
|
||||
|
||||
inline CLocker(_T &arg): _T(arg)
|
||||
{}*/
|
||||
public:
|
||||
CScopeLock(pthread_mutex_t* mutex) :
|
||||
m_Mutex(mutex)
|
||||
{
|
||||
LOCK_MUTEX(m_Mutex);
|
||||
}
|
||||
|
||||
CScopeLock(CMutex& mutex) :
|
||||
m_Mutex(&mutex.m_Mutex)
|
||||
{
|
||||
LOCK_MUTEX(m_Mutex);
|
||||
}
|
||||
|
||||
~CScopeLock()
|
||||
{
|
||||
UNLOCK_MUTEX(m_Mutex);
|
||||
}
|
||||
|
||||
private:
|
||||
pthread_mutex_t* m_Mutex;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
namespace ThreadUtil
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns whether the current thread is the 'main' thread
|
||||
* (i.e. matches an earlier call to SetMainThread).
|
||||
*/
|
||||
bool IsMainThread();
|
||||
|
||||
/**
|
||||
* Set the current thread as the 'main' thread.
|
||||
* (This is called during engine initialisation.)
|
||||
*/
|
||||
void SetMainThread();
|
||||
|
||||
}
|
||||
|
||||
#endif // INCLUDED_THREADUTIL
|
||||
|
@ -51,11 +51,6 @@ public:
|
||||
logger->Log(CLogger::Normal, L"", L"%hs", msg2.c_str());
|
||||
logger->Log(CLogger::Normal, L"", L"%hs", msg3.c_str());
|
||||
|
||||
logger->LogOnce(CLogger::Normal, L"", L"%hs", msg0.c_str());
|
||||
logger->LogOnce(CLogger::Normal, L"", L"%hs", msg1.c_str());
|
||||
logger->LogOnce(CLogger::Normal, L"", L"%hs", msg2.c_str());
|
||||
logger->LogOnce(CLogger::Normal, L"", L"%hs", msg3.c_str());
|
||||
|
||||
logger->LogMessage(L"%hs", msg0.c_str());
|
||||
logger->LogMessage(L"%hs", msg1.c_str());
|
||||
logger->LogMessage(L"%hs", msg2.c_str());
|
||||
@ -73,7 +68,7 @@ public:
|
||||
|
||||
ParseOutput();
|
||||
|
||||
TS_ASSERT_EQUALS((int)lines.size(), 4*5-1);
|
||||
TS_ASSERT_EQUALS((int)lines.size(), 4*4);
|
||||
TS_ASSERT_EQUALS(lines[0], msg0);
|
||||
TS_ASSERT_EQUALS(lines[1], msg1);
|
||||
TS_ASSERT_EQUALS(lines[2], clipped);
|
||||
@ -82,21 +77,17 @@ public:
|
||||
TS_ASSERT_EQUALS(lines[4], msg0);
|
||||
TS_ASSERT_EQUALS(lines[5], msg1);
|
||||
TS_ASSERT_EQUALS(lines[6], clipped);
|
||||
TS_ASSERT_EQUALS(lines[7], clipped);
|
||||
|
||||
TS_ASSERT_EQUALS(lines[7], msg0);
|
||||
TS_ASSERT_EQUALS(lines[8], msg1);
|
||||
TS_ASSERT_EQUALS(lines[9], clipped);
|
||||
TS_ASSERT_EQUALS(lines[10], clipped);
|
||||
TS_ASSERT_EQUALS(lines[8], "WARNING: "+msg0);
|
||||
TS_ASSERT_EQUALS(lines[9], "WARNING: "+msg1);
|
||||
TS_ASSERT_EQUALS(lines[10], "WARNING: "+clipped);
|
||||
TS_ASSERT_EQUALS(lines[11], "WARNING: "+clipped);
|
||||
|
||||
TS_ASSERT_EQUALS(lines[11], "WARNING: "+msg0);
|
||||
TS_ASSERT_EQUALS(lines[12], "WARNING: "+msg1);
|
||||
TS_ASSERT_EQUALS(lines[13], "WARNING: "+clipped);
|
||||
TS_ASSERT_EQUALS(lines[14], "WARNING: "+clipped);
|
||||
|
||||
TS_ASSERT_EQUALS(lines[15], "ERROR: "+msg0);
|
||||
TS_ASSERT_EQUALS(lines[16], "ERROR: "+msg1);
|
||||
TS_ASSERT_EQUALS(lines[17], "ERROR: "+clipped);
|
||||
TS_ASSERT_EQUALS(lines[18], "ERROR: "+clipped);
|
||||
TS_ASSERT_EQUALS(lines[12], "ERROR: "+msg0);
|
||||
TS_ASSERT_EQUALS(lines[13], "ERROR: "+msg1);
|
||||
TS_ASSERT_EQUALS(lines[14], "ERROR: "+clipped);
|
||||
TS_ASSERT_EQUALS(lines[15], "ERROR: "+clipped);
|
||||
}
|
||||
|
||||
void test_unicode()
|
||||
|
@ -41,8 +41,6 @@
|
||||
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
#define LOG_CATEGORY L"graphics"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ModelRenderer implementation
|
||||
@ -82,7 +80,7 @@ void ModelRenderer::BuildPositionAndNormals(
|
||||
// some broken situations
|
||||
if (numVertices && vertices[0].m_Blend.m_Bone[0] == 0xff)
|
||||
{
|
||||
LOG_ONCE(CLogger::Error, LOG_CATEGORY, L"Model %ls is boned with unboned animation", mdef->GetName().string().c_str());
|
||||
LOGERROR(L"Model %ls is boned with unboned animation", mdef->GetName().string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -252,10 +252,14 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContex
|
||||
debug_assert(m_rt); // TODO: error handling
|
||||
|
||||
#if ENABLE_SCRIPT_PROFILING
|
||||
if (CProfileManager::IsInitialised())
|
||||
// Profiler isn't thread-safe, so only enable this on the main thread
|
||||
if (ThreadUtil::IsMainThread())
|
||||
{
|
||||
JS_SetExecuteHook(m_rt, jshook_script, this);
|
||||
JS_SetCallHook(m_rt, jshook_function, this);
|
||||
if (CProfileManager::IsInitialised())
|
||||
{
|
||||
JS_SetExecuteHook(m_rt, jshook_script, this);
|
||||
JS_SetCallHook(m_rt, jshook_function, this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -316,14 +320,21 @@ void ScriptInterface_impl::Register(const char* name, JSFastNative fptr, uintN n
|
||||
ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName) :
|
||||
m(new ScriptInterface_impl(nativeScopeName, NULL))
|
||||
{
|
||||
if (g_ScriptStatsTable)
|
||||
g_ScriptStatsTable->Add(this, debugName);
|
||||
// Profiler stats table isn't thread-safe, so only enable this on the main thread
|
||||
if (ThreadUtil::IsMainThread())
|
||||
{
|
||||
if (g_ScriptStatsTable)
|
||||
g_ScriptStatsTable->Add(this, debugName);
|
||||
}
|
||||
}
|
||||
|
||||
ScriptInterface::~ScriptInterface()
|
||||
{
|
||||
if (g_ScriptStatsTable)
|
||||
g_ScriptStatsTable->Remove(this);
|
||||
if (ThreadUtil::IsMainThread())
|
||||
{
|
||||
if (g_ScriptStatsTable)
|
||||
g_ScriptStatsTable->Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptInterface::ShutDown()
|
||||
@ -691,9 +702,16 @@ CScriptValRooted ScriptInterface::ParseJSON(const utf16string& string)
|
||||
return CScriptValRooted(m->m_cx, vp);
|
||||
}
|
||||
|
||||
CScriptValRooted ScriptInterface::ParseJSON(const std::string& string_utf8)
|
||||
{
|
||||
// TODO: we could do more efficient string conversion
|
||||
std::wstring attrsW = wstring_from_utf8(string_utf8);
|
||||
return ParseJSON(utf16string(attrsW.begin(), attrsW.end()));
|
||||
}
|
||||
|
||||
struct Stringifier
|
||||
{
|
||||
static JSBool callback(const jschar *buf, uint32 len, void *data)
|
||||
static JSBool callback(const jschar* buf, uint32 len, void* data)
|
||||
{
|
||||
utf16string str(buf, buf+len);
|
||||
std::wstring strw(str.begin(), str.end());
|
||||
|
@ -50,6 +50,15 @@ namespace boost { class rand48; }
|
||||
#endif
|
||||
|
||||
struct ScriptInterface_impl;
|
||||
|
||||
/**
|
||||
* Abstraction around a SpiderMonkey JSContext.
|
||||
*
|
||||
* Thread-safety:
|
||||
* - May be used in non-main threads.
|
||||
* - Each ScriptInterface must be created, used, and destroyed, all in a single thread
|
||||
* (it must never be shared between threads).
|
||||
*/
|
||||
class ScriptInterface
|
||||
{
|
||||
public:
|
||||
@ -170,6 +179,11 @@ public:
|
||||
*/
|
||||
CScriptValRooted ParseJSON(const utf16string& string);
|
||||
|
||||
/**
|
||||
* Parse a UTF-8-encoded JSON string. Returns the undefined value on error.
|
||||
*/
|
||||
CScriptValRooted ParseJSON(const std::string& string_utf8);
|
||||
|
||||
/**
|
||||
* Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error.
|
||||
*/
|
||||
|
@ -138,8 +138,7 @@ public:
|
||||
std::string stringified = script.StringifyJSON(val.get());
|
||||
TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\":1,\n \"z\":[2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\":true\n}");
|
||||
|
||||
std::wstring stringifiedw = wstring_from_utf8(stringified.c_str());
|
||||
val = script.ParseJSON(utf16string(stringifiedw.begin(), stringifiedw.end()));
|
||||
val = script.ParseJSON(stringified);
|
||||
TS_ASSERT_WSTR_EQUALS(script.ToString(val.get()), L"({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})");
|
||||
}
|
||||
};
|
||||
|
@ -906,7 +906,7 @@ CScriptVal CComponentManager::Script_ReadJSONFile(void* cbdata, std::string file
|
||||
return CScriptVal();
|
||||
}
|
||||
|
||||
utf16string content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // TODO: encodings etc
|
||||
std::string content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // assume it's UTF-8
|
||||
|
||||
return (componentManager->m_ScriptInterface.ParseJSON(content)).get();
|
||||
|
||||
|
@ -85,9 +85,10 @@ void MessagePasserImpl::Add(IMessage* msg)
|
||||
if (m_Trace)
|
||||
debug_printf(L"%8.3f add message: %hs\n", timer_Time(), msg->GetName());
|
||||
|
||||
m_Mutex.Lock();
|
||||
m_Queue.push(msg);
|
||||
m_Mutex.Unlock();
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
m_Queue.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
IMessage* MessagePasserImpl::Retrieve()
|
||||
@ -96,16 +97,16 @@ IMessage* MessagePasserImpl::Retrieve()
|
||||
// since there's only one thread adding items and one thread consuming;
|
||||
// but it's not worthwhile yet.)
|
||||
|
||||
m_Mutex.Lock();
|
||||
|
||||
IMessage* msg = NULL;
|
||||
if (! m_Queue.empty())
|
||||
{
|
||||
msg = m_Queue.front();
|
||||
m_Queue.pop();
|
||||
}
|
||||
|
||||
m_Mutex.Unlock();
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
if (! m_Queue.empty())
|
||||
{
|
||||
msg = m_Queue.front();
|
||||
m_Queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Trace && msg)
|
||||
debug_printf(L"%8.3f retrieved message: %hs\n", timer_Time(), msg->GetName());
|
||||
@ -124,9 +125,10 @@ void MessagePasserImpl::Query(QueryMessage* qry, void(* UNUSED(timeoutCallback)
|
||||
// Set the semaphore, so we can block until the query has been handled
|
||||
qry->m_Semaphore = static_cast<void*>(m_Semaphore);
|
||||
|
||||
m_Mutex.Lock();
|
||||
m_Queue.push(qry);
|
||||
m_Mutex.Unlock();
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
m_Queue.push(qry);
|
||||
}
|
||||
|
||||
// Wait until the query handler has handled the query and called sem_post:
|
||||
|
||||
@ -183,10 +185,8 @@ void MessagePasserImpl::Query(QueryMessage* qry, void(* UNUSED(timeoutCallback)
|
||||
|
||||
bool MessagePasserImpl::IsEmpty()
|
||||
{
|
||||
m_Mutex.Lock();
|
||||
bool empty = m_Queue.empty();
|
||||
m_Mutex.Unlock();
|
||||
return empty;
|
||||
CScopeLock lock(m_Mutex);
|
||||
return m_Queue.empty();
|
||||
}
|
||||
|
||||
void MessagePasserImpl::SetTrace(bool t)
|
||||
|
Loading…
Reference in New Issue
Block a user