mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-28 00:12:44 +00:00
# Add opt-in automatic feedback system.
Add GL implementation limits to hwdetect. Make the in-game manual support multiple text files. Remove unnecessary instruction on how to close the manual window. Make guitextobject.caption return the original value without stripping formatting tags. Remove unused non-VFS support from config system. Support writing config files back to their original path. Remove unnecessary user profile directory; use normal config directory. This was SVN commit r8925.
This commit is contained in:
@@ -318,6 +318,8 @@ bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
|
||||
|
||||
void CGUIString::SetValue(const CStrW& str)
|
||||
{
|
||||
m_OriginalString = str;
|
||||
|
||||
// clear
|
||||
m_TextChunks.clear();
|
||||
m_Words.clear();
|
||||
|
||||
@@ -294,6 +294,11 @@ public:
|
||||
*/
|
||||
const CStrW& GetRawString() const { return m_RawString; }
|
||||
|
||||
/**
|
||||
* Get String, with tags
|
||||
*/
|
||||
const CStrW& GetOriginalString() const { return m_OriginalString; }
|
||||
|
||||
/**
|
||||
* Generate Text Call from specified range. The range
|
||||
* must span only within ONE TextChunk though. Otherwise
|
||||
@@ -333,6 +338,11 @@ private:
|
||||
* The full raw string. Stripped of tags.
|
||||
*/
|
||||
CStrW m_RawString;
|
||||
|
||||
/**
|
||||
* The original string value passed to SetValue.
|
||||
*/
|
||||
CStrW m_OriginalString;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -203,7 +203,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval*
|
||||
{
|
||||
CGUIString value;
|
||||
GUI<CGUIString>::GetSetting(e, propName, value);
|
||||
*vp = ScriptInterface::ToJSVal(cx, value.GetRawString());
|
||||
*vp = ScriptInterface::ToJSVal(cx, value.GetOriginalString());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsid id, jsval*
|
||||
|
||||
for (size_t i = 0; i < value.m_Items.size(); ++i)
|
||||
{
|
||||
jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetRawString());
|
||||
jsval val = ScriptInterface::ToJSVal(cx, value.m_Items[i].GetOriginalString());
|
||||
JS_SetElement(cx, obj, (jsint)i, &val);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "graphics/MapReader.h"
|
||||
#include "gui/GUIManager.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "lib/sysdep/sysdep.h"
|
||||
#include "maths/FixedVector3D.h"
|
||||
#include "network/NetClient.h"
|
||||
@@ -36,6 +37,7 @@
|
||||
#include "ps/Hotkey.h"
|
||||
#include "ps/Overlay.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
#include "ps/UserReport.h"
|
||||
#include "ps/GameSetup/Atlas.h"
|
||||
#include "ps/GameSetup/Config.h"
|
||||
|
||||
@@ -342,6 +344,29 @@ void DisplayErrorDialog(void* UNUSED(cbdata), std::wstring msg)
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool IsUserReportEnabled(void* UNUSED(cbdata))
|
||||
{
|
||||
return g_UserReporter.IsReportingEnabled();
|
||||
}
|
||||
|
||||
void SetUserReportEnabled(void* UNUSED(cbdata), bool enabled)
|
||||
{
|
||||
g_UserReporter.SetReportingEnabled(enabled);
|
||||
}
|
||||
|
||||
std::string GetUserReportStatus(void* UNUSED(cbdata))
|
||||
{
|
||||
return g_UserReporter.GetStatus();
|
||||
}
|
||||
|
||||
void SubmitUserReport(void* UNUSED(cbdata), std::string type, int version, std::wstring data)
|
||||
{
|
||||
g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SetSimRate(void* UNUSED(cbdata), float rate)
|
||||
{
|
||||
g_Game->SetSimRate(rate);
|
||||
@@ -449,6 +474,12 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed");
|
||||
scriptInterface.RegisterFunction<void, std::wstring, &DisplayErrorDialog>("DisplayErrorDialog");
|
||||
|
||||
// User report functions
|
||||
scriptInterface.RegisterFunction<bool, &IsUserReportEnabled>("IsUserReportEnabled");
|
||||
scriptInterface.RegisterFunction<void, bool, &SetUserReportEnabled>("SetUserReportEnabled");
|
||||
scriptInterface.RegisterFunction<std::string, &GetUserReportStatus>("GetUserReportStatus");
|
||||
scriptInterface.RegisterFunction<void, std::string, int, std::wstring, &SubmitUserReport>("SubmitUserReport");
|
||||
|
||||
// Development/debugging functions
|
||||
scriptInterface.RegisterFunction<void, float, &SetSimRate>("SetSimRate");
|
||||
scriptInterface.RegisterFunction<void, int, &SetTurnLength>("SetTurnLength");
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#ifndef INCLUDED_CURL
|
||||
#define INCLUDED_CURL
|
||||
|
||||
#if OS_WIN
|
||||
|
||||
// curl.h wants to include winsock2.h which causes conflicts.
|
||||
// provide some required definitions from winsock.h, then pretend
|
||||
// we already included winsock.h
|
||||
@@ -43,6 +45,8 @@ struct fd_set;
|
||||
|
||||
#define _WINSOCKAPI_ // winsock.h include guard
|
||||
|
||||
#endif // OS_WIN
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#endif // #ifndef INCLUDED_CURL
|
||||
|
||||
@@ -52,6 +52,7 @@ that of Atlas depending on commandline parameters.
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/Pyrogenesis.h"
|
||||
#include "ps/Replay.h"
|
||||
#include "ps/UserReport.h"
|
||||
#include "ps/Util.h"
|
||||
#include "ps/VideoMode.h"
|
||||
#include "ps/GameSetup/GameSetup.h"
|
||||
@@ -360,6 +361,8 @@ static void Frame()
|
||||
g_NetClient->Flush();
|
||||
PROFILE_END("network flush");
|
||||
|
||||
g_UserReporter.Update();
|
||||
|
||||
PROFILE_START( "update console" );
|
||||
g_Console->Update(TimeSinceLastFrame);
|
||||
PROFILE_END( "update console" );
|
||||
|
||||
+30
-25
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -30,8 +30,7 @@
|
||||
|
||||
typedef std::map <CStr, CConfigValueSet> TConfigMap;
|
||||
TConfigMap CConfigDB::m_Map[CFG_LAST];
|
||||
CStrW CConfigDB::m_ConfigFile[CFG_LAST];
|
||||
bool CConfigDB::m_UseVFS[CFG_LAST];
|
||||
VfsPath CConfigDB::m_ConfigFile[CFG_LAST];
|
||||
|
||||
#define GET_NS_PRIVATE(cx, obj) (EConfigNamespace)((intptr_t)JS_GetPrivate(cx, obj) >> 1)
|
||||
|
||||
@@ -110,18 +109,14 @@ namespace ConfigNamespace_JS
|
||||
if (cfgNs < 0 || cfgNs >= CFG_LAST)
|
||||
return JS_FALSE;
|
||||
|
||||
if (argc != 2)
|
||||
return JS_FALSE;
|
||||
|
||||
bool useVFS;
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS))
|
||||
if (argc != 1)
|
||||
return JS_FALSE;
|
||||
|
||||
std::wstring path;
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path))
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path))
|
||||
return JS_FALSE;
|
||||
|
||||
bool res = g_ConfigDB.WriteFile(cfgNs, useVFS, path);
|
||||
bool res = g_ConfigDB.WriteFile(cfgNs, path);
|
||||
|
||||
JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(res));
|
||||
return JS_TRUE;
|
||||
@@ -147,18 +142,14 @@ namespace ConfigNamespace_JS
|
||||
if (cfgNs < 0 || cfgNs >= CFG_LAST)
|
||||
return JS_FALSE;
|
||||
|
||||
if (argc != 2)
|
||||
return JS_FALSE;
|
||||
|
||||
bool useVFS;
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], useVFS))
|
||||
if (argc != 1)
|
||||
return JS_FALSE;
|
||||
|
||||
std::wstring path;
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[1], path))
|
||||
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], path))
|
||||
return JS_FALSE;
|
||||
|
||||
g_ConfigDB.SetConfigFile(cfgNs, useVFS, path);
|
||||
g_ConfigDB.SetConfigFile(cfgNs, path);
|
||||
|
||||
JS_SET_RVAL(cx, vp, JSVAL_VOID);
|
||||
return JS_TRUE;
|
||||
@@ -297,7 +288,7 @@ CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name)
|
||||
return &(it->second[0]);
|
||||
}
|
||||
|
||||
void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path)
|
||||
void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path)
|
||||
{
|
||||
if (ns < 0 || ns >= CFG_LAST)
|
||||
{
|
||||
@@ -306,11 +297,16 @@ void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& pat
|
||||
}
|
||||
|
||||
m_ConfigFile[ns]=path;
|
||||
m_UseVFS[ns]=useVFS;
|
||||
}
|
||||
|
||||
bool CConfigDB::Reload(EConfigNamespace ns)
|
||||
{
|
||||
if (ns < 0 || ns >= CFG_LAST)
|
||||
{
|
||||
debug_warn(L"CConfigDB: Invalid ns value");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up CParser
|
||||
CParser parser;
|
||||
CParserLine parserLine;
|
||||
@@ -323,16 +319,16 @@ bool CConfigDB::Reload(EConfigNamespace ns)
|
||||
// Handle missing files quietly
|
||||
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
|
||||
{
|
||||
LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].c_str());
|
||||
LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].string().c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].c_str());
|
||||
LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].string().c_str());
|
||||
LibError ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
|
||||
if (ret != INFO::OK)
|
||||
{
|
||||
LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].c_str(), ret);
|
||||
LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].string().c_str(), ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -388,10 +384,19 @@ bool CConfigDB::Reload(EConfigNamespace ns)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path)
|
||||
bool CConfigDB::WriteFile(EConfigNamespace ns)
|
||||
{
|
||||
debug_assert(useVFS);
|
||||
if (ns < 0 || ns >= CFG_LAST)
|
||||
{
|
||||
debug_warn(L"CConfigDB: Invalid ns value");
|
||||
return false;
|
||||
}
|
||||
|
||||
return WriteFile(ns, m_ConfigFile[ns]);
|
||||
}
|
||||
|
||||
bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path)
|
||||
{
|
||||
if (ns < 0 || ns >= CFG_LAST)
|
||||
{
|
||||
debug_warn(L"CConfigDB: Invalid ns value");
|
||||
@@ -410,7 +415,7 @@ bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path)
|
||||
LibError ret = g_VFS->CreateFile(path, buf, len);
|
||||
if(ret < 0)
|
||||
{
|
||||
LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.c_str(), (int)ret);
|
||||
LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.string().c_str(), (int)ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+16
-8
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -54,6 +54,8 @@
|
||||
#include "CStr.h"
|
||||
#include "Singleton.h"
|
||||
|
||||
#include "lib/file/vfs/vfs_path.h"
|
||||
|
||||
// Namespace priorities: User supersedes mod supersedes system.
|
||||
// Command-line arguments override everything.
|
||||
|
||||
@@ -75,8 +77,7 @@ typedef std::vector<CParserValue> CConfigValueSet;
|
||||
class CConfigDB: public Singleton<CConfigDB>
|
||||
{
|
||||
static std::map <CStr, CConfigValueSet> m_Map[];
|
||||
static CStrW m_ConfigFile[];
|
||||
static bool m_UseVFS[];
|
||||
static VfsPath m_ConfigFile[];
|
||||
|
||||
public:
|
||||
// NOTE: Construct the Singleton Object *after* JavaScript init, so that
|
||||
@@ -123,11 +124,8 @@ public:
|
||||
* the Reload() method if you want to read the config file at the same time.
|
||||
*
|
||||
* 'path': The path to the config file.
|
||||
* VFS: relative to VFS root
|
||||
* non-VFS: relative to current working directory (binaries/data/)
|
||||
* 'useVFS': true if the path is a VFS path, false if it is a real path
|
||||
*/
|
||||
void SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path);
|
||||
void SetConfigFile(EConfigNamespace ns, const VfsPath& path);
|
||||
|
||||
/**
|
||||
* Reload the config file associated with the specified config namespace
|
||||
@@ -147,7 +145,17 @@ public:
|
||||
* true: if the config namespace was successfully written to the file
|
||||
* false: if an error occurred
|
||||
*/
|
||||
bool WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path);
|
||||
bool WriteFile(EConfigNamespace ns, const VfsPath& path);
|
||||
|
||||
/**
|
||||
* Write the current state of the specified config namespace to the file
|
||||
* it was originally loaded from.
|
||||
*
|
||||
* Returns:
|
||||
* true: if the config namespace was successfully written to the file
|
||||
* false: if an error occurred
|
||||
*/
|
||||
bool WriteFile(EConfigNamespace ns);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -29,7 +29,6 @@
|
||||
// (these variables are documented in the header.)
|
||||
|
||||
CStrW g_CursorName = L"test";
|
||||
CStr g_ActiveProfile = "default";
|
||||
|
||||
bool g_NoGLS3TC = false;
|
||||
bool g_NoGLAutoMipmap = false;
|
||||
@@ -63,32 +62,12 @@ CStr g_AutostartMap = "";
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// config and profile
|
||||
// config
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static void LoadProfile( const CStr& profile )
|
||||
{
|
||||
VfsPath path = VfsPath(L"config/profiles") / wstring_from_utf8(profile);
|
||||
|
||||
VfsPath configFilename = path / L"settings/user.cfg";
|
||||
g_ConfigDB.SetConfigFile(CFG_USER, true, configFilename.string().c_str());
|
||||
g_ConfigDB.Reload(CFG_USER);
|
||||
|
||||
int max_history_lines = 200;
|
||||
CFG_GET_USER_VAL("console.history.size", Int, max_history_lines);
|
||||
g_Console->UseHistoryFile(path / L"settings/history", max_history_lines);
|
||||
}
|
||||
|
||||
|
||||
// Fill in the globals from the config files.
|
||||
static void LoadGlobals()
|
||||
{
|
||||
CFG_GET_SYS_VAL("profile", String, g_ActiveProfile);
|
||||
|
||||
// Now load the profile before trying to retrieve the values of the rest of these.
|
||||
|
||||
LoadProfile( g_ActiveProfile );
|
||||
|
||||
CFG_GET_USER_VAL("vsync", Bool, g_VSync);
|
||||
|
||||
CFG_GET_USER_VAL("nos3tc", Bool, g_NoGLS3TC);
|
||||
@@ -180,19 +159,27 @@ void CONFIG_Init(const CmdLineArgs& args)
|
||||
new CConfigDB;
|
||||
|
||||
// Load the global, default config file
|
||||
g_ConfigDB.SetConfigFile(CFG_DEFAULT, false, L"config/default.cfg");
|
||||
g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg");
|
||||
g_ConfigDB.Reload(CFG_DEFAULT); // 216ms
|
||||
// Try loading the local system config file (which doesn't exist by
|
||||
// default) - this is designed as a way of letting developers edit the
|
||||
// system config without accidentally committing their changes back to SVN.
|
||||
g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, L"config/local.cfg");
|
||||
g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg");
|
||||
g_ConfigDB.Reload(CFG_SYSTEM);
|
||||
|
||||
g_ConfigDB.SetConfigFile(CFG_MOD, true, L"config/mod.cfg");
|
||||
g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg");
|
||||
g_ConfigDB.Reload(CFG_USER);
|
||||
|
||||
g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg");
|
||||
// No point in reloading mod.cfg here - we haven't mounted mods yet
|
||||
|
||||
ProcessCommandLineArgs(args);
|
||||
|
||||
// Initialise console history file
|
||||
int max_history_lines = 200;
|
||||
CFG_GET_USER_VAL("console.history.size", Int, max_history_lines);
|
||||
g_Console->UseHistoryFile(L"config/console.txt", max_history_lines);
|
||||
|
||||
// Collect information from system.cfg, the profile file,
|
||||
// and any command-line overrides to fill in the globals.
|
||||
LoadGlobals(); // 64ms
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "ps/Overlay.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "ps/ProfileViewer.h"
|
||||
#include "ps/UserReport.h"
|
||||
#include "ps/Util.h"
|
||||
#include "ps/VideoMode.h"
|
||||
#include "ps/World.h"
|
||||
@@ -672,6 +673,10 @@ void Shutdown(int UNUSED(flags))
|
||||
|
||||
g_VideoMode.Shutdown();
|
||||
|
||||
TIMER_BEGIN(L"shutdown UserReporter");
|
||||
g_UserReporter.Deinitialize();
|
||||
TIMER_END(L"shutdown UserReporter");
|
||||
|
||||
TIMER_BEGIN(L"shutdown ScriptingHost");
|
||||
delete &g_ScriptingHost;
|
||||
TIMER_END(L"shutdown ScriptingHost");
|
||||
@@ -848,6 +853,9 @@ void Init(const CmdLineArgs& args, int UNUSED(flags))
|
||||
|
||||
// g_ConfigDB, command line args, globals
|
||||
CONFIG_Init(args);
|
||||
|
||||
if (!g_Quickstart)
|
||||
g_UserReporter.Initialize(); // after config
|
||||
}
|
||||
|
||||
void InitGraphics(const CmdLineArgs& args, int flags)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -21,12 +21,15 @@
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
||||
#include "lib/ogl.h"
|
||||
#include "lib/svn_revision.h"
|
||||
#include "lib/timer.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "lib/sysdep/cpu.h"
|
||||
#include "lib/sysdep/gfx.h"
|
||||
#include "lib/sysdep/os_cpu.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Filesystem.h"
|
||||
#include "ps/UserReport.h"
|
||||
#include "ps/VideoMode.h"
|
||||
#include "ps/GameSetup/Config.h"
|
||||
|
||||
@@ -35,8 +38,12 @@ void SetDisableAudio(void* UNUSED(cbdata), bool disabled)
|
||||
g_DisableAudio = disabled;
|
||||
}
|
||||
|
||||
static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings);
|
||||
|
||||
void RunHardwareDetection()
|
||||
{
|
||||
TIMER(L"RunHardwareDetection");
|
||||
|
||||
ScriptInterface& scriptInterface = g_ScriptingHost.GetScriptInterface();
|
||||
|
||||
scriptInterface.RegisterFunction<void, bool, &SetDisableAudio>("SetDisableAudio");
|
||||
@@ -57,47 +64,195 @@ void RunHardwareDetection()
|
||||
scriptInterface.LoadScript(scriptName, code);
|
||||
|
||||
// Collect all the settings we'll pass to the script:
|
||||
// (We'll use this same data for the opt-in online reporting system, so it
|
||||
// includes some fields that aren't directly useful for the hwdetect script)
|
||||
|
||||
CScriptValRooted settings;
|
||||
scriptInterface.Eval("({})", settings);
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX, false);
|
||||
scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX, false);
|
||||
scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX, false);
|
||||
scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN, false);
|
||||
scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX);
|
||||
scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX);
|
||||
scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX);
|
||||
scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN);
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card), false);
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver), false);
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem, false);
|
||||
scriptInterface.SetProperty(settings.get(), "arch_ia32", ARCH_IA32);
|
||||
scriptInterface.SetProperty(settings.get(), "arch_amd64", ARCH_AMD64);
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "gl_vendor", std::string((const char*)glGetString(GL_VENDOR)), false);
|
||||
scriptInterface.SetProperty(settings.get(), "gl_renderer", std::string((const char*)glGetString(GL_RENDERER)), false);
|
||||
scriptInterface.SetProperty(settings.get(), "gl_version", std::string((const char*)glGetString(GL_VERSION)), false);
|
||||
#ifdef NDEBUG
|
||||
scriptInterface.SetProperty(settings.get(), "build_debug", 0);
|
||||
#else
|
||||
scriptInterface.SetProperty(settings.get(), "build_debug", 1);
|
||||
#endif
|
||||
scriptInterface.SetProperty(settings.get(), "build_datetime", std::string(__DATE__ " " __TIME__));
|
||||
scriptInterface.SetProperty(settings.get(), "build_revision", std::wstring(svn_revision));
|
||||
|
||||
const char* exts = ogl_ExtensionString();
|
||||
if (!exts) exts = "";
|
||||
scriptInterface.SetProperty(settings.get(), "gl_extensions", std::string(exts), false);
|
||||
scriptInterface.SetProperty(settings.get(), "gl_max_tex_size", (int)ogl_max_tex_size, false);
|
||||
scriptInterface.SetProperty(settings.get(), "gl_max_tex_units", (int)ogl_max_tex_units, false);
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card));
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver));
|
||||
scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem);
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes(), false);
|
||||
scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes(), false);
|
||||
scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP(), false);
|
||||
ReportGLLimits(scriptInterface, settings);
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes());
|
||||
scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes());
|
||||
scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP());
|
||||
|
||||
struct utsname un;
|
||||
uname(&un);
|
||||
scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname), false);
|
||||
scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release), false);
|
||||
scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version), false);
|
||||
scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine), false);
|
||||
scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname));
|
||||
scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release));
|
||||
scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version));
|
||||
scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine));
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()), false);
|
||||
scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency(), false);
|
||||
scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()));
|
||||
scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency());
|
||||
|
||||
scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize(), false);
|
||||
scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable(), false);
|
||||
scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize());
|
||||
scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable());
|
||||
|
||||
// Send the same data to the reporting system
|
||||
g_UserReporter.SubmitReport("hwdetect", 1, scriptInterface.StringifyJSON(settings.get(), false));
|
||||
|
||||
// Run the detection script:
|
||||
|
||||
scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "RunHardwareDetection", settings);
|
||||
}
|
||||
|
||||
|
||||
static void ReportGLLimits(ScriptInterface& scriptInterface, CScriptValRooted settings)
|
||||
{
|
||||
#define INTEGER(id) do { \
|
||||
GLint i = -1; \
|
||||
glGetIntegerv(GL_##id, &i); \
|
||||
ogl_WarnIfError(); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id, i); \
|
||||
} while (false)
|
||||
|
||||
#define INTEGER2(id) do { \
|
||||
GLint i[2] = { -1, -1 }; \
|
||||
glGetIntegerv(GL_##id, i); \
|
||||
ogl_WarnIfError(); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", i[0]); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", i[1]); \
|
||||
} while (false)
|
||||
|
||||
#define FLOAT(id) do { \
|
||||
GLfloat f = std::numeric_limits<GLfloat>::quiet_NaN(); \
|
||||
glGetFloatv(GL_##id, &f); \
|
||||
ogl_WarnIfError(); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id, f); \
|
||||
} while (false)
|
||||
|
||||
#define FLOAT2(id) do { \
|
||||
GLfloat f[2] = { std::numeric_limits<GLfloat>::quiet_NaN(), std::numeric_limits<GLfloat>::quiet_NaN() }; \
|
||||
glGetFloatv(GL_##id, f); \
|
||||
ogl_WarnIfError(); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id "[0]", f[0]); \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id "[1]", f[1]); \
|
||||
} while (false)
|
||||
|
||||
#define STRING(id) do { \
|
||||
const char* c = (const char*)glGetString(GL_##id); \
|
||||
ogl_WarnIfError(); \
|
||||
if (!c) c = ""; \
|
||||
scriptInterface.SetProperty(settings.get(), "GL_" #id, std::string(c)); \
|
||||
} while (false)
|
||||
|
||||
#define BOOL(id) INTEGER(id)
|
||||
|
||||
// Core OpenGL 1.3:
|
||||
// (We don't bother checking extension strings for anything older than 1.3;
|
||||
// it'll just produce harmless warnings)
|
||||
STRING(VERSION);
|
||||
STRING(VENDOR);
|
||||
STRING(RENDERER);
|
||||
STRING(EXTENSIONS);
|
||||
INTEGER(MAX_LIGHTS);
|
||||
INTEGER(MAX_CLIP_PLANES);
|
||||
if (ogl_HaveExtension("GL_ARB_imaging")) // only in imaging subset
|
||||
INTEGER(MAX_COLOR_MATRIX_STACK_DEPTH);
|
||||
INTEGER(MAX_MODELVIEW_STACK_DEPTH);
|
||||
INTEGER(MAX_PROJECTION_STACK_DEPTH);
|
||||
INTEGER(MAX_TEXTURE_STACK_DEPTH);
|
||||
INTEGER(SUBPIXEL_BITS);
|
||||
INTEGER(MAX_3D_TEXTURE_SIZE);
|
||||
INTEGER(MAX_TEXTURE_SIZE);
|
||||
INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
|
||||
INTEGER(MAX_PIXEL_MAP_TABLE);
|
||||
INTEGER(MAX_NAME_STACK_DEPTH);
|
||||
INTEGER(MAX_LIST_NESTING);
|
||||
INTEGER(MAX_EVAL_ORDER);
|
||||
INTEGER2(MAX_VIEWPORT_DIMS);
|
||||
INTEGER(MAX_ATTRIB_STACK_DEPTH);
|
||||
INTEGER(MAX_CLIENT_ATTRIB_STACK_DEPTH);
|
||||
INTEGER(AUX_BUFFERS);
|
||||
BOOL(RGBA_MODE);
|
||||
BOOL(INDEX_MODE);
|
||||
BOOL(DOUBLEBUFFER);
|
||||
BOOL(STEREO);
|
||||
FLOAT2(ALIASED_POINT_SIZE_RANGE);
|
||||
FLOAT2(SMOOTH_POINT_SIZE_RANGE);
|
||||
FLOAT(SMOOTH_POINT_SIZE_GRANULARITY);
|
||||
FLOAT2(ALIASED_LINE_WIDTH_RANGE);
|
||||
FLOAT2(SMOOTH_LINE_WIDTH_RANGE);
|
||||
FLOAT(SMOOTH_LINE_WIDTH_GRANULARITY);
|
||||
// Skip MAX_CONVOLUTION_WIDTH, MAX_CONVOLUTION_HEIGHT since they'd need GetConvolutionParameteriv
|
||||
INTEGER(MAX_ELEMENTS_INDICES);
|
||||
INTEGER(MAX_ELEMENTS_VERTICES);
|
||||
INTEGER(MAX_TEXTURE_UNITS);
|
||||
INTEGER(SAMPLE_BUFFERS);
|
||||
INTEGER(SAMPLES);
|
||||
// TODO: compressed texture formats
|
||||
INTEGER(RED_BITS);
|
||||
INTEGER(GREEN_BITS);
|
||||
INTEGER(BLUE_BITS);
|
||||
INTEGER(ALPHA_BITS);
|
||||
INTEGER(INDEX_BITS);
|
||||
INTEGER(DEPTH_BITS);
|
||||
INTEGER(STENCIL_BITS);
|
||||
INTEGER(ACCUM_RED_BITS);
|
||||
INTEGER(ACCUM_GREEN_BITS);
|
||||
INTEGER(ACCUM_BLUE_BITS);
|
||||
INTEGER(ACCUM_ALPHA_BITS);
|
||||
|
||||
// Core OpenGL 2.0 (treated as extensions):
|
||||
|
||||
if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
|
||||
FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
|
||||
|
||||
// Skip GL_ARB_occlusion_query's QUERY_COUNTER_BITS_ARB since it'd need GetQueryiv
|
||||
|
||||
if (ogl_HaveExtension("GL_ARB_shading_language_100"))
|
||||
STRING(SHADING_LANGUAGE_VERSION_ARB);
|
||||
|
||||
if (ogl_HaveExtension("GL_ARB_vertex_shader"))
|
||||
{
|
||||
INTEGER(MAX_VERTEX_ATTRIBS_ARB);
|
||||
INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
|
||||
INTEGER(MAX_VARYING_FLOATS_ARB);
|
||||
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
|
||||
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
|
||||
}
|
||||
|
||||
if (ogl_HaveExtension("GL_ARB_fragment_shader"))
|
||||
INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
|
||||
|
||||
if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader"))
|
||||
{
|
||||
INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
|
||||
INTEGER(MAX_TEXTURE_COORDS_ARB);
|
||||
}
|
||||
|
||||
if (ogl_HaveExtension("GL_ARB_draw_buffers"))
|
||||
INTEGER(MAX_DRAW_BUFFERS_ARB);
|
||||
|
||||
// Other interesting extensions:
|
||||
|
||||
if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
|
||||
{
|
||||
INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
|
||||
INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
|
||||
}
|
||||
|
||||
if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
|
||||
FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,601 @@
|
||||
/* Copyright (C) 2011 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 "UserReport.h"
|
||||
|
||||
#include "lib/timer.h"
|
||||
#include "lib/external_libraries/curl.h"
|
||||
#include "lib/external_libraries/sdl.h"
|
||||
#include "lib/external_libraries/zlib.h"
|
||||
#include "lib/file/archive/stream.h"
|
||||
#include "lib/sysdep/sysdep.h"
|
||||
#include "ps/ConfigDB.h"
|
||||
#include "ps/Filesystem.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
#define DEBUG_UPLOADS 0
|
||||
|
||||
/*
|
||||
* The basic idea is that the game submits reports to us, which we send over
|
||||
* HTTP to a server for storage and analysis.
|
||||
*
|
||||
* We can't use libcurl's asynchronous 'multi' API, because DNS resolution can
|
||||
* be synchronous and slow (which would make the game pause).
|
||||
* So we use the 'easy' API in a background thread.
|
||||
* The main thread submits reports, toggles whether uploading is enabled,
|
||||
* and polls for the current status (typically to display in the GUI);
|
||||
* the worker thread does all of the uploading.
|
||||
*
|
||||
* It'd be nice to extend this in the future to handle things like crash reports.
|
||||
* The game should store the crashlogs (suitably anonymised) in a directory, and
|
||||
* we should detect those files and upload them when we're restarted and online.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Version number stored in config file when the user agrees to the reporting.
|
||||
* Reporting will be disabled if the config value is missing or is less than
|
||||
* this value. If we start reporting a lot more data, we should increase this
|
||||
* value and get the user to re-confirm.
|
||||
*/
|
||||
static const int REPORTER_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Time interval (seconds) at which the worker thread will check its reconnection
|
||||
* timers. (This should be relatively high so the thread doesn't waste much time
|
||||
* continually waking up.)
|
||||
*/
|
||||
static const double TIMER_CHECK_INTERVAL = 10.0;
|
||||
|
||||
/**
|
||||
* Seconds we should wait before reconnecting to the server after a failure.
|
||||
*/
|
||||
static const double RECONNECT_INVERVAL = 60.0;
|
||||
|
||||
CUserReporter g_UserReporter;
|
||||
|
||||
struct CUserReport
|
||||
{
|
||||
time_t m_Time;
|
||||
std::string m_Type;
|
||||
int m_Version;
|
||||
std::string m_Data;
|
||||
};
|
||||
|
||||
class CUserReporterWorker
|
||||
{
|
||||
public:
|
||||
CUserReporterWorker(const std::string& userID, const std::string& url) :
|
||||
m_UserID(userID), m_Enabled(false), m_Shutdown(false), m_Status("disabled"),
|
||||
m_PauseUntilTime(timer_Time()), m_LastUpdateTime(timer_Time())
|
||||
{
|
||||
// Set up libcurl:
|
||||
|
||||
m_Curl = curl_easy_init();
|
||||
debug_assert(m_Curl);
|
||||
|
||||
#if DEBUG_UPLOADS
|
||||
curl_easy_setopt(m_Curl, CURLOPT_VERBOSE, 1L);
|
||||
#endif
|
||||
|
||||
// Capture error messages
|
||||
curl_easy_setopt(m_Curl, CURLOPT_ERRORBUFFER, m_ErrorBuffer);
|
||||
|
||||
// Disable signal handlers (required for multithreaded applications)
|
||||
curl_easy_setopt(m_Curl, CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
// To minimise security risks, don't support redirects
|
||||
curl_easy_setopt(m_Curl, CURLOPT_FOLLOWLOCATION, 0L);
|
||||
|
||||
// Set IO callbacks
|
||||
curl_easy_setopt(m_Curl, CURLOPT_WRITEFUNCTION, ReceiveCallback);
|
||||
curl_easy_setopt(m_Curl, CURLOPT_WRITEDATA, this);
|
||||
curl_easy_setopt(m_Curl, CURLOPT_READFUNCTION, SendCallback);
|
||||
curl_easy_setopt(m_Curl, CURLOPT_READDATA, this);
|
||||
|
||||
// Set URL to POST to
|
||||
curl_easy_setopt(m_Curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(m_Curl, CURLOPT_POST, 1L);
|
||||
|
||||
// Set up HTTP headers
|
||||
m_Headers = NULL;
|
||||
// Set the UA string
|
||||
std::string ua = "User-Agent: 0ad ";
|
||||
ua += curl_version();
|
||||
ua += " (http://wildfiregames.com/0ad/)";
|
||||
m_Headers = curl_slist_append(m_Headers, ua.c_str());
|
||||
// Override the default application/x-www-form-urlencoded type since we're not using that type
|
||||
m_Headers = curl_slist_append(m_Headers, "Content-Type: application/octet-stream");
|
||||
// Disable the Accept header because it's a waste of a dozen bytes
|
||||
m_Headers = curl_slist_append(m_Headers, "Accept: ");
|
||||
curl_easy_setopt(m_Curl, CURLOPT_HTTPHEADER, m_Headers);
|
||||
|
||||
|
||||
// Set up the worker thread:
|
||||
|
||||
// Use SDL semaphores since OS X doesn't implement sem_init
|
||||
m_WorkerSem = SDL_CreateSemaphore(0);
|
||||
debug_assert(m_WorkerSem);
|
||||
|
||||
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
|
||||
debug_assert(ret == 0);
|
||||
}
|
||||
|
||||
~CUserReporterWorker()
|
||||
{
|
||||
// Clean up resources
|
||||
|
||||
SDL_DestroySemaphore(m_WorkerSem);
|
||||
|
||||
curl_slist_free_all(m_Headers);
|
||||
curl_easy_cleanup(m_Curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by main thread, when the online reporting is enabled/disabled.
|
||||
*/
|
||||
void SetEnabled(bool enabled)
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
if (enabled != m_Enabled)
|
||||
{
|
||||
m_Enabled = enabled;
|
||||
|
||||
// Wake up the worker thread
|
||||
SDL_SemPost(m_WorkerSem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by main thread to request shutdown.
|
||||
* Returns true if we've shut down successfully.
|
||||
* Returns false if shutdown is taking too long (we might be blocked on a
|
||||
* sync network operation) - you mustn't destroy this object, just leak it
|
||||
* and terminate.
|
||||
*/
|
||||
bool Shutdown()
|
||||
{
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_Shutdown = true;
|
||||
}
|
||||
|
||||
// Wake up the worker thread
|
||||
SDL_SemPost(m_WorkerSem);
|
||||
|
||||
// Wait for it to shut down cleanly
|
||||
// TODO: should have a timeout in case of network hangs
|
||||
pthread_join(m_WorkerThread, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by main thread to determine the current status of the uploader.
|
||||
*/
|
||||
std::string GetStatus()
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
return m_Status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by main thread to add a new report to the queue.
|
||||
*/
|
||||
void Submit(const shared_ptr<CUserReport>& report)
|
||||
{
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_ReportQueue.push_back(report);
|
||||
}
|
||||
|
||||
// Wake up the worker thread
|
||||
SDL_SemPost(m_WorkerSem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the main thread every frame, so we can check
|
||||
* retransmission timers.
|
||||
*/
|
||||
void Update()
|
||||
{
|
||||
double now = timer_Time();
|
||||
if (now > m_LastUpdateTime + TIMER_CHECK_INTERVAL)
|
||||
{
|
||||
// Wake up the worker thread
|
||||
SDL_SemPost(m_WorkerSem);
|
||||
|
||||
m_LastUpdateTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void* RunThread(void* data)
|
||||
{
|
||||
debug_SetThreadName("CUserReportWorker");
|
||||
|
||||
static_cast<CUserReporterWorker*>(data)->Run();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Run()
|
||||
{
|
||||
/*
|
||||
* We use a semaphore to let the thread be woken up when it has
|
||||
* work to do. Various actions from the main thread can wake it:
|
||||
* * SetEnabled()
|
||||
* * Shutdown()
|
||||
* * Submit()
|
||||
* * Retransmission timeouts, once every several seconds
|
||||
*
|
||||
* If multiple actions have triggered wakeups, we might respond to
|
||||
* all of those actions after the first wakeup, which is okay (we'll do
|
||||
* nothing during the subsequent wakeups). We should never hang due to
|
||||
* processing fewer actions than wakeups.
|
||||
*
|
||||
* Retransmission timeouts are triggered via the main thread - we can't simply
|
||||
* use SDL_SemWaitTimeout because on Linux it's implemented as an inefficient
|
||||
* busy-wait loop, and we can't use a manual busy-wait with a long delay time
|
||||
* because we'd lose responsiveness. So the main thread pings the worker
|
||||
* occasionally so it can check its timer.
|
||||
*/
|
||||
|
||||
// Wait until the main thread wakes us up
|
||||
while (SDL_SemWait(m_WorkerSem) == 0)
|
||||
{
|
||||
// Handle shutdown requests as soon as possible
|
||||
if (GetShutdown())
|
||||
return;
|
||||
|
||||
// If we're not enabled, ignore this wakeup
|
||||
if (!GetEnabled())
|
||||
continue;
|
||||
|
||||
// If we're still pausing due to a failed connection,
|
||||
// go back to sleep again
|
||||
if (timer_Time() < m_PauseUntilTime)
|
||||
continue;
|
||||
|
||||
// We're enabled, so process as many reports as possible
|
||||
while (ProcessReport())
|
||||
{
|
||||
// Handle shutdowns while we were sending the report
|
||||
if (GetShutdown())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GetEnabled()
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
return m_Enabled;
|
||||
}
|
||||
|
||||
bool GetShutdown()
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
return m_Shutdown;
|
||||
}
|
||||
|
||||
void SetStatus(const std::string& status)
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_Status = status;
|
||||
#if DEBUG_UPLOADS
|
||||
debug_printf(L">>> CUserReporterWorker status: %hs\n", status.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ProcessReport()
|
||||
{
|
||||
shared_ptr<CUserReport> report;
|
||||
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
if (m_ReportQueue.empty())
|
||||
return false;
|
||||
report = m_ReportQueue.front();
|
||||
m_ReportQueue.pop_front();
|
||||
}
|
||||
|
||||
ConstructRequestData(*report);
|
||||
m_RequestDataOffset = 0;
|
||||
m_ResponseData.clear();
|
||||
|
||||
curl_easy_setopt(m_Curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)m_RequestData.size());
|
||||
|
||||
SetStatus("connecting");
|
||||
|
||||
#if DEBUG_UPLOADS
|
||||
TIMER(L"CUserReporterWorker request");
|
||||
#endif
|
||||
|
||||
CURLcode err = curl_easy_perform(m_Curl);
|
||||
|
||||
#if DEBUG_UPLOADS
|
||||
printf(">>>\n%s\n<<<\n", m_ResponseData.c_str());
|
||||
#endif
|
||||
|
||||
if (err == CURLE_OK)
|
||||
{
|
||||
long code = -1;
|
||||
curl_easy_getinfo(m_Curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
SetStatus("completed:" + CStr(code));
|
||||
|
||||
// Check for success code
|
||||
if (code == 200)
|
||||
return true;
|
||||
|
||||
// If the server returns the 410 Gone status, interpret that as meaning
|
||||
// it no longer supports uploads (at least from this version of the game),
|
||||
// so shut down and stop talking to it (to avoid wasting bandwidth)
|
||||
if (code == 410)
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_Shutdown = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatus("failed:" + CStr(err) + ":" + m_ErrorBuffer);
|
||||
}
|
||||
|
||||
// We got an unhandled return code or a connection failure;
|
||||
// push this report back onto the queue and try again after
|
||||
// a long interval
|
||||
|
||||
{
|
||||
CScopeLock lock(m_WorkerMutex);
|
||||
m_ReportQueue.push_front(report);
|
||||
}
|
||||
|
||||
m_PauseUntilTime = timer_Time() + RECONNECT_INVERVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConstructRequestData(const CUserReport& report)
|
||||
{
|
||||
// Construct the POST request data in the application/x-www-form-urlencoded format
|
||||
|
||||
std::string r;
|
||||
|
||||
r += "user_id=";
|
||||
AppendEscaped(r, m_UserID);
|
||||
|
||||
r += "&time=" + CStr(report.m_Time);
|
||||
|
||||
r += "&type=";
|
||||
AppendEscaped(r, report.m_Type);
|
||||
|
||||
r += "&version=" + CStr(report.m_Version);
|
||||
|
||||
r += "&data=";
|
||||
AppendEscaped(r, report.m_Data);
|
||||
|
||||
// Compress the content with zlib to save bandwidth.
|
||||
// (Note that we send a request with unlabelled compressed data instead
|
||||
// of using Content-Encoding, because Content-Encoding is a mess and causes
|
||||
// problems with servers and breaks Content-Length and this is much easier.)
|
||||
std::string compressed;
|
||||
compressed.resize(compressBound(r.size()));
|
||||
uLongf destLen = compressed.size();
|
||||
int ok = compress((Bytef*)compressed.c_str(), &destLen, (const Bytef*)r.c_str(), r.size());
|
||||
debug_assert(ok == Z_OK);
|
||||
compressed.resize(destLen);
|
||||
|
||||
m_RequestData.swap(compressed);
|
||||
}
|
||||
|
||||
void AppendEscaped(std::string& buffer, const std::string& str)
|
||||
{
|
||||
char* escaped = curl_easy_escape(m_Curl, str.c_str(), str.size());
|
||||
buffer += escaped;
|
||||
curl_free(escaped);
|
||||
}
|
||||
|
||||
static size_t ReceiveCallback(void* buffer, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
|
||||
|
||||
if (self->GetShutdown())
|
||||
return 0; // signals an error
|
||||
|
||||
self->m_ResponseData += std::string((char*)buffer, (char*)buffer+size*nmemb);
|
||||
|
||||
return size*nmemb;
|
||||
}
|
||||
|
||||
static size_t SendCallback(char* bufptr, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
CUserReporterWorker* self = static_cast<CUserReporterWorker*>(userp);
|
||||
|
||||
if (self->GetShutdown())
|
||||
return CURL_READFUNC_ABORT; // signals an error
|
||||
|
||||
// We can return as much data as available, up to the buffer size
|
||||
size_t amount = std::min(self->m_RequestData.size() - self->m_RequestDataOffset, size*nmemb);
|
||||
|
||||
// ...But restrict to sending a small amount at once, so that we remain
|
||||
// responsive to shutdown requests even if the network is pretty slow
|
||||
amount = std::min((size_t)1024, amount);
|
||||
|
||||
memcpy(bufptr, &self->m_RequestData[self->m_RequestDataOffset], amount);
|
||||
self->m_RequestDataOffset += amount;
|
||||
|
||||
self->SetStatus("sending:" + CStr((float)self->m_RequestDataOffset / self->m_RequestData.size()));
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
private:
|
||||
// Thread-related members:
|
||||
pthread_t m_WorkerThread;
|
||||
CMutex m_WorkerMutex;
|
||||
SDL_sem* m_WorkerSem;
|
||||
|
||||
// Shared by main thread and worker thread:
|
||||
// These variables are all protected by m_WorkerMutex
|
||||
std::deque<shared_ptr<CUserReport> > m_ReportQueue;
|
||||
bool m_Enabled;
|
||||
bool m_Shutdown;
|
||||
std::string m_Status;
|
||||
|
||||
// Initialised in constructor by main thread; otherwise used only by worker thread:
|
||||
std::string m_UserID;
|
||||
CURL* m_Curl;
|
||||
curl_slist* m_Headers;
|
||||
double m_PauseUntilTime;
|
||||
|
||||
// Only used by worker thread:
|
||||
std::string m_ResponseData;
|
||||
std::string m_RequestData;
|
||||
size_t m_RequestDataOffset;
|
||||
char m_ErrorBuffer[CURL_ERROR_SIZE];
|
||||
|
||||
// Only used by main thread:
|
||||
double m_LastUpdateTime;
|
||||
};
|
||||
|
||||
|
||||
|
||||
CUserReporter::CUserReporter() :
|
||||
m_Worker(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
CUserReporter::~CUserReporter()
|
||||
{
|
||||
debug_assert(!m_Worker); // Deinitialize should have been called before shutdown
|
||||
}
|
||||
|
||||
std::string CUserReporter::LoadUserID()
|
||||
{
|
||||
std::string userID;
|
||||
|
||||
// Read the user ID from user.cfg (if there is one)
|
||||
CFG_GET_USER_VAL("userreport.id", String, userID);
|
||||
|
||||
// If we don't have a validly-formatted user ID, generate a new one
|
||||
if (userID.length() != 16)
|
||||
{
|
||||
u8 bytes[8] = {0};
|
||||
sys_generate_random_bytes(bytes, ARRAY_SIZE(bytes));
|
||||
// ignore failures - there's not much we can do about it
|
||||
|
||||
userID = "";
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bytes); ++i)
|
||||
{
|
||||
char hex[3];
|
||||
sprintf_s(hex, ARRAY_SIZE(hex), "%02x", bytes[i]);
|
||||
userID += hex;
|
||||
}
|
||||
|
||||
g_ConfigDB.CreateValue(CFG_USER, "userreport.id")->m_String = userID;
|
||||
g_ConfigDB.WriteFile(CFG_USER);
|
||||
}
|
||||
|
||||
return userID;
|
||||
}
|
||||
|
||||
bool CUserReporter::IsReportingEnabled()
|
||||
{
|
||||
int version = -1;
|
||||
CFG_GET_USER_VAL("userreport.enabledversion", Int, version);
|
||||
return (version >= REPORTER_VERSION);
|
||||
}
|
||||
|
||||
void CUserReporter::SetReportingEnabled(bool enabled)
|
||||
{
|
||||
CStr val(enabled ? REPORTER_VERSION : 0);
|
||||
g_ConfigDB.CreateValue(CFG_USER, "userreport.enabledversion")->m_String = val;
|
||||
g_ConfigDB.WriteFile(CFG_USER);
|
||||
|
||||
if (m_Worker)
|
||||
m_Worker->SetEnabled(enabled);
|
||||
}
|
||||
|
||||
std::string CUserReporter::GetStatus()
|
||||
{
|
||||
if (!m_Worker)
|
||||
return "disabled";
|
||||
|
||||
return m_Worker->GetStatus();
|
||||
}
|
||||
|
||||
|
||||
void CUserReporter::Initialize()
|
||||
{
|
||||
debug_assert(!m_Worker); // must only be called once
|
||||
|
||||
std::string userID = LoadUserID();
|
||||
std::string url;
|
||||
CFG_GET_SYS_VAL("userreport.url", String, url);
|
||||
|
||||
// Initialise everything except Win32 sockets (because our networking
|
||||
// system already inits those)
|
||||
curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32);
|
||||
|
||||
m_Worker = new CUserReporterWorker(userID, url);
|
||||
|
||||
m_Worker->SetEnabled(IsReportingEnabled());
|
||||
}
|
||||
|
||||
void CUserReporter::Deinitialize()
|
||||
{
|
||||
if (!m_Worker)
|
||||
return;
|
||||
|
||||
if (m_Worker->Shutdown())
|
||||
{
|
||||
// Worker was shut down cleanly
|
||||
|
||||
SAFE_DELETE(m_Worker);
|
||||
curl_global_cleanup();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Worker failed to shut down in a reasonable time
|
||||
// Leak the resources (since that's better than hanging or crashing)
|
||||
m_Worker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CUserReporter::Update()
|
||||
{
|
||||
if (m_Worker)
|
||||
m_Worker->Update();
|
||||
}
|
||||
|
||||
void CUserReporter::SubmitReport(const char* type, int version, const std::string& data)
|
||||
{
|
||||
// If not initialised, discard the report
|
||||
if (!m_Worker)
|
||||
return;
|
||||
|
||||
shared_ptr<CUserReport> report(new CUserReport);
|
||||
report->m_Time = time(NULL);
|
||||
report->m_Type = type;
|
||||
report->m_Version = version;
|
||||
report->m_Data = data;
|
||||
|
||||
m_Worker->Submit(report);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2011 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/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_USERREPORT
|
||||
#define INCLUDED_USERREPORT
|
||||
|
||||
class CUserReporterWorker;
|
||||
|
||||
class CUserReporter
|
||||
{
|
||||
public:
|
||||
CUserReporter();
|
||||
~CUserReporter();
|
||||
|
||||
void Initialize();
|
||||
void Deinitialize();
|
||||
|
||||
/**
|
||||
* Must be called frequently (preferably every frame), to update some
|
||||
* internal reconnection timers.
|
||||
*/
|
||||
void Update();
|
||||
|
||||
// Functions for the GUI to control the reporting:
|
||||
bool IsReportingEnabled();
|
||||
void SetReportingEnabled(bool enabled);
|
||||
std::string GetStatus();
|
||||
|
||||
/**
|
||||
* Submit a report to be transmitted to the online server.
|
||||
* Nothing will be transmitted until reporting is enabled by the user, so
|
||||
* you don't need to check for that first.
|
||||
* @param type short string identifying the type of data ("hwdetect", "message", etc)
|
||||
* @param version positive integer that should be incremented if the data is changed in
|
||||
* a non-compatible way and the server will have to distinguish old and new formats
|
||||
* @param data the actual data (typically UTF-8-encoded text, or JSON, but could be binary)
|
||||
*/
|
||||
void SubmitReport(const char* type, int version, const std::string& data);
|
||||
|
||||
private:
|
||||
std::string LoadUserID();
|
||||
|
||||
CUserReporterWorker* m_Worker;
|
||||
};
|
||||
|
||||
extern CUserReporter g_UserReporter;
|
||||
|
||||
#endif // INCLUDED_USERREPORT
|
||||
@@ -176,14 +176,14 @@ public:
|
||||
* Optionally makes it {ReadOnly, DontDelete, DontEnum}.
|
||||
*/
|
||||
template<typename T>
|
||||
bool SetProperty(jsval obj, const char* name, const T& value, bool constant, bool enumerate = true);
|
||||
bool SetProperty(jsval obj, const char* name, const T& value, bool constant = false, bool enumerate = true);
|
||||
|
||||
/**
|
||||
* Set the integer-named property on the given object.
|
||||
* Optionally makes it {ReadOnly, DontDelete, DontEnum}.
|
||||
*/
|
||||
template<typename T>
|
||||
bool SetPropertyInt(jsval obj, int name, const T& value, bool constant, bool enumerate = true);
|
||||
bool SetPropertyInt(jsval obj, int name, const T& value, bool constant = false, bool enumerate = true);
|
||||
|
||||
template<typename T>
|
||||
bool GetProperty(jsval obj, const char* name, T& out);
|
||||
|
||||
Reference in New Issue
Block a user