Skip to content

Commit

Permalink
feat: Console commands POC
Browse files Browse the repository at this point in the history
  • Loading branch information
Force67 committed Jan 14, 2022
1 parent 48b75d8 commit 0e8d9e8
Show file tree
Hide file tree
Showing 10 changed files with 10,037 additions and 47 deletions.
106 changes: 106 additions & 0 deletions Code/base/Command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (C) 2022 TiltedPhoques SRL.
// For licensing information see LICENSE at the root of this distribution.
#pragma once

#include <cstdint>
#include <functional>
#include <any>
#include <charconv>
#include <vector>

namespace base
{
// CommandNode
// CommandData

using ArgStack = std::vector<std::any>;

class CommandBase
{
friend class CommandRegistry;

public:
enum class Type
{
kBoolean,
kNumeric,
kPointer,
kVoid

// I think we should introduce signed_numeric
// and unsigned numeric
};

CommandBase(CommandBase*& parent, const char* n, const char* d, size_t argc)
: m_name(n), m_desc(d), m_argCount(argc), next(parent)
{
parent = this;
}

explicit CommandBase(const char* n, const char* d, size_t argc) : CommandBase(ROOT(), n, d, argc)
{
}

//static constexpr bool IsIntegral = std::is_integral_v<bool>;

protected:
template <typename T> static constexpr Type ToValueTypeIndex()
{
if constexpr (std::is_same_v<T, bool>)
return Type::kBoolean;
if constexpr (std::is_integral_v<T>)
return Type::kNumeric;
if constexpr (std::is_pointer_v<T>)
static_assert(true, "What are you doing?");
return Type::kVoid;
}

private:
static inline CommandBase*& ROOT() noexcept
{
static CommandBase* root{nullptr};
return root;
}

void Invoke()
{
}

public:
const char* m_name;
const char* m_desc;

// Type info
const size_t m_argCount;
Type* m_pArgIndicesArray = nullptr;
std::function<void(const ArgStack&)> m_Handler;

// Next static node.
CommandBase* next = nullptr;
};

template <typename... Ts> class Command : public CommandBase
{
static constexpr size_t N = sizeof...(Ts);

public:
// Construct the command and fill the indices list at runtime.
// and no, it couldnd be done with std::function
template<typename TFunctor>
explicit Command(const char* acName, const char* acDesc, const TFunctor& functor)
: m_indices{(ToValueTypeIndex<Ts>())...},
CommandBase(acName, acDesc, N)
{
CommandBase::m_pArgIndicesArray = reinterpret_cast<Type*>(&m_indices);
CommandBase::m_Handler = functor;
}

constexpr size_t arg_count()
{
return N;
}

private:
Type m_indices[N];
};
} // namespace base
132 changes: 132 additions & 0 deletions Code/base/CommandRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright (C) 2022 TiltedPhoques SRL.
// For licensing information see LICENSE at the root of this distribution.
#pragma once

#include <base/Command.h>

namespace base
{
namespace detail
{
// SEE
// https://stackoverflow.com/questions/29169153/how-do-i-verify-a-string-is-valid-double-even-if-it-has-a-point-in-it
bool IsNumber(const std::string& s)
{
return !s.empty() &&
std::find_if(s.begin(), s.end(), [](char c) { return !(std::isdigit(c) || c == '.'); }) == s.end();
}
} // namespace detail

// Never make this class static
class CommandRegistry
{
public:
enum class Status
{
kSuccess,
kNotFound,
kMissingArgs
};

CommandRegistry()
{
// Register global commands.
for (auto* i = CommandBase::ROOT(); i;)
{
m_commands.push_back(i);
i = i->next;
}
}

template <typename... Ts> void RegisterCommand(const char* name, const char* desc, std::function<void()> func)
{
auto pCommand = new Command<Ts>(name, desc, func);
m_commands.push_back(pCommand);
}

CommandBase* FindCommand(const char* acName)
{
// TODO: Maybe lookup by some hash...
auto it = std::find_if(m_commands.begin(), m_commands.end(),
[&](CommandBase* apCommand) { return std::strcmp(apCommand->m_name, acName) == 0; });
if (it == m_commands.end())
return nullptr;
return *it;
}

Status TryExecuteCommand(const char* acName, const std::vector<std::string>& acArgs)
{
auto* pCommand = FindCommand(acName);
if (!pCommand)
return Status::kNotFound;

// Validate arg counts against each other.
if (acArgs.size() != pCommand->m_argCount)
{
// Invalid

}

ArgStack stack;
CommandBase::Type* pType = pCommand->m_pArgIndicesArray;
for (size_t i = 0; i < pCommand->m_argCount; i++)
{
auto& stringArg = acArgs[i];
switch (*pType)
{
case CommandBase::Type::kBoolean: {
if (stringArg == "true" || stringArg == "TRUE")
{
stack.push_back(true);
continue;
}
else if (stringArg == "false" || stringArg == "FALSE")
{
stack.push_back(false);
continue;
}

// Expected x got this
return Status::kMissingArgs;
}
case CommandBase::Type::kNumeric: {
if (!detail::IsNumber(stringArg))
{
// Expected argument of type numeric, got this

__debugbreak();
}

int64_t value = false;
auto result = std::from_chars(stringArg.data(), stringArg.data() + stringArg.length(), value);
if (result.ec != std::errc())
{
__debugbreak();

// Expected number, got this, number malformed
return Status::kMissingArgs;
}

stack.push_back(value);
break;
}
case CommandBase::Type::kVoid:
case CommandBase::Type::kPointer:
__debugbreak();
// Ignore.
break;
}
pType++;
}

pCommand->m_Handler(stack);
// Need to queue execution onto the right thread...
return Status::kSuccess;
}

private:
std::vector<CommandBase*> m_commands;

// MPSC queue
};
} // namespace base
45 changes: 27 additions & 18 deletions Code/base/Setting.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,25 @@ struct SettingBase
kString
};

enum class Flags : uint16_t
enum Flags : uint16_t
{
kNone,
// Doesn't show up in the help list.
kHidden = 1 << 0,
// Value is write protected
// TODO: honor the lock.
// Value is write protected, cannot be altered
// at runtime.
kLocked = 1 << 1,
// Does alter game-play
kCheat = 1 << 2,
};

SettingBase(SettingBase*& parent, const char* n, const char* d, Type t) : next(parent), name(n), desc(d), type(t)
SettingBase(SettingBase*& parent, const char* n, const char* d, Type t, Flags f)
: next(parent), name(n), desc(d), type(t)
{
parent = this;
}

explicit SettingBase(const char* n, const char* d, Type t) : SettingBase(ROOT(), n, d, t)
explicit SettingBase(const char* n, const char* d, Type t, Flags f) : SettingBase(ROOT(), n, d, t, f)
{
}

Expand Down Expand Up @@ -78,20 +81,20 @@ struct SettingBase

const char* c_str() const
{
//BASE_ASSERT(type == Type::kString, "Must be a string");
BASE_ASSERT(type == Type::kString, "Must be a string");
return data.as_string;
}

// type info
Flags flags{Flags::kNone};
Type type{Type::kNone};
Flags flags;
Type type;

// descriptor
const char* name{nullptr};
const char* desc{nullptr};
const char* name;
const char* desc;

// Gets aligned to 8 bytes anyway
size_t dataLength;
size_t dataLength = 0;
union {
bool as_boolean;
int32_t as_int32;
Expand Down Expand Up @@ -143,26 +146,26 @@ template <typename T> struct DynamicStringStorage
private:
std::basic_string<T, std::char_traits<T>, StlAllocator<T>> m_data;
};
}
} // namespace detail

// Settings can have their own custom storage spaces.
// However, make sure to make the providers aware of this.
template <typename T, class TStorage = detail::FixedStorage<T>> class Setting : public SettingBase, public TStorage
{
public:
Setting(const char* acName, const char* acDesc, const T acDefault)
: SettingBase(acName, acDesc, ToTypeIndex<T>()), TStorage(*this, acDefault)
Setting(const char* acName, const char* acDesc, const T acDefault, const Flags acFlags = Flags::kNone)
: SettingBase(acName, acDesc, ToTypeIndex<T>(), acFlags), TStorage(*this, acDefault)
{
}

Setting() = delete;

T& value()
T value() const
{
return reinterpret_cast<T&>(data.as_uint64);
return reinterpret_cast<T>(data.as_uint64);
}

template <typename Tas> Tas value_as()
template <typename Tas> Tas value_as() const
{
return static_cast<Tas>(data.as_uint64);
}
Expand All @@ -180,10 +183,16 @@ template <typename T, class TStorage = detail::FixedStorage<T>> class Setting :

void operator=(const T value)
{
if (flags & Flags::kLocked)
{
BASE_ASSERT(false, "Tried to write to locked variable");
return;
}

TStorage::StoreValue(*this, value);
}
};

using StringSetting = Setting<const char*, detail::DynamicStringStorage<char>>;
// NOTE: Wide strings are not supported, since our ini cant handle them.
// NOTE: Wide strings are not supported, since our INI cant handle them.
} // namespace base
Loading

0 comments on commit 0e8d9e8

Please sign in to comment.