Chira Engine
A customizable MIT-licensed game engine.
ConEntry.h
1 #pragma once
2 
3 #include <exception>
4 #include <functional>
5 #include <string>
6 #include <string_view>
7 #include <type_traits>
8 #include <vector>
9 #include <core/Assertions.h>
10 #include <core/Logger.h>
11 #include <loader/settings/JSONSettingsLoader.h>
12 
13 CHIRA_GET_LOG(CONENTRY);
14 
15 namespace chira {
16 
17 enum ConFlags {
18  CON_FLAG_NONE = 0, // None
19  CON_FLAG_CHEAT = 1 << 0, // Cheat-protected
20  CON_FLAG_HIDDEN = 1 << 1, // Doesn't show up in search
21  CON_FLAG_CACHE = 1 << 2, // Value is saved at exit and loaded at start (useless for concommands)
22  CON_FLAG_READONLY = 1 << 3, // ConVar cannot be changed in the CONSOLE. Still modifiable in code (useless for concommands)
23  CON_FLAG_DEVONLY = 1 << 4, // Reserved for possible future use
24 };
25 
26 class ConEntry {
27 public:
28  ConEntry(std::string name_, std::string description_, int flags_ = CON_FLAG_NONE);
29  virtual ~ConEntry() = default;
30  [[nodiscard]] std::string_view getName() const;
31  [[nodiscard]] std::string_view getDescription() const;
32  [[nodiscard]] bool hasFlag(ConFlags flag) const;
33 protected:
34  std::string name;
35  std::string description;
36  int flags;
37 };
38 
39 class ConCommand : public ConEntry {
40 public:
41  using CallbackArgs = const std::vector<std::string>&;
42 
43  ConCommand(std::string name_, const std::function<void()>& callback_, int flags_ = CON_FLAG_NONE);
44  ConCommand(std::string name_, std::function<void(CallbackArgs)> callback_, int flags_ = CON_FLAG_NONE);
45  ConCommand(std::string name_, std::string description_, const std::function<void()>& callback_, int flags_ = CON_FLAG_NONE);
46  ConCommand(std::string name_, std::string description_, std::function<void(CallbackArgs)> callback_, int flags_ = CON_FLAG_NONE);
47  ~ConCommand() override;
48 
49  void fire(CallbackArgs args);
50  [[nodiscard]] explicit inline operator std::string() const {
51  return std::string{this->getName()} + " - " + this->getDescription().data();
52  }
53 private:
54  std::function<void(CallbackArgs)> callback;
55 };
56 
57 // Registry be declared before ConVar because of the magic of templates
58 class ConVar;
59 
61  friend ConCommand;
62  friend ConVar;
63 public:
64  ConEntryRegistry() = delete;
65 
66  [[nodiscard]] static bool hasConCommand(std::string_view name);
67  [[nodiscard]] static ConCommand* getConCommand(std::string_view name);
68  [[nodiscard]] static std::vector<std::string> getConCommandList();
69 
70  [[nodiscard]] static bool hasConVar(std::string_view name);
71  [[nodiscard]] static ConVar* getConVar(std::string_view name);
72  [[nodiscard]] static std::vector<std::string> getConVarList();
73 private:
74  static std::vector<ConCommand*>& getConCommands();
75  static bool registerConCommand(ConCommand* concommand);
76  static void deregisterConCommand(ConCommand* concommand);
77 
78  static std::vector<ConVar*>& getConVars();
79  static JSONSettingsLoader& getConVarCache();
80  static bool registerConVar(ConVar* convar);
81  static void deregisterConVar(ConVar* convar);
82 };
83 
84 // These are all the types that can currently be serialized into JSON
85 template<typename T>
86 concept ConVarValidType = std::same_as<bool, T> ||
87  std::same_as<int, T> ||
88  std::same_as<double, T> ||
89  std::same_as<std::string, T>;
90 
91 // Don't make the ConVar class a template :)
92 enum class ConVarType {
93  BOOLEAN,
94  INTEGER,
95  DOUBLE,
96  STRING,
97 };
98 
99 class ConVar : public ConEntry {
100 public:
101  using CallbackArg = std::string_view;
102 
103  ConVar(std::string name_, ConVarValidType auto defaultValue, int flags_ = CON_FLAG_NONE, std::function<void(CallbackArg)> onChanged = [](CallbackArg) {})
104  : ConEntry(std::move(name_), "No description provided.", flags_)
105  , changedCallback(std::move(onChanged)) {
106  if constexpr (std::is_same_v<decltype(defaultValue), std::string>) {
107  this->value = std::move(defaultValue);
108  this->type = ConVarType::STRING;
109  } else {
110  this->value = std::to_string(defaultValue);
111  if constexpr (std::is_same_v<decltype(defaultValue), bool>) {
112  this->type = ConVarType::BOOLEAN;
113  } else if constexpr (std::is_same_v<decltype(defaultValue), int>) {
114  this->type = ConVarType::INTEGER;
115  } else /* if constexpr (std::is_same_v<decltype(defaultValue), double>) */ {
116  this->type = ConVarType::DOUBLE;
117  }
118  }
119  runtime_assert(ConEntryRegistry::registerConVar(this), "This ConVar already exists!");
120  }
121 
122  ConVar(std::string name_, ConVarValidType auto defaultValue, std::string description_, int flags_ = CON_FLAG_NONE, std::function<void(CallbackArg)> onChanged = [](CallbackArg) {})
123  : ConEntry(std::move(name_), std::move(description_), flags_)
124  , changedCallback(std::move(onChanged)) {
125  if constexpr (std::is_same_v<decltype(defaultValue), std::string>) {
126  this->value = std::move(defaultValue);
127  this->type = ConVarType::STRING;
128  } else {
129  this->value = std::to_string(defaultValue);
130  if constexpr (std::is_same_v<decltype(defaultValue), bool>) {
131  this->type = ConVarType::BOOLEAN;
132  } else if constexpr (std::is_same_v<decltype(defaultValue), int>) {
133  this->type = ConVarType::INTEGER;
134  } else /* if constexpr (std::is_same_v<decltype(defaultValue), double>) */ {
135  this->type = ConVarType::DOUBLE;
136  }
137  }
138  runtime_assert(ConEntryRegistry::registerConVar(this), "This ConVar already exists!");
139  }
140 
141  ~ConVar() override;
142  [[nodiscard]] ConVarType getType() const;
143  [[nodiscard]] std::string_view getTypeAsString() const;
144 
145  template<ConVarValidType T>
146  [[nodiscard]] inline T getValue() const {
147  if constexpr (std::is_same_v<T, std::string>) {
148  return this->value;
149  } else {
150  if (this->type == ConVarType::STRING) {
151  return static_cast<T>(this->value.size());
152  } else if (this->type == ConVarType::DOUBLE) {
153  return static_cast<T>(std::stod(this->value));
154  } else {
155  return static_cast<T>(std::stoi(this->value));
156  }
157  }
158  }
159 
160  void setValue(ConVarValidType auto newValue, bool runCallback = true) {
161  if (this->hasFlag(CON_FLAG_CHEAT) && !ConVar::areCheatsEnabled()) {
162  LOG_CONENTRY.error("Cannot set value of cheat-protected ConVar with cheats disabled.");
163  return;
164  }
165 
166  if constexpr (std::is_same_v<decltype(newValue), std::string>) {
167  switch (this->type) {
168  using enum ConVarType;
169  case BOOLEAN:
170  if (newValue == "true") {
171  this->value = "1";
172  break;
173  } else if (newValue == "false") {
174  this->value = "0";
175  }
176  [[fallthrough]];
177  case INTEGER:
178  try {
179  this->value = std::to_string(std::stoi(newValue));
180  } catch (const std::invalid_argument&) {
181  this->value = std::to_string(newValue.size());
182  }
183  break;
184  case DOUBLE:
185  try {
186  this->value = std::to_string(std::stod(newValue));
187  } catch (const std::invalid_argument&) {
188  this->value = std::to_string(static_cast<double>(newValue.size()));
189  }
190  break;
191  case STRING:
192  this->value = newValue;
193  break;
194  }
195  } else {
196  switch (this->type) {
197  using enum ConVarType;
198  case BOOLEAN:
199  this->value = std::to_string(static_cast<bool>(newValue));
200  break;
201  case INTEGER:
202  this->value = std::to_string(static_cast<int>(newValue));
203  break;
204  case DOUBLE:
205  this->value = std::to_string(static_cast<double>(newValue));
206  break;
207  case STRING:
208  this->value = std::to_string(newValue);
209  break;
210  }
211  }
212 
213  if (runCallback) {
214  try {
215  this->changedCallback(this->value);
216  } catch (const std::exception& e) {
217  LOG_CONENTRY.error("Encountered error executing ConVar callback: {}", e.what());
218  }
219  }
220  }
221 
222  [[nodiscard]] explicit inline operator std::string() const {
223  return std::string{this->getName()} + ": " + this->getTypeAsString().data() + " - " + this->getDescription().data();
224  }
225 
227  [[nodiscard]] static bool areCheatsEnabled();
228 private:
229  std::function<void(CallbackArg)> changedCallback;
230  std::string value;
231  ConVarType type;
232 };
233 
235 public:
236  explicit ConCommandRef(std::string_view name) {
237  this->command = ConEntryRegistry::getConCommand(name);
238  if (!this->command) {
239  LOG_CONENTRY.error("ConCommandRef named \"{}\" refers to a nonexistent ConVar!", name);
240  }
241  }
242  [[nodiscard]] inline std::string_view getName() const { return this->command->getName(); }
243  [[nodiscard]] inline std::string_view getDescription() const { return this->command->getDescription(); }
244  [[nodiscard]] inline bool hasFlag(ConFlags flag) const { return this->command->hasFlag(flag); }
245  inline void fire(ConCommand::CallbackArgs args) { return this->command->fire(args); }
246  [[nodiscard]] explicit inline operator std::string() const { return this->command->operator std::string(); }
247 
248  [[nodiscard]] explicit inline operator bool() const { return this->command; }
249 private:
250  ConCommand* command;
251 };
252 
253 class ConVarRef {
254 public:
255  explicit ConVarRef(std::string_view name) {
256  this->var = ConEntryRegistry::getConVar(name);
257  if (!this->var) {
258  LOG_CONENTRY.error("ConVarRef named \"{}\" refers to a nonexistent ConVar!", name);
259  }
260  }
261  [[nodiscard]] inline std::string_view getName() const { return this->var->getName(); }
262  [[nodiscard]] inline std::string_view getDescription() const { return this->var->getDescription(); }
263  [[nodiscard]] inline bool hasFlag(ConFlags flag) const { return this->var->hasFlag(flag); }
264  [[nodiscard]] inline ConVarType getType() const { return this->var->getType(); }
265  [[nodiscard]] inline std::string_view getTypeAsString() const { return this->var->getTypeAsString(); }
266  template<ConVarValidType T> [[nodiscard]] inline T getValue() const { return this->var->getValue<T>(); }
267  inline void setValue(ConVarValidType auto value, bool runCallback = true) const { return this->var->setValue(value, runCallback); }
268  [[nodiscard]] explicit inline operator std::string() const { return this->var->operator std::string(); }
269 
270  [[nodiscard]] explicit inline operator bool() const { return this->var; }
271 private:
272  ConVar* var;
273 };
274 
275 } // namespace chira
Definition: ConEntry.h:26
Definition: ConEntry.h:60
static bool areCheatsEnabled()
Convenience function to check the value of sv_cheats.
Definition: ConEntry.cpp:282