F4MP/codigos originales/tiltedcode/Code/components/console/ConsoleRegistry.cpp
2026-01-06 18:53:59 +01:00

344 lines
10 KiB
C++

// Copyright (C) 2022 TiltedPhoques SRL.
// For licensing information see LICENSE at the root of this distribution.
#include <algorithm>
#include <optional>
#include <console/ConsoleRegistry.h>
#include <console/ConsoleUtils.h>
#include <console/StringTokenizer.h>
namespace Console
{
namespace
{
constexpr char kCommandPrefix = '/';
std::unique_ptr<TiltedPhoques::String[]> SplitLineTokens(const TiltedPhoques::String& arLine, size_t& tokenCount)
{
if (!CheckIsValidUTF8(arLine))
return nullptr;
StringTokenizer tokenizer(&arLine[1]);
tokenCount = tokenizer.CountTokens();
auto tokens = std::make_unique<TiltedPhoques::String[]>(tokenCount);
size_t i = 0;
while (tokenizer.HasMore())
{
if (i > tokenCount)
{
spdlog::error("Expected {} tokens, got more than {} tokens", tokenCount, i);
return nullptr;
}
tokenizer.GetNext(tokens[i]);
i++;
}
return tokens;
}
std::optional<bool> BoolifyString(std::string_view view)
{
// let the compiler optimize this.
if (view == "true" || view == "True" || view == "TRUE" || view == "1")
return true;
if (view == "false" || view == "False" || view == "FALSE" || view == "0")
return false;
return std::nullopt;
}
} // namespace
ConsoleRegistry::ConsoleRegistry(const char* acLoggerName)
{
m_out = spdlog::get(acLoggerName);
BASE_ASSERT(m_out.get(), "Output logger not found");
BindStaticItems();
RegisterNatives();
}
ConsoleRegistry::~ConsoleRegistry() = default;
void ConsoleRegistry::BindStaticItems()
{
spdlog::trace("ConsoleRegistry::BindStaticItems()");
auto* i = CommandBase::ROOT();
CommandBase::ROOT() = nullptr;
while (i)
{
m_commands.push_back(i);
auto* j = i->next;
i->next = nullptr;
i = j;
}
auto* k = SettingBase::ROOT();
SettingBase::ROOT() = nullptr;
while (k)
{
m_settings.push_back(k);
auto* j = k->next;
k->next = nullptr;
k = j;
}
}
void ConsoleRegistry::RegisterNatives()
{
spdlog::trace("ConsoleRegistry::RegisterNatives()");
RegisterCommand<>(
"help", "Show a list of commands",
[&](const ArgStack&)
{
m_out->info("<------Commands-({})--->", m_commands.size());
for (CommandBase* c : m_commands)
{
m_out->info("/{}: {}", c->m_name, c->m_desc);
}
m_out->info("<------Variables-({})--->", m_settings.size());
for (SettingBase* s : m_settings)
{
if (!s->IsHidden())
m_out->info("{}: {}", s->name, s->desc);
}
});
RegisterCommand<const char*, const char*>(
"set", R"(Set a setting e.g "set mysetting true")",
[&](ArgStack& aStack)
{
const TiltedPhoques::String variableName{aStack.Pop<TiltedPhoques::String>()};
if (variableName.empty())
{
m_out->error("set <varname> <state>");
return;
}
if (auto* pSetting = FindSetting(variableName.c_str()))
{
if (pSetting->IsLocked())
{
m_out->error("Failed apply value: setting is locked");
return;
}
const auto value{aStack.Pop<TiltedPhoques::String>()};
switch (pSetting->type)
{
case SettingBase::Type::kBoolean:
{
auto result = BoolifyString(value);
if (result.has_value())
pSetting->data.as_boolean = *result;
break;
}
case SettingBase::Type::kInt: pSetting->data.as_int32 = ConvertStringValue(value.c_str(), pSetting->data.as_int32); break;
case SettingBase::Type::kUInt: pSetting->data.as_uint32 = ConvertStringValue(value.c_str(), pSetting->data.as_uint32); break;
case SettingBase::Type::kInt64: pSetting->data.as_int64 = ConvertStringValue(value.c_str(), pSetting->data.as_int64); break;
case SettingBase::Type::kUInt64: pSetting->data.as_uint64 = ConvertStringValue(value.c_str(), pSetting->data.as_uint64); break;
case SettingBase::Type::kFloat: pSetting->data.as_float = ConvertStringValue(value.c_str(), pSetting->data.as_float); break;
case SettingBase::Type::kString:
{
static_cast<StringSetting*>(pSetting)->StoreValue(*pSetting, value.c_str());
break;
}
}
// signal to the worker thread that we need to flush the settings
MarkDirty();
m_out->info("Set {} to {}", variableName, value);
return;
}
m_out->error("Failed to find setting {}", variableName);
});
}
void ConsoleRegistry::AddCommand(TiltedPhoques::UniquePtr<CommandBase> apCommand)
{
std::lock_guard _(m_listLock);
(void)_;
// Add to global and tracking pool
m_commands.push_back(apCommand.get());
m_dynamicCommands.push_back(std::move(apCommand));
}
void ConsoleRegistry::AddSetting(TiltedPhoques::UniquePtr<SettingBase> apSetting)
{
std::lock_guard guard(m_listLock);
(void)guard;
m_settings.push_back(apSetting.get());
m_dynamicSettings.push_back(std::move(apSetting));
}
CommandBase* ConsoleRegistry::FindCommand(const char* acName)
{
// TODO: Maybe lookup by some hash...
auto it = std::find_if(
m_commands.begin(), m_commands.end(),
[&](CommandBase* apCommand)
{
TiltedPhoques::String findName(acName);
TiltedPhoques::String settingName(apCommand->m_name);
std::transform(findName.begin(), findName.end(), findName.begin(), [](unsigned char c) { return std::tolower(c); });
std::transform(settingName.begin(), settingName.end(), settingName.begin(), [](unsigned char c) { return std::tolower(c); });
return std::strcmp(settingName.c_str(), findName.c_str()) == 0;
});
if (it == m_commands.end())
return nullptr;
return *it;
}
SettingBase* ConsoleRegistry::FindSetting(const char* acName)
{
// TODO: Maybe lookup by some hash...
auto it = std::find_if(
m_settings.begin(), m_settings.end(),
[&](SettingBase* apSetting)
{
TiltedPhoques::String findName(acName);
TiltedPhoques::String settingName(apSetting->name);
std::transform(findName.begin(), findName.end(), findName.begin(), [](unsigned char c) { return std::tolower(c); });
std::transform(settingName.begin(), settingName.end(), settingName.begin(), [](unsigned char c) { return std::tolower(c); });
return std::strcmp(settingName.c_str(), findName.c_str()) == 0;
});
if (it == m_settings.end())
return nullptr;
return *it;
}
ConsoleRegistry::ExecutionResult ConsoleRegistry::TryExecuteCommand(const TiltedPhoques::String& acLine)
{
if (acLine.length() <= 2 || acLine[0] != kCommandPrefix)
{
m_out->error("Commands must begin with {}", kCommandPrefix);
return ExecutionResult::kFailure;
}
size_t tokenCount = 0;
auto tokens = SplitLineTokens(acLine, tokenCount);
if (!tokenCount)
{
m_out->error("Failed to parse line");
return ExecutionResult::kFailure;
}
auto* pCommand = FindCommand(tokens[0].c_str());
if (!pCommand)
{
m_out->error("Unknown command. Type {}help for help.", kCommandPrefix);
return ExecutionResult::kFailure;
}
// Must subtract one, since the first is the literal command.
tokenCount -= 1;
if (tokenCount != pCommand->m_argCount)
{
m_out->error("Expected {} arguments but got {}", pCommand->m_argCount, tokenCount);
return ExecutionResult::kFailure;
}
ArgStack stack(pCommand->m_argCount);
auto result = CreateArgStack(pCommand, &tokens[1], stack);
if (!result.val)
{
m_out->error(result.msg);
return ExecutionResult::kFailure;
}
stack.ResetCounter();
m_queue.Upload(pCommand, stack);
StoreCommandInHistory(acLine);
if (m_requestFlush)
{
m_requestFlush = false;
return ExecutionResult::kDirty;
}
return ExecutionResult::kSuccess;
}
void ConsoleRegistry::StoreCommandInHistory(const TiltedPhoques::String& acLine)
{
m_commandHistory.push_back(acLine);
// Do some housekeeping.
if (m_commandHistory.size() == 10)
{
m_commandHistory.pop_back();
}
}
ResultAnd<bool> ConsoleRegistry::CreateArgStack(const CommandBase* apCommand, const TiltedPhoques::String* acStringArgs, ArgStack& aStackOut)
{
CommandBase::Type* pType = apCommand->m_pArgIndicesArray;
for (size_t i = 0; i < apCommand->m_argCount; i++)
{
auto& stringArg = acStringArgs[i];
switch (*pType)
{
case CommandBase::Type::kBoolean:
{
auto result = BoolifyString(stringArg);
if (result.has_value())
{
aStackOut.Push(*result /*to boolean conv*/);
continue;
}
return ResultAnd("Expected boolean argument", false);
}
case CommandBase::Type::kNumeric:
{
if (!IsNumber(stringArg))
{
return ResultAnd("Expected argument of type numeric", false);
}
int64_t value = false;
auto result = std::from_chars(stringArg.data(), stringArg.data() + stringArg.length(), value);
if (result.ec != std::errc())
{
return ResultAnd("Malformed numeric argument", false);
}
aStackOut.Push(value);
break;
}
case CommandBase::Type::kString: aStackOut.Push(stringArg); break;
default: return ResultAnd("Couldn't handle value type", false);
}
pType++;
}
return ResultAnd(nullptr, true);
}
bool ConsoleRegistry::Update()
{
// Drain the queue one by one
if (m_queue.HasWork())
{
CommandQueue::Item item;
m_queue.Fetch(item);
item.m_pCommand->m_Handler(item.m_stack);
return true;
}
return false;
}
} // namespace Console