PixelBullet  0.0.1
A C++ game engine
Loading...
Searching...
No Matches
node.h
1#pragma once
2
4#include "pixelbullet/math/validation.h"
5
6#include <algorithm>
7#include <charconv>
8#include <cstddef>
9#include <cstdint>
10#include <iosfwd>
11#include <map>
12#include <string>
13#include <string_view>
14#include <system_error>
15#include <type_traits>
16#include <utility>
17#include <vector>
18
19namespace YAML
20{
21class Node;
22}
23
24namespace pixelbullet
25{
27enum class NodeType : uint8_t
28{
29 Null,
30 Object,
31 Array,
32 String,
33 Boolean,
34 Integer,
35 Decimal
36};
37
38using NodeValue = std::string;
39using NodeProperty = std::pair<std::string, class Node>;
40using NodeProperties = std::vector<NodeProperty>;
41
48class Node
49{
50public:
54 struct Format
55 {
57 char new_line;
58 char space;
60
61 constexpr Format(int s, char nl, char spc, bool inline_arr)
63 , new_line(nl)
64 , space(spc)
65 , inline_arrays(inline_arr)
66 {
67 }
68
69 std::string indentation(const int indent_level) const
70 {
71 ASSERT(indent_level >= 0, "Negative indentation level");
72 ASSERT(spaces_per_indent >= 0, "Negative spaces per indent");
73 return std::string(static_cast<std::size_t>(spaces_per_indent * indent_level), ' ');
74 }
75
76 static const Format authoring;
77 static const Format beautified;
78 static const Format minified;
79 };
80
81 Node() = default;
82 Node(const Node&) = default;
83 Node(Node&&) noexcept = default;
84 Node& operator=(const Node&) = default;
85 Node& operator=(Node&&) noexcept = default;
86 ~Node() = default;
87
88 [[nodiscard]] const NodeValue& value() const noexcept;
89 void set_value(const NodeValue& value);
90 void set_value(NodeValue&& value);
91
92 [[nodiscard]] NodeType type() const noexcept;
93 void set_type(NodeType type);
94
95 void clear();
96
97 [[nodiscard]] bool is_valid() const noexcept;
98
99 [[nodiscard]] const NodeProperties& properties() const noexcept;
100 NodeProperties& properties();
101
102 [[nodiscard]] bool has_property(const std::string& name) const;
103 [[nodiscard]] const Node* get_property(const std::string& name) const;
104 void set_property(const std::string& name, const Node& node);
105 bool remove_property(const std::string& name);
106
107 // For object-style indexing
108 Node& operator[](const std::string& key);
109 const Node& operator[](const std::string& key) const;
110
111 // For array-style indexing
112 Node& operator[](std::size_t index);
113 const Node& operator[](std::size_t index) const;
114
115 // Convenience functions for type conversion.
116 template <typename T>
117 T as() const
118 {
119 T out{};
120 (*this) >> out;
121 return out;
122 }
123
124 template <typename T>
125 void set(const T& in)
126 {
127 (*this) << in;
128 }
129
130 // Iterators to properties
131 auto begin()
132 {
133 return properties_.begin();
134 }
135 auto end()
136 {
137 return properties_.end();
138 }
139 auto begin() const
140 {
141 return properties_.begin();
142 }
143 auto end() const
144 {
145 return properties_.end();
146 }
147
148 // Optional: returns the number of properties (useful for arrays/objects)
149 [[nodiscard]] std::size_t size() const noexcept
150 {
151 return properties_.size();
152 }
153
155 friend const Node& operator>>(const Node& node, Node& out)
156 {
157 out = node;
158 return node;
159 }
160
162 friend Node& operator<<(Node& node, const Node& in)
163 {
164 node = in;
165 return node;
166 }
167
168 friend const Node& operator>>(const Node& node, bool& b)
169 {
170 if (node.value_ == "true" || node.value_ == "1")
171 {
172 b = true;
173 }
174 else if (node.value_ == "false" || node.value_ == "0")
175 {
176 b = false;
177 }
178 else
179 {
180 ASSERT(false, "Invalid boolean value: {}", node.value_);
181 b = false;
182 }
183 return node;
184 }
185
186 friend Node& operator<<(Node& node, bool b)
187 {
188 node.set_value(b ? "true" : "false");
189 node.set_type(NodeType::Boolean);
190 return node;
191 }
192
193 static bool parse_yaml(Node& node, std::string_view str);
194 static bool write_yaml(const Node& node, std::ostream& stream, const Format& format = Format::minified);
195
196private:
197 void EnsureObjectStateForMutation();
198
199 NodeProperties properties_;
200 NodeValue value_;
201 NodeType type_ = NodeType::Null;
202};
203
204// --- Free Conversion Operators ---
205
206// Arithmetic conversion using C++20 requires clause.
207template <typename T>
208 requires std::is_arithmetic_v<T>
209Node& operator<<(Node& node, const T& value)
210{
211 char buffer[64]{};
212 if constexpr (std::is_floating_point_v<T>)
213 {
214 ASSERT(pixelbullet::math::is_finite(static_cast<long double>(value)), "Cannot serialize non-finite floating-point value");
215 }
216
217 const auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), value);
218 ASSERT(ec == std::errc(), "Conversion to string failed");
219 node.set_value(std::string(buffer, ptr));
220 if constexpr (std::is_integral_v<T>)
221 {
222 node.set_type(NodeType::Integer);
223 }
224 else if constexpr (std::is_floating_point_v<T>)
225 {
226 node.set_type(NodeType::Decimal);
227 }
228 return node;
229}
230
231template <typename T>
232 requires std::is_arithmetic_v<T>
233void operator>>(const Node& node, T& value)
234{
235 const std::string& s = node.value();
236 const char* begin = s.data();
237 const char* end = s.data() + s.size();
238 const auto result = std::from_chars(begin, end, value);
239 ASSERT(result.ec == std::errc() && result.ptr == end, "Conversion failed for value: {}", s);
240}
241
242inline Node& operator<<(Node& node, const std::string& value)
243{
244 node.set_value(value);
245 node.set_type(NodeType::String);
246 return node;
247}
248
249inline Node& operator<<(Node& node, const char* value)
250{
251 ASSERT(value != nullptr, "Cannot serialize null C string");
252 node.set_value(value);
253 node.set_type(NodeType::String);
254 return node;
255}
256
257inline const Node& operator>>(const Node& node, std::string& value)
258{
259 value = node.value();
260 return node;
261}
262
263template <typename T>
264Node& operator<<(Node& node, const std::vector<T>& vec)
265{
266 node.set_type(NodeType::Array);
267 auto& props = node.properties();
268 props.clear();
269 for (const T& item : vec)
270 {
271 Node child;
272 child << item;
273 props.emplace_back("", child);
274 }
275 return node;
276}
277
278template <typename T>
279void operator>>(const Node& node, std::vector<T>& vec)
280{
281 ASSERT(node.type() == NodeType::Array, "Node is not an array");
282 vec.clear();
283 for (const auto& [key, child] : node.properties())
284 {
285 T item;
286 child >> item;
287 vec.push_back(std::move(item));
288 }
289}
290
291template <typename T>
292Node& operator<<(Node& node, const std::map<std::string, T>& mapData)
293{
294 node.set_type(NodeType::Object);
295 auto& props = node.properties();
296 props.clear();
297 for (const auto& [key, value] : mapData)
298 {
299 Node child;
300 child << value;
301 props.emplace_back(key, child);
302 }
303 return node;
304}
305
306template <typename T>
307void operator>>(const Node& node, std::map<std::string, T>& mapData)
308{
309 ASSERT(node.type() == NodeType::Object, "Node is not an object");
310 mapData.clear();
311 for (const auto& [key, child] : node.properties())
312 {
313 T item;
314 child >> item;
315 mapData.emplace(key, std::move(item));
316 }
317}
318
319inline bool operator==(const Node& lhs, const Node& rhs)
320{
321 return lhs.type() == rhs.type() && lhs.value() == rhs.value() && lhs.properties() == rhs.properties();
322}
323
324inline bool operator!=(const Node& lhs, const Node& rhs)
325{
326 return !(lhs == rhs);
327}
328
329inline bool operator<(const Node& lhs, const Node& rhs)
330{
331 if (lhs.type() != rhs.type())
332 {
333 return lhs.type() < rhs.type();
334 }
335 if (lhs.value() != rhs.value())
336 {
337 return lhs.value() < rhs.value();
338 }
339 return lhs.properties() < rhs.properties();
340}
341
342// Enum conversions – now enabled only for enums.
343template <typename T>
344 requires std::is_enum_v<T>
345const Node& operator>>(const Node& node, T& object)
346{
347 using Underlying = std::underlying_type_t<T>;
348 Underlying value{};
349 node >> value;
350 object = static_cast<T>(value);
351 return node;
352}
353
354template <typename T>
355 requires std::is_enum_v<T>
356Node& operator<<(Node& node, T object)
357{
358 return node << static_cast<std::underlying_type_t<T>>(object);
359}
360
361inline const Node& operator>>(const Node& node, std::pair<std::string, std::string>& p)
362{
363 ASSERT(node.type() == NodeType::Object, "Expected object type for a pair");
364 const auto& props = node.properties();
365 ASSERT(props.size() == 1, "Expected exactly one property for a pair");
366 p.first = props[0].first;
367 props[0].second >> p.second;
368 return node;
369}
370
371inline Node& operator<<(Node& node, const std::pair<std::string, std::string>& p)
372{
373 node.set_type(NodeType::Object);
374 auto& props = node.properties();
375 props.clear();
376 Node valueNode;
377 valueNode << p.second;
378 props.emplace_back(p.first, valueNode);
379 return node;
380}
381} // namespace pixelbullet
Provides assertion and panic mechanisms with optional custom formatting.
#define ASSERT(condition,...)
Asserts that a condition is true.
Definition assert.h:142
Represents a hierarchical node capable of storing various data types and supporting YAML serializatio...
Definition node.h:49
friend Node & operator<<(Node &node, const Node &in)
Copy content from one node to another.
Definition node.h:162
friend const Node & operator>>(const Node &node, Node &out)
Copy the node's content.
Definition node.h:155
Formatting options for YAML serialization.
Definition node.h:55
char space
Space character.
Definition node.h:58
char new_line
New line character.
Definition node.h:57
static const Format minified
Predefined minified format.
Definition node.h:78
static const Format beautified
Predefined beautified format.
Definition node.h:77
bool inline_arrays
Flag to inline arrays.
Definition node.h:59
static const Format authoring
Predefined canonical format for authored YAML assets.
Definition node.h:76
int spaces_per_indent
Number of spaces per indent level.
Definition node.h:56