metaforce/Runtime/ConsoleVariables/CVarManager.cpp

308 lines
10 KiB
C++
Raw Normal View History

#include "Runtime/ConsoleVariables/CVarManager.hpp"
#include "Runtime/ConsoleVariables/FileStoreManager.hpp"
#include "Runtime/CBasics.hpp"
#include "Runtime/Streams/CTextInStream.hpp"
#include "Runtime/Streams/CTextOutStream.hpp"
#include "Runtime/Streams/CMemoryInStream.hpp"
#include "Runtime/Streams/CMemoryStreamOut.hpp"
#include "Runtime/CStringExtras.hpp"
#include <logvisor/logvisor.hpp>
#include <algorithm>
#include <memory>
#include <regex>
namespace metaforce {
CVar* com_developer = nullptr;
CVar* com_configfile = nullptr;
CVar* com_enableCheats = nullptr;
CVar* com_cubemaps = nullptr;
2021-06-02 13:16:27 +00:00
static const std::regex cmdLineRegex(R"(\+([\w\.]+)([=])?([\/\\\s\w\.\-]+)?)");
CVarManager* CVarManager::m_instance = nullptr;
static logvisor::Module CVarLog("CVarManager");
CVarManager::CVarManager(FileStoreManager& store, bool useBinary) : m_store(store), m_useBinary(useBinary) {
m_instance = this;
com_configfile =
newCVar("config", "File to store configuration", std::string("config"),
CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::NoDeveloper | CVar::EFlags::Hidden);
com_developer = newCVar("developer", "Enables developer mode", false,
(CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));
com_enableCheats = newCVar(
"cheats", "Enable cheats", false,
(CVar::EFlags::System | CVar::EFlags::ReadOnly | CVar::EFlags::Hidden | CVar::EFlags::InternalArchivable));
com_cubemaps = newCVar("cubemaps", "Enable cubemaps", false,
(CVar::EFlags::Game | CVar::EFlags::ReadOnly | CVar::EFlags::InternalArchivable));
}
CVarManager::~CVarManager() {}
CVar* CVarManager::registerCVar(std::unique_ptr<CVar>&& cvar) {
std::string tmp(cvar->name());
CStringExtras::ToLower(tmp);
if (m_cvars.find(tmp) != m_cvars.end()) {
return nullptr;
}
CVar* ret = cvar.get();
m_cvars.insert_or_assign(std::move(tmp), std::move(cvar));
return ret;
}
CVar* CVarManager::findCVar(std::string_view name) {
std::string lower(name);
CStringExtras::ToLower(lower);
auto search = m_cvars.find(lower);
if (search == m_cvars.end())
return nullptr;
return search->second.get();
}
std::vector<CVar*> CVarManager::archivedCVars() const {
std::vector<CVar*> ret;
for (const auto& pair : m_cvars)
if (pair.second->isArchive())
ret.push_back(pair.second.get());
return ret;
}
std::vector<CVar*> CVarManager::cvars(CVar::EFlags filter) const {
std::vector<CVar*> ret;
for (const auto& pair : m_cvars)
if (filter == CVar::EFlags::Any || True(pair.second->flags() & filter))
ret.push_back(pair.second.get());
return ret;
}
void CVarManager::deserialize(CVar* cvar) {
/* Make sure we're not trying to deserialize a CVar that is invalid*/
if (!cvar) {
return;
}
/* First let's check for a deferred value */
std::string lowName = cvar->name().data();
CStringExtras::ToLower(lowName);
if (const auto iter = m_deferedCVars.find(lowName); iter != m_deferedCVars.end()) {
std::string val = std::move(iter->second);
m_deferedCVars.erase(lowName);
if (cvar->isBoolean() && val.empty()) {
// We were deferred without a value, assume true
cvar->fromBoolean(true);
cvar->m_wasDeserialized = true;
return;
}
if (!val.empty() && cvar->fromLiteralToType(val)) {
cvar->m_wasDeserialized = true;
return;
}
}
/* Enforce isArchive and isInternalArchivable now that we've checked if it's been deferred */
if (!cvar->isArchive() && !cvar->isInternalArchivable()) {
return;
}
/* We were either unable to find a deferred value or got an invalid value */
std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml";
auto container = loadCVars(filename);
auto serialized =
std::find_if(container.cbegin(), container.cend(), [&cvar](const auto& c) { return c.m_name == cvar->name(); });
if (serialized != container.cend()) {
if (cvar->m_value != serialized->m_value) {
CVarUnlocker lc(cvar);
cvar->fromLiteralToType(serialized->m_value);
cvar->m_wasDeserialized = true;
}
}
}
void CVarManager::serialize() {
std::string filename = std::string(m_store.getStoreRoot()) + '/' + com_configfile->toLiteral() + ".yaml";
/* If we have an existing config load it in, so we can update it */
auto container = loadCVars(filename);
u32 minLength = 0;
bool write = false;
for (const auto& pair : m_cvars) {
const auto& cvar = pair.second;
if (cvar->isArchive() || (cvar->isInternalArchivable() && cvar->wasDeserialized() && !cvar->hasDefaultValue())) {
write = true;
/* Look for an existing CVar in the file... */
auto serialized =
std::find_if(container.begin(), container.end(), [&cvar](const auto& c) { return c.m_name == cvar->name(); });
if (serialized != container.end()) {
/* Found it! Update the value */
serialized->m_value = cvar->value();
} else {
/* Store this value as a new CVar in the config */
container.emplace_back(StoreCVar::CVar{std::string(cvar->name()), cvar->value()});
}
}
}
/* Compute length needed for all cvars */
std::for_each(container.cbegin(), container.cend(),
[&minLength](const auto& cvar) { minLength += cvar.m_name.length() + cvar.m_value.length() + 2; });
/* Only write the CVars if any have been modified */
if (!write) {
return;
}
// Allocate enough space to write all the strings with some space to spare
const auto requiredLen = minLength + (4 * container.size());
std::unique_ptr<u8[]> workBuf(new u8[requiredLen]);
CMemoryStreamOut memOut(workBuf.get(), requiredLen, CMemoryStreamOut::EOwnerShip::NotOwned, 32);
CTextOutStream textOut(memOut);
for (const auto& cvar : container) {
auto str = fmt::format(FMT_STRING("{}: {}"), cvar.m_name, cvar.m_value);
textOut.WriteString(str);
}
auto* file = fopen(filename.c_str(), "wbe");
if (file != nullptr) {
u32 writeLen = memOut.GetWritePosition();
u32 offset = 0;
while (offset < writeLen) {
offset += fwrite(workBuf.get() + offset, 1, writeLen - offset, file);
}
fflush(file);
}
fclose(file);
}
std::vector<StoreCVar::CVar> CVarManager::loadCVars(const std::string& filename) const {
std::vector<StoreCVar::CVar> ret;
CBasics::Sstat st;
if (CBasics::Stat(filename.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
auto* file = fopen(filename.c_str(), "rbe");
if (file != nullptr) {
std::unique_ptr<u8[]> inBuf(new u8[st.st_size]);
fread(inBuf.get(), 1, st.st_size, file);
fclose(file);
CMemoryInStream mem(inBuf.get(), st.st_size, CMemoryInStream::EOwnerShip::NotOwned);
CTextInStream textIn(mem, st.st_size);
while (!textIn.IsEOF()) {
auto cvString = textIn.GetNextLine();
if (cvString.empty()) {
continue;
}
auto parts = CStringExtras::Split(cvString, ':');
if (parts.size() < 2) {
continue;
}
ret.emplace_back(StoreCVar::CVar{CStringExtras::Trim(parts[0]), CStringExtras::Trim(parts[1])});
}
}
}
return ret;
}
CVarManager* CVarManager::instance() { return m_instance; }
void CVarManager::setDeveloperMode(bool v, bool setDeserialized) {
com_developer->unlock();
com_developer->fromBoolean(v);
if (setDeserialized)
com_developer->m_wasDeserialized = true;
com_developer->lock();
com_developer->setModified();
}
void CVarManager::setCheatsEnabled(bool v, bool setDeserialized) {
com_enableCheats->unlock();
com_enableCheats->fromBoolean(v);
if (setDeserialized)
com_enableCheats->m_wasDeserialized = true;
com_enableCheats->lock();
com_enableCheats->setModified();
}
bool CVarManager::restartRequired() const {
return std::any_of(m_cvars.cbegin(), m_cvars.cend(), [](const auto& entry) {
return entry.second->isModified() && entry.second->modificationRequiresRestart();
});
}
void CVarManager::parseCommandLine(const std::vector<std::string>& args) {
bool oldDeveloper = suppressDeveloper();
std::string developerName(com_developer->name());
CStringExtras::ToLower(developerName);
for (const std::string& arg : args) {
if (arg[0] != '+') {
continue;
}
std::smatch matches;
std::string cvarName;
std::string cvarValue;
if (std::regex_match(arg, matches, cmdLineRegex)) {
2021-06-02 13:16:27 +00:00
std::vector<std::string> realMatches;
for (auto match : matches) {
if (match.matched) {
realMatches.push_back(match);
}
}
if (realMatches.size() == 2) {
cvarName = matches[1].str();
2021-06-02 13:16:27 +00:00
} else if (realMatches.size() == 4) {
cvarName = matches[1].str();
cvarValue = matches[3].str();
}
}
if (CVar* cv = findCVar(cvarName)) {
if (cvarValue.empty() && cv->isBoolean()) {
// We were set from the command line with an empty value, assume true
cv->fromBoolean(true);
} else if (!cvarValue.empty()) {
cv->fromLiteralToType(cvarValue);
}
cv->m_wasDeserialized = true;
CStringExtras::ToLower(cvarName);
if (developerName == cvarName)
/* Make sure we're not overriding developer mode when we restore */
oldDeveloper = com_developer->toBoolean();
} else {
/* Unable to find an existing CVar, let's defer for the time being 8 */
CStringExtras::ToLower(cvarName);
m_deferedCVars.insert_or_assign(std::move(cvarName), std::move(cvarValue));
}
}
restoreDeveloper(oldDeveloper);
}
bool CVarManager::suppressDeveloper() {
bool oldDeveloper = com_developer->toBoolean();
CVarUnlocker unlock(com_developer);
com_developer->fromBoolean(true);
return oldDeveloper;
}
void CVarManager::restoreDeveloper(bool oldDeveloper) {
CVarUnlocker unlock(com_developer);
com_developer->fromBoolean(oldDeveloper);
}
void CVarManager::proc() {
for (const auto& [name, cvar] : m_cvars) {
if (cvar->isModified() && !cvar->modificationRequiresRestart()) {
cvar->dispatch();
// Clear the modified flag now that we've informed everyone we've changed
cvar->clearModified();
}
}
}
} // namespace metaforce