Index: ps/trunk/source/graphics/ObjectManager.cpp
===================================================================
--- ps/trunk/source/graphics/ObjectManager.cpp (revision 25325)
+++ ps/trunk/source/graphics/ObjectManager.cpp (revision 25326)
@@ -1,195 +1,193 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "ObjectManager.h"
#include "graphics/ObjectBase.h"
#include "graphics/ObjectEntry.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpVisual.h"
bool CObjectManager::ObjectKey::operator< (const CObjectManager::ObjectKey& a) const
{
if (ObjectBaseIdentifier < a.ObjectBaseIdentifier)
return true;
else if (ObjectBaseIdentifier > a.ObjectBaseIdentifier)
return false;
else
return ActorVariation < a.ActorVariation;
}
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
CObjectManager::CObjectManager(CMeshManager& meshManager, CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
: m_MeshManager(meshManager), m_SkeletonAnimManager(skeletonAnimManager), m_Simulation(simulation)
{
RegisterFileReloadFunc(ReloadChangedFileCB, this);
- m_QualityHook = std::make_unique(g_ConfigDB.RegisterHookAndCall("max_actor_quality", [this]() { this->ActorQualityChanged(); }));
+ m_QualityHook = std::make_unique(g_ConfigDB.RegisterHookAndCall("max_actor_quality", [this]() { ActorQualityChanged(); }));
if (!CXeromyces::AddValidator(g_VFS, "actor", "art/actors/actor.rng"))
LOGERROR("CObjectManager: failed to load actor grammar file 'art/actors/actor.rng'");
}
CObjectManager::~CObjectManager()
{
UnloadObjects();
- g_ConfigDB.UnregisterHook(std::move(m_QualityHook));
-
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
std::pair CObjectManager::FindActorDef(const CStrW& actorName)
{
ENSURE(!actorName.empty());
decltype(m_ActorDefs)::iterator it = m_ActorDefs.find(actorName);
if (it != m_ActorDefs.end() && !it->second.outdated)
return { true, *it->second.obj };
std::unique_ptr actor = std::make_unique(*this);
VfsPath pathname = VfsPath("art/actors/") / actorName;
bool success = true;
if (!actor->Load(pathname))
{
// In case of failure, load a placeholder - we want to have an actor around for hotloading.
// (this will leave garbage actors in the object manager if loading files with typos in the name,
// but that's unlikely to be a large memory problem).
LOGERROR("CObjectManager::FindActorDef(): Cannot find actor '%s'", utf8_from_wstring(actorName));
actor->LoadErrorPlaceholder(pathname);
success = false;
}
return { success, *m_ActorDefs.insert_or_assign(actorName, std::move(actor)).first->second.obj };
}
CObjectEntry* CObjectManager::FindObjectVariation(const CActorDef* actor, const std::vector>& selections, uint32_t seed)
{
if (!actor)
return nullptr;
const std::shared_ptr& base = actor->GetBase(m_QualityLevel);
std::vector*> completeSelections;
for (const std::set& selectionSet : selections)
completeSelections.emplace_back(&selectionSet);
// To maintain a consistent look between quality levels, first complete with the highest-quality variants.
// then complete again at the required quality level (since not all variants may be available).
std::set highQualitySelections = actor->GetBase(255)->CalculateRandomRemainingSelections(seed, selections);
completeSelections.emplace_back(&highQualitySelections);
// We don't have to pass the high-quality selections here because they have higher priority anyways.
std::set remainingSelections = base->CalculateRandomRemainingSelections(seed, selections);
completeSelections.emplace_back(&remainingSelections);
return FindObjectVariation(base, completeSelections);
}
CObjectEntry* CObjectManager::FindObjectVariation(const std::shared_ptr& base, const std::vector*>& completeSelections)
{
PROFILE2("FindObjectVariation");
// Look to see whether this particular variation has already been loaded
std::vector choices = base->CalculateVariationKey(completeSelections);
ObjectKey key (base->GetIdentifier(), choices);
decltype(m_Objects)::iterator it = m_Objects.find(key);
if (it != m_Objects.end() && !it->second.outdated)
return it->second.obj.get();
// If it hasn't been loaded, load it now.
std::unique_ptr obj = std::make_unique(base, m_Simulation);
// TODO (for some efficiency): use the pre-calculated choices for this object,
// which has already worked out what to do for props, instead of passing the
// selections into BuildVariation and having it recalculate the props' choices.
if (!obj->BuildVariation(completeSelections, choices, *this))
return nullptr;
return m_Objects.insert_or_assign(key, std::move(obj)).first->second.obj.get();
}
CTerrain* CObjectManager::GetTerrain()
{
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (!cmpTerrain)
return NULL;
return cmpTerrain->GetCTerrain();
}
void CObjectManager::UnloadObjects()
{
m_Objects.clear();
m_ActorDefs.clear();
}
Status CObjectManager::ReloadChangedFile(const VfsPath& path)
{
// Mark old entries as outdated so we don't reload them from the cache
for (std::pair>& object : m_Objects)
if (!object.second.outdated && object.second.obj->m_Base->UsesFile(path))
object.second.outdated = true;
const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
// Reload actors that use a changed object
for (std::pair>& actor : m_ActorDefs)
{
if (!actor.second.outdated && actor.second.obj->UsesFile(path))
actor.second.outdated = true;
// Slightly ugly hack: The graphics system doesn't preserve enough information to regenerate the
// object with all correct variations, and we don't want to waste space storing it just for the
// rare occurrence of hotloading, so we'll tell the component (which does preserve the information)
// to do the reloading itself
for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
static_cast(eit->second)->Hotload(actor.first);
}
return INFO::OK;
}
void CObjectManager::ActorQualityChanged()
{
int quality;
CFG_GET_VAL("max_actor_quality", quality);
if (quality == m_QualityLevel)
return;
m_QualityLevel = quality > 255 ? 255 : quality < 0 ? 0 : quality;
// No need to reload entries or actors, but we do need to reload all units.
const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
static_cast(eit->second)->Hotload();
// Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise.
m_Simulation.Interpolate(0.f, 0.f, 0.f);
}
Index: ps/trunk/source/gui/tests/test_GuiManager.h
===================================================================
--- ps/trunk/source/gui/tests/test_GuiManager.h (revision 25325)
+++ ps/trunk/source/gui/tests/test_GuiManager.h (revision 25326)
@@ -1,195 +1,196 @@
/* Copyright (C) 2021 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 .
*/
#include "lib/self_test.h"
#include "gui/GUIManager.h"
#include "gui/CGUI.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Hotkey.h"
#include "ps/XML/Xeromyces.h"
+#include
+
class TestGuiManager : public CxxTest::TestSuite
{
- CConfigDB* configDB;
+ std::unique_ptr configDB;
public:
void setUp()
{
g_VFS = CreateVfs();
TS_ASSERT_OK(g_VFS->Mount(L"", DataDir() / "mods" / "_test.gui" / "", VFS_MOUNT_MUST_EXIST));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY));
- configDB = new CConfigDB;
+ configDB = std::make_unique();
CXeromyces::Startup();
g_GUI = new CGUIManager();
}
void tearDown()
{
delete g_GUI;
CXeromyces::Terminate();
- delete configDB;
+ configDB.reset();
g_VFS.reset();
DeleteDirectory(DataDir()/"_testcache");
}
void test_EventObject()
{
// Load up a test page.
const ScriptInterface& scriptInterface = *(g_GUI->GetScriptInterface());
ScriptRequest rq(scriptInterface);
JS::RootedValue val(rq.cx);
scriptInterface.CreateObject(rq, &val);
ScriptInterface::StructuredClone data = scriptInterface.WriteStructuredClone(JS::NullHandleValue);
g_GUI->PushPage(L"event/page_event.xml", data, JS::UndefinedHandleValue);
const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
ScriptRequest prq(pageScriptInterface);
JS::RootedValue global(prq.cx, prq.globalValue());
int called_value = 0;
JS::RootedValue js_called_value(prq.cx);
// Ticking will call the onTick handlers of all object. The second
// onTick is configured to disable the onTick handlers of the first and
// third and enable the fourth. So ticking once will only call the
// first and second object. We don't want the fourth object to be
// called, to avoid infinite additions of objects.
g_GUI->TickObjects();
pageScriptInterface.GetProperty(global, "called1", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 1);
pageScriptInterface.GetProperty(global, "called2", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 1);
pageScriptInterface.GetProperty(global, "called3", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 0);
pageScriptInterface.GetProperty(global, "called4", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 0);
// Ticking again will still call the second object, but also the fourth.
g_GUI->TickObjects();
pageScriptInterface.GetProperty(global, "called1", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 1);
pageScriptInterface.GetProperty(global, "called2", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 2);
pageScriptInterface.GetProperty(global, "called3", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 0);
pageScriptInterface.GetProperty(global, "called4", &js_called_value);
ScriptInterface::FromJSVal(prq, js_called_value, called_value);
TS_ASSERT_EQUALS(called_value, 1);
}
void test_hotkeysState()
{
// Load up a fake test hotkey when pressing 'a'.
const char* test_hotkey_name = "hotkey.test";
configDB->SetValueString(CFG_SYSTEM, test_hotkey_name, "A");
LoadHotkeys(*configDB);
// Load up a test page.
const ScriptInterface& scriptInterface = *(g_GUI->GetScriptInterface());
ScriptRequest rq(scriptInterface);
JS::RootedValue val(rq.cx);
scriptInterface.CreateObject(rq, &val);
ScriptInterface::StructuredClone data = scriptInterface.WriteStructuredClone(JS::NullHandleValue);
g_GUI->PushPage(L"hotkey/page_hotkey.xml", data, JS::UndefinedHandleValue);
// Press 'a'.
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_KEYDOWN;
hotkeyNotification.ev.key.keysym.scancode = SDL_SCANCODE_A;
hotkeyNotification.ev.key.repeat = 0;
// Init input and poll the event.
InitInput();
in_push_priority_event(&hotkeyNotification);
SDL_Event_ ev;
while (in_poll_event(&ev))
in_dispatch_event(&ev);
const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
ScriptRequest prq(pageScriptInterface);
JS::RootedValue global(prq.cx, prq.globalValue());
// Ensure that our hotkey state was synchronised with the event itself.
bool hotkey_pressed_value = false;
JS::RootedValue js_hotkey_pressed_value(prq.cx);
pageScriptInterface.GetProperty(global, "state_before", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, true);
hotkey_pressed_value = false;
pageScriptInterface.GetProperty(global, "state_after", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, true);
// We are listening to KeyDown events, so repeat shouldn't matter.
hotkeyNotification.ev.key.repeat = 1;
in_push_priority_event(&hotkeyNotification);
while (in_poll_event(&ev))
in_dispatch_event(&ev);
hotkey_pressed_value = false;
pageScriptInterface.GetProperty(global, "state_before", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, true);
hotkey_pressed_value = false;
pageScriptInterface.GetProperty(global, "state_after", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, true);
hotkeyNotification.ev.type = SDL_KEYUP;
in_push_priority_event(&hotkeyNotification);
while (in_poll_event(&ev))
in_dispatch_event(&ev);
hotkey_pressed_value = true;
pageScriptInterface.GetProperty(global, "state_before", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, false);
hotkey_pressed_value = true;
pageScriptInterface.GetProperty(global, "state_after", &js_hotkey_pressed_value);
ScriptInterface::FromJSVal(prq, js_hotkey_pressed_value, hotkey_pressed_value);
TS_ASSERT_EQUALS(hotkey_pressed_value, false);
- configDB->RemoveValue(CFG_SYSTEM, test_hotkey_name);
UnloadHotkeys();
}
};
Index: ps/trunk/source/ps/ConfigDB.cpp
===================================================================
--- ps/trunk/source/ps/ConfigDB.cpp (revision 25325)
+++ ps/trunk/source/ps/ConfigDB.cpp (revision 25326)
@@ -1,508 +1,536 @@
/* Copyright (C) 2021 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 .
*/
#include "precompiled.h"
#include "ConfigDB.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/file/vfs/vfs_path.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Filesystem.h"
#include
#include
#include
namespace
{
void TriggerAllHooks(const std::multimap>& hooks, const CStr& name)
{
std::for_each(hooks.lower_bound(name), hooks.upper_bound(name), [](const std::pair>& hook) { hook.second(); });
}
-std::recursive_mutex g_ConfigDBMutex;
-
// These entries will not be printed to logfiles, so that logfiles can be shared without leaking personal or sensitive data
const std::unordered_set g_UnloggedEntries = {
"lobby.password",
"lobby.buddies",
"userreport.id" // authentication token for GDPR personal data requests
};
#define CHECK_NS(rval)\
do {\
if (ns < 0 || ns >= CFG_LAST)\
{\
debug_warn(L"CConfigDB: Invalid ns value");\
return rval;\
}\
} while (false)
template void Get(const CStr& value, T& ret)
{
std::stringstream ss(value);
ss >> ret;
}
template<> void Get<>(const CStr& value, bool& ret)
{
ret = value == "true";
}
template<> void Get<>(const CStr& value, std::string& ret)
{
ret = value;
}
std::string EscapeString(const CStr& str)
{
std::string ret;
for (size_t i = 0; i < str.length(); ++i)
{
if (str[i] == '\\')
ret += "\\\\";
else if (str[i] == '"')
ret += "\\\"";
else
ret += str[i];
}
return ret;
}
} // anonymous namespace
typedef std::map TConfigMap;
-TConfigMap CConfigDB::m_Map[CFG_LAST];
-VfsPath CConfigDB::m_ConfigFile[CFG_LAST];
-bool CConfigDB::m_HasChanges[CFG_LAST];
-
-std::multimap> CConfigDB::m_Hooks;
#define GETVAL(type)\
void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\
{\
CHECK_NS(;);\
- std::lock_guard s(g_ConfigDBMutex);\
+ std::lock_guard s(m_Mutex);\
TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\
if (it != m_Map[CFG_COMMAND].end())\
{\
if (!it->second.empty())\
Get(it->second[0], value);\
return;\
}\
for (int search_ns = ns; search_ns >= 0; --search_ns)\
{\
it = m_Map[search_ns].find(name);\
if (it != m_Map[search_ns].end())\
{\
if (!it->second.empty())\
Get(it->second[0], value);\
return;\
}\
}\
}
GETVAL(bool)
GETVAL(int)
GETVAL(u32)
GETVAL(float)
GETVAL(double)
GETVAL(std::string)
#undef GETVAL
+std::unique_ptr g_ConfigDBPtr;
+
+void CConfigDB::Initialise()
+{
+ g_ConfigDBPtr = std::make_unique();
+}
+
+void CConfigDB::Shutdown()
+{
+ g_ConfigDBPtr.reset();
+}
+
+bool CConfigDB::IsInitialised()
+{
+ return !!g_ConfigDBPtr;
+}
+
+CConfigDB* CConfigDB::Instance()
+{
+ return g_ConfigDBPtr.get();
+}
+
+CConfigDB::CConfigDB()
+{
+}
+
+CConfigDB::~CConfigDB()
+{
+}
+
bool CConfigDB::HasChanges(EConfigNamespace ns) const
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
return m_HasChanges[ns];
}
void CConfigDB::SetChanges(EConfigNamespace ns, bool value)
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
m_HasChanges[ns] = value;
}
void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
- TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);
+ std::lock_guard s(m_Mutex);
+ TConfigMap::const_iterator it = m_Map[CFG_COMMAND].find(name);
if (it != m_Map[CFG_COMMAND].end())
{
values = it->second;
return;
}
for (int search_ns = ns; search_ns >= 0; --search_ns)
{
it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
{
values = it->second;
return;
}
}
}
EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) const
{
CHECK_NS(CFG_LAST);
- std::lock_guard s(g_ConfigDBMutex);
- TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);
+ std::lock_guard s(m_Mutex);
+ TConfigMap::const_iterator it = m_Map[CFG_COMMAND].find(name);
if (it != m_Map[CFG_COMMAND].end())
return CFG_COMMAND;
for (int search_ns = ns; search_ns >= 0; --search_ns)
{
it = m_Map[search_ns].find(name);
if (it != m_Map[search_ns].end())
return (EConfigNamespace)search_ns;
}
return CFG_LAST;
}
std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) const
{
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
std::map ret;
CHECK_NS(ret);
// Loop upwards so that values in later namespaces can override
// values in earlier namespaces
for (int search_ns = 0; search_ns <= ns; ++search_ns)
for (const std::pair& p : m_Map[search_ns])
if (boost::algorithm::starts_with(p.first, prefix))
ret[p.first] = p.second;
for (const std::pair& p : m_Map[CFG_COMMAND])
if (boost::algorithm::starts_with(p.first, prefix))
ret[p.first] = p.second;
return ret;
}
void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value)
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
TConfigMap::iterator it = m_Map[ns].find(name);
if (it == m_Map[ns].end())
it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
if (!it->second.empty())
it->second[0] = value;
else
it->second.emplace_back(value);
TriggerAllHooks(m_Hooks, name);
}
void CConfigDB::SetValueBool(EConfigNamespace ns, const CStr& name, const bool value)
{
CStr valueString = value ? "true" : "false";
SetValueString(ns, name, valueString);
}
void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector values)
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
TConfigMap::iterator it = m_Map[ns].find(name);
if (it == m_Map[ns].end())
it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
it->second = values;
}
void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
TConfigMap::iterator it = m_Map[ns].find(name);
if (it == m_Map[ns].end())
return;
m_Map[ns].erase(it);
TriggerAllHooks(m_Hooks, name);
}
void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path)
{
CHECK_NS(;);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
m_ConfigFile[ns] = path;
}
bool CConfigDB::Reload(EConfigNamespace ns)
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
shared_ptr buffer;
size_t buflen;
{
// Handle missing files quietly
if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0)
{
LOGMESSAGE("Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].string8());
return false;
}
LOGMESSAGE("Loading config file \"%s\"", m_ConfigFile[ns].string8());
Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen);
if (ret != INFO::OK)
{
LOGERROR("CConfigDB::Reload(): vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].string8(), (long long)ret);
return false;
}
}
TConfigMap newMap;
char *filebuf = (char*)buffer.get();
char *filebufend = filebuf+buflen;
bool quoted = false;
CStr header;
CStr name;
CStr value;
int line = 1;
std::vector values;
for (char* pos = filebuf; pos < filebufend; ++pos)
{
switch (*pos)
{
case '\n':
case ';':
break; // We finished parsing this line
case ' ':
case '\r':
case '\t':
continue; // ignore
case '[':
header.clear();
for (++pos; pos < filebufend && *pos != '\n' && *pos != ']'; ++pos)
header.push_back(*pos);
if (pos == filebufend || *pos == '\n')
{
LOGERROR("Config header with missing close tag encountered on line %d in '%s'", line, m_ConfigFile[ns].string8());
header.clear();
++line;
continue;
}
LOGMESSAGE("Found config header '%s'", header.c_str());
header.push_back('.');
while (++pos < filebufend && *pos != '\n' && *pos != ';')
if (*pos != ' ' && *pos != '\r')
{
LOGERROR("Config settings on the same line as a header on line %d in '%s'", line, m_ConfigFile[ns].string8());
break;
}
while (pos < filebufend && *pos != '\n')
++pos;
++line;
continue;
case '=':
// Parse parameters (comma separated, possibly quoted)
for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos)
{
switch (*pos)
{
case '"':
quoted = true;
// parse until not quoted anymore
for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos)
{
if (*pos == '\\' && ++pos == filebufend)
{
LOGERROR("Escape character at end of input (line %d in '%s')", line, m_ConfigFile[ns].string8());
break;
}
value.push_back(*pos);
}
if (pos < filebufend && *pos == '"')
quoted = false;
else
--pos; // We should terminate the outer loop too
break;
case ' ':
case '\r':
case '\t':
break; // ignore
case ',':
if (!value.empty())
values.push_back(value);
value.clear();
break;
default:
value.push_back(*pos);
break;
}
}
if (quoted) // We ignore the invalid parameter
LOGERROR("Unmatched quote while parsing config file '%s' on line %d", m_ConfigFile[ns].string8(), line);
else if (!value.empty())
values.push_back(value);
value.clear();
quoted = false;
break; // We are either at the end of the line, or we still have a comment to parse
default:
name.push_back(*pos);
continue;
}
// Consume the rest of the line
while (pos < filebufend && *pos != '\n')
++pos;
// Store the setting
if (!name.empty())
{
CStr key(header + name);
newMap[key] = values;
if (g_UnloggedEntries.find(key) != g_UnloggedEntries.end())
LOGMESSAGE("Loaded config string \"%s\"", key);
else if (values.empty())
LOGMESSAGE("Loaded config string \"%s\" = (empty)", key);
else
{
std::string vals;
for (size_t i = 0; i + 1 < newMap[key].size(); ++i)
vals += "\"" + EscapeString(newMap[key][i]) + "\", ";
vals += "\"" + EscapeString(newMap[key][values.size()-1]) + "\"";
LOGMESSAGE("Loaded config string \"%s\" = %s", key, vals);
}
}
name.clear();
values.clear();
++line;
}
if (!name.empty())
LOGERROR("Config file does not have a new line after the last config setting '%s'", name);
m_Map[ns].swap(newMap);
return true;
}
bool CConfigDB::WriteFile(EConfigNamespace ns) const
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
return WriteFile(ns, m_ConfigFile[ns]);
}
bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) const
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
shared_ptr buf;
AllocateAligned(buf, 1*MiB, maxSectorSize);
char* pos = (char*)buf.get();
for (const std::pair& p : m_Map[ns])
{
size_t i;
pos += sprintf(pos, "%s = ", p.first.c_str());
for (i = 0; i + 1 < p.second.size(); ++i)
pos += sprintf(pos, "\"%s\", ", EscapeString(p.second[i]).c_str());
if (!p.second.empty())
pos += sprintf(pos, "\"%s\"\n", EscapeString(p.second[i]).c_str());
else
pos += sprintf(pos, "\"\"\n");
}
const size_t len = pos - (char*)buf.get();
Status ret = g_VFS->CreateFile(path, buf, len);
if (ret < 0)
{
LOGERROR("CConfigDB::WriteFile(): CreateFile \"%s\" failed (error: %d)", path.string8(), (int)ret);
return false;
}
return true;
}
bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value)
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
return WriteValueToFile(ns, name, value, m_ConfigFile[ns]);
}
bool CConfigDB::WriteValueToFile(EConfigNamespace ns, const CStr& name, const CStr& value, const VfsPath& path)
{
CHECK_NS(false);
- std::lock_guard s(g_ConfigDBMutex);
+ std::lock_guard s(m_Mutex);
TConfigMap newMap;
m_Map[ns].swap(newMap);
Reload(ns);
SetValueString(ns, name, value);
bool ret = WriteFile(ns, path);
m_Map[ns].swap(newMap);
return ret;
}
CConfigDBHook CConfigDB::RegisterHookAndCall(const CStr& name, std::function hook)
{
hook();
+ std::lock_guard s(m_Mutex);
return CConfigDBHook(*this, m_Hooks.emplace(name, hook));
}
void CConfigDB::UnregisterHook(CConfigDBHook&& hook)
{
if (hook.m_Ptr != m_Hooks.end())
+ {
+ std::lock_guard s(m_Mutex);
m_Hooks.erase(hook.m_Ptr);
+ hook.m_Ptr = m_Hooks.end();
+ }
}
void CConfigDB::UnregisterHook(std::unique_ptr hook)
{
UnregisterHook(std::move(*hook.get()));
}
#undef CHECK_NS
Index: ps/trunk/source/ps/ConfigDB.h
===================================================================
--- ps/trunk/source/ps/ConfigDB.h (revision 25325)
+++ ps/trunk/source/ps/ConfigDB.h (revision 25326)
@@ -1,216 +1,244 @@
/* Copyright (C) 2021 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 .
*/
/*
CConfigDB - Load, access and store configuration variables
TDD : http://www.wildfiregames.com/forum/index.php?showtopic=1125
OVERVIEW:
JavaScript: Check this documentation: http://trac.wildfiregames.com/wiki/Exposed_ConfigDB_Functions
*/
#ifndef INCLUDED_CONFIGDB
#define INCLUDED_CONFIGDB
#include "lib/file/vfs/vfs_path.h"
#include "ps/CStr.h"
-#include "ps/Singleton.h"
+#include
#include