# 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:
Ykkrosh
2011-02-16 20:40:15 +00:00
parent 1c3c962ea1
commit 0da7e822ff
24 changed files with 1165 additions and 105 deletions
+2
View File
@@ -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();
+10
View File
@@ -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);
}
+31
View File
@@ -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");
+4
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+13 -26
View File
@@ -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
+8
View File
@@ -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)
+182 -27
View File
@@ -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);
}
+601
View File
@@ -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);
}
+62
View File
@@ -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
+2 -2
View File
@@ -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);