Toast Engine
Loading...
Searching...
No Matches
settings.hpp
Go to the documentation of this file.
1
6
7#pragma once
8#include <array>
9#include <cstdint>
10#include <deque>
11#include <filesystem>
12#include <functional>
13#include <mutex>
14#include <optional>
15#include <string>
16#include <string_view>
17#include <toast/export.hpp>
18#include <unordered_map>
19#include <variant>
20#include <vector>
21
22namespace toast::settings {
23
25enum class Type : uint8_t {
26 boolean,
27 integer,
28 floating,
29 string
30};
31
41enum class Layer : uint8_t {
42 code,
43 project,
44 user
45};
46
47inline constexpr size_t k_layer_count = 2;
48
49using Value = std::variant<bool, int64_t, double, std::string>;
50
58struct Meta {
59 std::string label;
60 std::string category;
61 std::string description;
62 double min = 0.0;
63 double max = 0.0;
64 double step = 0.0;
65 std::vector<std::string> options;
66 bool requires_restart = false;
67 bool hidden = false;
68};
69
71struct Entry {
72 std::string key;
73 Type type = Type::boolean;
74 Meta meta;
75 Value default_value;
76 std::array<std::optional<Value>, k_layer_count> overrides;
77 Value resolved;
78 std::vector<std::function<void(const Value&)>> on_change;
79};
80
93class TOAST_API Settings {
94public:
95 static auto get() -> Settings&;
96
97 auto declareBool(std::string_view key, bool default_value, Meta meta = {}) -> Entry*;
98 auto declareInt(std::string_view key, int64_t default_value, Meta meta = {}) -> Entry*;
99 auto declareFloat(std::string_view key, double default_value, Meta meta = {}) -> Entry*;
100 auto declareString(std::string_view key, std::string default_value, Meta meta = {}) -> Entry*;
101
103 [[nodiscard]]
104 auto find(std::string_view key) -> Entry*;
105
110 [[nodiscard]]
111 auto entries() -> std::vector<Entry*>;
112
114 [[nodiscard]]
115 auto value(std::string_view key) -> std::optional<Value>;
116
118 [[nodiscard]]
119 auto resolved(const Entry* entry) -> Value;
120
125 auto setValue(std::string_view key, const Value& value) -> bool;
126
128 auto reset(std::string_view key) -> bool;
129
131 void resetAll();
132
134 void setPaths(std::filesystem::path project_file, std::filesystem::path user_file);
135
140 void setActiveLayer(Layer layer);
141
142 [[nodiscard]]
143 auto activeLayer() const -> Layer {
144 return m_active_layer;
145 }
146
148 [[nodiscard]]
149 auto activeFilePath() const -> const std::filesystem::path& {
150 return m_active_layer == Layer::project ? m_project_path : m_user_path;
151 }
152
157 void load();
158
164 auto save() -> bool;
165
166 [[nodiscard]]
167 auto dirty() const -> bool {
168 return m_dirty;
169 }
170
172 auto saveIfDirty() -> bool;
173
174private:
175 auto declare(std::string_view key, Type type, Value default_value, Meta meta) -> Entry*;
176 void resolveEntry(Entry& entry);
177 auto loadFile(const std::filesystem::path& path, Layer layer) -> bool;
178
180 struct TransparentHash {
181 using is_transparent = void;
182
183 auto operator()(std::string_view key) const -> size_t { return std::hash<std::string_view> {}(key); }
184 };
185
186 std::recursive_mutex m_mutex;
187 std::deque<Entry> m_entries;
188 std::unordered_map<std::string, Entry*, TransparentHash, std::equal_to<>> m_by_key;
189
195 std::array<std::unordered_map<std::string, Value>, k_layer_count> m_pending;
196
197 std::filesystem::path m_project_path;
198 std::filesystem::path m_user_path;
199 Layer m_active_layer = Layer::user;
200 bool m_dirty = false;
201};
202
209template<typename T>
210class Setting {
211public:
212 Setting() = default;
213
214 explicit Setting(Entry* entry) : m_entry(entry) { }
215
216 [[nodiscard]]
217 auto valid() const -> bool {
218 return m_entry != nullptr;
219 }
220
221 [[nodiscard]]
222 auto value() const -> T {
223 if (m_entry == nullptr) {
224 return T {};
225 }
226 // Reads the entry directly. Going back through value(key) hashed the key and built a std::string to
227 // look up the entry this handle is already holding
228 const Value resolved = Settings::get().resolved(m_entry);
229 if (const auto* typed = std::get_if<T>(&resolved)) {
230 return *typed;
231 }
232 return T {};
233 }
234
235 operator T() const { // NOLINT(google-explicit-constructor) - the whole point is reading it like a T
236 return value();
237 }
238
239 void set(T v) {
240 if (m_entry != nullptr) {
241 Settings::get().setValue(m_entry->key, Value {std::move(v)});
242 }
243 }
244
249 void onChange(std::function<void(T)> callback) {
250 if (m_entry == nullptr) {
251 return;
252 }
253 auto wrapper = [callback = std::move(callback)](const Value& v) {
254 if (const auto* typed = std::get_if<T>(&v)) {
255 callback(*typed);
256 }
257 };
258 wrapper(Value {value()});
259 m_entry->on_change.emplace_back(std::move(wrapper));
260 }
261
262 [[nodiscard]]
263 auto entry() const -> Entry* {
264 return m_entry;
265 }
266
267private:
268 Entry* m_entry = nullptr;
269};
270
272inline auto declareBool(std::string_view key, bool default_value, Meta meta = {}) -> Setting<bool> {
273 return Setting<bool> {Settings::get().declareBool(key, default_value, std::move(meta))};
274}
275
276inline auto declareInt(std::string_view key, int64_t default_value, Meta meta = {}) -> Setting<int64_t> {
277 return Setting<int64_t> {Settings::get().declareInt(key, default_value, std::move(meta))};
278}
279
280inline auto declareFloat(std::string_view key, double default_value, Meta meta = {}) -> Setting<double> {
281 return Setting<double> {Settings::get().declareFloat(key, default_value, std::move(meta))};
282}
283
284inline auto declareString(std::string_view key, std::string default_value, Meta meta = {}) -> Setting<std::string> {
285 return Setting<std::string> {Settings::get().declareString(key, std::move(default_value), std::move(meta))};
286}
287
288}
Typed handle to a declared setting.
Definition settings.hpp:210
void onChange(std::function< void(T)> callback)
Runs callback whenever this setting changes, and once immediately with the current value.
Definition settings.hpp:249
The engine-wide settings registry.
Definition settings.hpp:93
void setPaths(std::filesystem::path project_file, std::filesystem::path user_file)
Points the two writable layers at their files; neither has to exist yet.
Definition settings.cpp:340
auto reset(std::string_view key) -> bool
Drops key's override in the active layer, exposing whatever is underneath.
Definition settings.cpp:294
auto find(std::string_view key) -> Entry *
Definition settings.cpp:221
auto entries() -> std::vector< Entry * >
Every declared setting, in declaration order.
Definition settings.cpp:227
void setActiveLayer(Layer layer)
Which layer setValue() writes to.
Definition settings.cpp:346
auto resolved(const Entry *entry) -> Value
Definition settings.cpp:246
auto activeFilePath() const -> const std::filesystem::path &
Definition settings.hpp:149
auto setValue(std::string_view key, const Value &value) -> bool
Writes value into the active layer and fires the setting's change callbacks.
Definition settings.cpp:254
void resetAll()
reset() for every declared setting
Definition settings.cpp:326
auto value(std::string_view key) -> std::optional< Value >
Definition settings.cpp:237
Type
Storage type of a setting; the editor picks its widget from this.
Definition settings.hpp:25
auto declareBool(std::string_view key, bool default_value, Meta meta={}) -> Setting< bool >
declare* wrappers returning a typed handle, which is what call sites actually want
Definition settings.hpp:272
Layer
Where a value comes from, lowest priority first.
Definition settings.hpp:41
constexpr size_t k_layer_count
Writable layers; code lives in Entry::default_value.
Definition settings.hpp:47
Everything the editor needs to render a setting without knowing what it is.
Definition settings.hpp:58
One registered setting. Held by the registry; Setting<T> is a handle to it.
Definition settings.hpp:71
std::array< std::optional< Value >, k_layer_count > overrides
Indexed by Layer minus one.
Definition settings.hpp:76
Everything the editor needs to render a setting without knowing what it is.
Definition settings.hpp:58
std::vector< std::string > options
Non-empty makes an integer setting a combo box indexing this.
Definition settings.hpp:65
double step
Drag increment; the editor picks one when 0.
Definition settings.hpp:64
std::string label
Display name; the key's last segment when empty.
Definition settings.hpp:59
std::string category
Grouping header; the key minus its last segment when empty.
Definition settings.hpp:60
bool hidden
Persisted and scriptable, but not shown.
Definition settings.hpp:67
bool requires_restart
Editor marks it; the value still applies to the next run.
Definition settings.hpp:66
std::string description
Tooltip.
Definition settings.hpp:61
double min
Slider bounds; equal min and max mean unbounded.
Definition settings.hpp:62