yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dock_tree.cc
Go to the documentation of this file.
2
3#include <atomic>
4#include <string>
5#include <unordered_set>
6#include <utility>
7#include <vector>
8
9namespace yaze {
10namespace editor {
11namespace layout_designer {
12
13namespace {
14
15// Process-wide monotonic counter for DockNodeId. Starts at 1 because 0 is
16// reserved as the kInvalidDockNodeId sentinel.
17std::atomic<DockNodeId> g_next_dock_node_id{1};
18
19} // namespace
20
21namespace internal {
22
24 return g_next_dock_node_id.fetch_add(1, std::memory_order_relaxed);
25}
26
28 if (id == kInvalidDockNodeId)
29 return;
30 // Bump the counter to id + 1 if we'd otherwise hand out a value <= id.
31 // Use compare-exchange-weak so concurrent observers race cleanly.
32 DockNodeId expected = g_next_dock_node_id.load(std::memory_order_relaxed);
33 while (expected <= id) {
34 if (g_next_dock_node_id.compare_exchange_weak(expected, id + 1,
35 std::memory_order_relaxed)) {
36 return;
37 }
38 // compare_exchange_weak refreshes `expected` on failure; loop and retry.
39 }
40}
41
42} // namespace internal
43
44std::unique_ptr<DockNode> DockNode::MakeLeaf(std::vector<PanelEntry> panels) {
45 auto node = std::make_unique<DockNode>();
47 node->type = Type::kLeaf;
48 node->panels = std::move(panels);
49 node->active_tab_index = 0;
50 return node;
51}
52
53std::unique_ptr<DockNode> DockNode::MakeSplit(SplitDirection dir, float ratio,
54 std::unique_ptr<DockNode> a,
55 std::unique_ptr<DockNode> b) {
56 auto node = std::make_unique<DockNode>();
58 node->type = Type::kSplit;
59 node->split_direction = dir;
60 node->split_ratio = ratio;
61 node->child_a = std::move(a);
62 node->child_b = std::move(b);
63 return node;
64}
65
66std::unique_ptr<DockNode> DockNode::Clone() const {
67 auto copy = std::make_unique<DockNode>();
68 // Preserve id verbatim — Clone is the undo/redo and snapshot path, and
69 // selection-by-id only survives those operations if the cloned tree's
70 // node ids match the originals'.
71 copy->id = id;
72 copy->type = type;
73 copy->panels = panels;
74 copy->active_tab_index = active_tab_index;
75 copy->split_direction = split_direction;
76 copy->split_ratio = split_ratio;
77 if (child_a)
78 copy->child_a = child_a->Clone();
79 if (child_b)
80 copy->child_b = child_b->Clone();
81 return copy;
82}
83
85 std::unique_ptr<DockNode> new_child,
86 bool new_child_first) {
87 auto saved_panels = std::move(panels);
88 const int saved_tab = active_tab_index;
89
91 split_direction = dir;
92 split_ratio = ratio;
93 panels.clear();
95
96 auto existing_leaf = MakeLeaf(std::move(saved_panels));
97 existing_leaf->active_tab_index = saved_tab;
98
99 if (new_child_first) {
100 child_a = std::move(new_child);
101 child_b = std::move(existing_leaf);
102 } else {
103 child_a = std::move(existing_leaf);
104 child_b = std::move(new_child);
105 }
106}
107
109 if (type != Type::kSplit)
110 return false;
111 if (child_a && child_b)
112 return false;
113 auto surviving = child_a ? std::move(child_a) : std::move(child_b);
114 if (!surviving)
115 return false;
116 *this = std::move(*surviving);
117 return true;
118}
119
120const PanelEntry* DockNode::FindPanel(const std::string& panel_id) const {
121 if (type == Type::kLeaf) {
122 for (const auto& p : panels) {
123 if (p.panel_id == panel_id)
124 return &p;
125 }
126 return nullptr;
127 }
128 if (child_a) {
129 if (const auto* hit = child_a->FindPanel(panel_id))
130 return hit;
131 }
132 if (child_b) {
133 if (const auto* hit = child_b->FindPanel(panel_id))
134 return hit;
135 }
136 return nullptr;
137}
138
139DockTree::DockTree() : root(DockNode::MakeLeaf({})) {}
140
141DockTree::DockTree(std::string n)
142 : name(std::move(n)), root(DockNode::MakeLeaf({})) {}
143
145 DockTree copy;
146 copy.name = name;
149 if (root)
150 copy.root = root->Clone();
151 return copy;
152}
153
154namespace {
155
156bool ValidateNode(const DockNode& node,
157 std::unordered_set<std::string>* seen_panel_ids,
158 std::unordered_set<DockNodeId>* seen_node_ids,
159 std::string* error) {
160 // Phase 8.5: every real DockNode must carry a unique non-zero id.
161 // FindNode does a linear DFS and returns the first match; without
162 // these checks a malformed file (or a future bug) could make
163 // selection/drag/property edits silently resolve to an arbitrary
164 // node.
165 if (node.id == kInvalidDockNodeId) {
166 if (error)
167 *error = "node has invalid id (kInvalidDockNodeId)";
168 return false;
169 }
170 if (!seen_node_ids->insert(node.id).second) {
171 if (error)
172 *error = "duplicate node id: " + std::to_string(node.id);
173 return false;
174 }
175
176 if (node.type == DockNode::Type::kLeaf) {
177 const int n = static_cast<int>(node.panels.size());
178 if (n == 0) {
179 if (node.active_tab_index != 0) {
180 if (error)
181 *error = "empty leaf has non-zero active_tab_index";
182 return false;
183 }
184 } else if (node.active_tab_index < 0 || node.active_tab_index >= n) {
185 if (error)
186 *error = "active_tab_index out of range";
187 return false;
188 }
189 for (const auto& p : node.panels) {
190 if (p.panel_id.empty()) {
191 if (error)
192 *error = "panel has empty panel_id";
193 return false;
194 }
195 auto inserted = seen_panel_ids->insert(p.panel_id);
196 if (!inserted.second) {
197 if (error)
198 *error = "panel id '" + p.panel_id + "' appears twice";
199 return false;
200 }
201 }
202 return true;
203 }
204
205 // kSplit
206 if (!node.child_a || !node.child_b) {
207 if (error)
208 *error = "split node has null child";
209 return false;
210 }
211 if (!(node.split_ratio >= 0.05f && node.split_ratio <= 0.95f)) {
212 if (error)
213 *error = "split_ratio out of range [0.05, 0.95]";
214 return false;
215 }
216 return ValidateNode(*node.child_a, seen_panel_ids, seen_node_ids, error) &&
217 ValidateNode(*node.child_b, seen_panel_ids, seen_node_ids, error);
218}
219
220} // namespace
221
222bool DockTree::Validate(std::string* error) const {
223 if (!root) {
224 if (error)
225 *error = "tree has null root";
226 return false;
227 }
228 std::unordered_set<std::string> seen_panel_ids;
229 std::unordered_set<DockNodeId> seen_node_ids;
230 return ValidateNode(*root, &seen_panel_ids, &seen_node_ids, error);
231}
232
233namespace {
234
236 if (node == nullptr)
237 return nullptr;
238 if (node->id == id)
239 return node;
240 if (node->type == DockNode::Type::kSplit) {
241 if (const auto* hit = FindNodeInSubtree(node->child_a.get(), id))
242 return hit;
243 if (const auto* hit = FindNodeInSubtree(node->child_b.get(), id))
244 return hit;
245 }
246 return nullptr;
247}
248
249} // namespace
250
252 if (id == kInvalidDockNodeId)
253 return nullptr;
254 return FindNodeInSubtree(root.get(), id);
255}
256
258 return const_cast<DockNode*>(
259 static_cast<const DockTree*>(this)->FindNode(id));
260}
261
262DockTree MakeEmptyTree(std::string name) {
263 return DockTree{std::move(name)};
264}
265
266} // namespace layout_designer
267} // namespace editor
268} // namespace yaze
bool ValidateNode(const DockNode &node, std::unordered_set< std::string > *seen_panel_ids, std::unordered_set< DockNodeId > *seen_node_ids, std::string *error)
Definition dock_tree.cc:156
const DockNode * FindNodeInSubtree(const DockNode *node, DockNodeId id)
Definition dock_tree.cc:235
DockTree MakeEmptyTree(std::string name)
Definition dock_tree.cc:262
constexpr DockNodeId kInvalidDockNodeId
Definition dock_tree.h:22
Represents a dock node in the layout tree.
Definition dock_tree.h:61
const PanelEntry * FindPanel(const std::string &panel_id) const
Definition dock_tree.cc:120
static std::unique_ptr< DockNode > MakeLeaf(std::vector< PanelEntry > panels)
Definition dock_tree.cc:44
std::unique_ptr< DockNode > child_a
Definition dock_tree.h:78
std::unique_ptr< DockNode > child_b
Definition dock_tree.h:79
static std::unique_ptr< DockNode > MakeSplit(SplitDirection dir, float ratio, std::unique_ptr< DockNode > a, std::unique_ptr< DockNode > b)
Definition dock_tree.cc:53
std::unique_ptr< DockNode > Clone() const
Definition dock_tree.cc:66
std::vector< PanelEntry > panels
Definition dock_tree.h:72
void SplitInPlace(SplitDirection dir, float ratio, std::unique_ptr< DockNode > new_child, bool new_child_first)
Definition dock_tree.cc:84
std::unique_ptr< DockNode > root
Definition dock_tree.h:115
bool Validate(std::string *error) const
Definition dock_tree.cc:222
const DockNode * FindNode(DockNodeId id) const
Definition dock_tree.cc:251