I've written some code for console variables. They support different types e.g. integer, string, vector, float, etc.
Is there a better way to do this? Like with an interface and a class for each different type? (I'm not sure how I would do that.) I also have to create a register function for each type of variable: float
, int
, etc.
Is there a way to do it with a template? E.g.
Console.RegisterConVar<float>("float_test", 1.2, 3.2);
Any other criticism is also welcome.
Vector.h:
#pragma once
class Vector2
{
public:
Vector2() : fX(0.0f), fY(0.0f) {}
Vector2(float x, float y) : fX(x), fY(y) {}
float fX, fY;
};
class Vector3
{
public:
Vector3() : fX(0.0f), fY(0.0f), fZ(0.0f) {}
Vector3(float x, float y, float z) : fX(x), fY(y), fZ(z) {}
float fX, fY, fZ;
};
class Vector4
{
public:
Vector4() : fX(0.0f), fY(0.0f), fZ(0.0f), fA(0.0f) {}
Vector4(float x, float y, float z, float a) : fX(x), fY(y), fZ(z), fA(a) {}
float fX, fY, fZ, fA;
};
ConVar.h:
#pragma once
enum ConVarType
{
CONVAR_TYPE_BOOL = 0x0,
CONVAR_TYPE_FLOAT = 0x1,
CONVAR_TYPE_VEC2 = 0x2,
CONVAR_TYPE_VEC3 = 0x3,
CONVAR_TYPE_VEC4 = 0x4,
CONVAR_TYPE_INT = 0x5,
CONVAR_TYPE_ENUM = 0x6,
CONVAR_TYPE_STRING = 0x7,
CONVAR_TYPE_INT64 = 0x8
};
enum ConVarValueType
{
CONVARVALUE_TYPE_CURRENT = 0x0,
CONVARVALUE_TYPE_DEFAULT = 0x1
};
union ConVarLimits
{
template<typename T>
struct Limits
{
T min;
T max;
};
Limits<int> integer;
Limits<__int64> integer64;
Limits<float> floating;
std::vector<std::string>* enumList;
};
union ConVarValue
{
ConVarValue() {}
~ConVarValue() {}
bool boolean;
int integer;
__int64 integer64;
float floating;
Vector2 Vec2;
Vector3 Vec3;
Vector4 Vec4;
const char* string;
};
class ConVar
{
public:
std::string m_Name;
ConVarType m_Type;
ConVarValue m_Current;
ConVarValue m_Default;
ConVarLimits m_Domain;
~ConVar()
{
if(m_Type == CONVAR_TYPE_STRING)
delete [] m_Current.string;
if(m_Type == CONVAR_TYPE_ENUM)
{
delete m_Domain.enumList;
}
}
bool GetValueString(ConVarValueType vType, char* buffer, size_t size)
{
ConVarValue* var = (vType == CONVARVALUE_TYPE_CURRENT) ? &this->m_Current : (vType == CONVARVALUE_TYPE_DEFAULT) ? &this->m_Default : nullptr;
if(var == nullptr)
return false;
switch(this->m_Type)
{
case CONVAR_TYPE_BOOL:
sprintf_s(buffer, size, "%s", (var->boolean) ? "1" : "0"); break;
case CONVAR_TYPE_FLOAT:
sprintf_s(buffer, size, "%g", var->floating); break;
case CONVAR_TYPE_VEC2:
sprintf_s(buffer, size, "%g %g", var->Vec2.fX, var->Vec2.fY); break;
case CONVAR_TYPE_VEC3:
sprintf_s(buffer, size, "%g %g %g", var->Vec3.fX, var->Vec3.fY, var->Vec3.fZ); break;
case CONVAR_TYPE_VEC4:
sprintf_s(buffer, size, "%g %g %g %g", var->Vec4.fX, var->Vec4.fY, var->Vec4.fZ, var->Vec4.fA); break;
case CONVAR_TYPE_INT:
sprintf_s(buffer, size, "%i", var->integer); break;
case CONVAR_TYPE_INT64:
sprintf_s(buffer, size, "%lli", var->integer64); break;
case CONVAR_TYPE_ENUM:
if(var->integer < 0 || var->integer >= static_cast<int>(m_Domain.enumList->size()))
{
sprintf_s(buffer, size, "");
break;
}
else
{
const char* enumVal = m_Domain.enumList->at(var->integer).c_str();
sprintf_s(buffer, size, "%s", (enumVal) ? enumVal : "" );
break;
}
case CONVAR_TYPE_STRING:
sprintf_s(buffer, size, "%s", (var->string) ? var->string : ""); break;
default:
sprintf_s(buffer, size, ""); break;
}
return true;
}
};
IConsole.h:
#pragma once
class IConsole
{
public:
~IConsole()
{
for(auto& cvar : m_VarPool)
delete cvar;
}
ConVar* RegisterNew(const std::string& name, ConVarType type, ConVarValue* value, ConVarLimits* domain)
{
if(FindConVar(name) == nullptr)
{
ConVar* Cvar = new ConVar();
Cvar->m_Name = std::move(name);
Cvar->m_Type = type;
memcpy(&Cvar->m_Current, value, sizeof(ConVarValue));
memcpy(&Cvar->m_Default, value, sizeof(ConVarValue));
memcpy(&Cvar->m_Domain, domain, sizeof(ConVarLimits));
m_VarPool.push_back(Cvar);
m_VarMap[name] = Cvar;
return Cvar;
}
return nullptr;
}
ConVar* RegisterInt(const std::string& name, int default_value, int min, int max)
{
ConVarValue value;
ConVarLimits domain;
value.integer = default_value;
domain.integer.min = min;
domain.integer.max = max;
return RegisterNew(name, CONVAR_TYPE_INT, &value, &domain);
}
ConVar* RegisterFloat(const std::string& name, float default_value, float min, float max)
{
ConVarValue value;
ConVarLimits domain;
value.floating = default_value;
domain.floating.min = min;
domain.floating.max = max;
return RegisterNew(name, CONVAR_TYPE_FLOAT, &value, &domain);
}
ConVar* RegisterString(const std::string& name, const char* string)
{
ConVarValue value;
ConVarLimits domain;
size_t len = strlen(string);
char* newStr = new char[len+1];
memcpy(newStr, string, len+1);
newStr[len] = 0;
value.string = newStr;
return RegisterNew(name, CONVAR_TYPE_STRING, &value, &domain);
}
ConVar* RegisterEnum(const std::string& name, std::initializer_list<const char*> enumList, int defaultIndex)
{
ConVarValue value;
ConVarLimits domain;
value.integer = (defaultIndex > 0) ? defaultIndex : 0;
domain.enumList = new std::vector<std::string>;
for(auto& mEnum : enumList)
domain.enumList->emplace_back(mEnum);
return RegisterNew(name, CONVAR_TYPE_ENUM, &value, &domain);
}
ConVar* FindConVar(const std::string& name)
{
auto it = m_VarMap.find(name);
return (it != m_VarMap.end()) ? (*it).second : nullptr;
}
private:
std::vector<ConVar*> m_VarPool;
std::unordered_map<std::string, ConVar*> m_VarMap;
};
main.cpp:
#include <vector>
#include <unordered_map>
#include <iostream>
#include <string>
#include "Vector.h"
#include "ConVar.h"
#include "IConsole.h"
void main()
{
IConsole Console;
Console.RegisterInt("int_test", 76, 0, 100);
Console.RegisterFloat("float_test", 33.0f, 0.0f, 100.0f);
Console.RegisterEnum("enum_test", {"enum1", "enum2", "enum3", "enum4"}, 0);
Console.RegisterString("string_test", "hello, this is a test string");
while(true)
{
std::string name;
std::cout << "Please enter cvar name: ";
std::getline(std::cin, name);
ConVar* Cvar = Console.FindConVar(name);
if(Cvar != nullptr)
{
char buffer[256];
Cvar->GetValueString(CONVARVALUE_TYPE_CURRENT, buffer, sizeof(buffer));
std::cout << buffer << std::endl;
}
else
{
std::cout << "Invalid Cvar" << std::endl;
}
}
}