stage/structure/paths.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/stage/structure/paths.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | |||
| 6 | #include "chrono_batch_render/util/error.h" | ||
| 7 | #include "chrono_batch_render/util/text.h" | ||
| 8 | |||
| 9 | namespace chrono::batch_render { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | 59208 | void CheckPath(std::string_view path) { | |
| 14 | 59208 | if (path.empty() || path.front() != '/') { | |
| 15 | 2 | throw Error("Path \"{}\" isn't absolute", path); | |
| 16 | } | ||
| 17 | 59206 | if (path == "/") { | |
| 18 | 2 | throw Error("The root, \"/\", isn't a prim"); | |
| 19 | } | ||
| 20 | 59205 | if (path.back() == '/') { | |
| 21 | 1 | throw Error("Path \"{}\" ends in /", path); | |
| 22 | } | ||
| 23 | 59204 | } | |
| 24 | |||
| 25 | } | ||
| 26 | |||
| 27 | 138267 | uint64_t Paths::Key(uint32_t parent, Token name) { | |
| 28 | 138267 | return (uint64_t(parent) << 32) | name.id; | |
| 29 | } | ||
| 30 | |||
| 31 | 33147 | const Paths::Node& Paths::Get(uint32_t prim) const { | |
| 32 | 33147 | if (prim >= m_nodes.size() || !m_nodes[prim].used) { | |
| 33 | 1 | throw Error("No prim {} in the hierarchy", prim); | |
| 34 | } | ||
| 35 | 33146 | return m_nodes[prim]; | |
| 36 | } | ||
| 37 | |||
| 38 | 206565 | std::optional<Paths::Lazy> Paths::Locate(uint32_t prim) const { | |
| 39 | 206565 | auto found = m_blockStarts.upper_bound(prim); | |
| 40 | 206565 | if (prim == kNone || found == m_blockStarts.begin()) { | |
| 41 | 206034 | return std::nullopt; | |
| 42 | } | ||
| 43 | 531 | const Block& block = m_blocks[std::prev(found)->second]; | |
| 44 | 531 | const uint32_t count = uint32_t(block.shape.parent.size()); | |
| 45 | 531 | const uint32_t offset = prim - block.firstPrim; | |
| 46 | 531 | if (offset >= count * block.copies || block.explicitCopy[offset / count]) { | |
| 47 | 232 | return std::nullopt; | |
| 48 | } | ||
| 49 | 299 | return Lazy{&block, offset / count, offset % count}; | |
| 50 | } | ||
| 51 | |||
| 52 | 35983 | bool Paths::Contains(uint32_t prim) const { | |
| 53 | 35983 | if (prim < m_nodes.size() && m_nodes[prim].used) { | |
| 54 | return true; | ||
| 55 | } | ||
| 56 | 35117 | const std::optional<Lazy> lazy = Locate(prim); | |
| 57 | 35117 | return lazy && lazy->index > 0; | |
| 58 | } | ||
| 59 | |||
| 60 | 152623 | uint32_t Paths::Child(uint32_t parent, std::string_view name) const { | |
| 61 | 152623 | const Token token = m_tokens->Find(name); | |
| 62 | 152623 | if (token.id == 0) { | |
| 63 | return kNone; | ||
| 64 | } | ||
| 65 | 102815 | if (const std::optional<Lazy> lazy = Locate(parent)) { | |
| 66 | 66 | const auto found = lazy->block->shape.lookup.find(Key(lazy->index, token)); | |
| 67 | 66 | if (found == lazy->block->shape.lookup.end()) { | |
| 68 | return kNone; | ||
| 69 | } | ||
| 70 | 66 | return lazy->block->firstPrim + lazy->copy * uint32_t(lazy->block->shape.parent.size()) + | |
| 71 | 66 | found->second; | |
| 72 | } | ||
| 73 | 102749 | const auto found = m_children.find(Key(parent, token)); | |
| 74 | 196051 | return found == m_children.end() ? kNone : found->second; | |
| 75 | } | ||
| 76 | |||
| 77 | 35109 | void Paths::Link(uint32_t prim, uint32_t parent, Token name) { | |
| 78 | 35109 | if (!m_children.emplace(Key(parent, name), prim).second) { | |
| 79 | ✗ | throw Error("A prim named {} is already under prim {}", m_tokens->Text(name), parent); | |
| 80 | } | ||
| 81 | 35109 | if (prim >= m_nodes.size()) { | |
| 82 | 34979 | m_nodes.resize(size_t(prim) + 1); | |
| 83 | } | ||
| 84 | 35109 | Node& node = m_nodes[prim]; | |
| 85 | 35109 | node.parent = parent; | |
| 86 | 35109 | node.name = name; | |
| 87 | 35109 | node.depth = parent == kRoot ? 0 : m_nodes[parent].depth + 1; | |
| 88 | 35109 | node.used = true; | |
| 89 | 35109 | node.children.clear(); | |
| 90 | 35109 | (parent == kRoot ? m_roots : m_nodes[parent].children).push_back(prim); | |
| 91 | 35109 | ++m_size; | |
| 92 | 35109 | } | |
| 93 | |||
| 94 | 35360 | void Paths::Materialize(uint32_t prim) { | |
| 95 | 35360 | const std::optional<Lazy> lazy = Locate(prim); | |
| 96 | 35360 | if (!lazy) { | |
| 97 | 35350 | return; | |
| 98 | } | ||
| 99 | 10 | Block& block = m_blocks[m_blockStarts.at(lazy->block->firstPrim)]; | |
| 100 | 10 | const uint32_t count = uint32_t(block.shape.parent.size()); | |
| 101 | 10 | const uint32_t first = block.firstPrim + lazy->copy * count; | |
| 102 | 10 | block.explicitCopy[lazy->copy] = true; | |
| 103 | 23 | for (uint32_t i = 1; i < count; ++i) { | |
| 104 | 13 | Link(first + i, first + block.shape.parent[i], block.shape.name[i]); | |
| 105 | } | ||
| 106 | 10 | m_lazy -= count - 1; | |
| 107 | } | ||
| 108 | |||
| 109 | 52 | PathShape Paths::Shape(uint32_t source) { | |
| 110 | 52 | const std::vector<uint32_t> subtree = Subtree(source); | |
| 111 | 52 | PathShape shape; | |
| 112 | 52 | std::unordered_map<uint32_t, uint32_t> index; | |
| 113 | 184 | for (uint32_t i = 0; i < subtree.size(); ++i) { | |
| 114 | 132 | index[subtree[i]] = i; | |
| 115 | 132 | const uint32_t parent = i == 0 ? kNone : index.at(Parent(subtree[i])); | |
| 116 | 132 | shape.parent.push_back(parent); | |
| 117 | 132 | shape.name.push_back(m_tokens->Intern(Name(subtree[i]))); | |
| 118 | 132 | shape.depth.push_back(i == 0 ? 0 : shape.depth[parent] + 1); | |
| 119 | 132 | shape.children.emplace_back(); | |
| 120 | 132 | if (i > 0) { | |
| 121 | 80 | shape.children[parent].push_back(i); | |
| 122 | 80 | shape.lookup.emplace(Key(parent, shape.name[i]), i); | |
| 123 | } | ||
| 124 | } | ||
| 125 | 104 | return shape; | |
| 126 | 52 | } | |
| 127 | |||
| 128 | 51 | void Paths::InsertClones(PathShape shape, std::span<const std::string> targets, | |
| 129 | uint32_t firstPrim) { | ||
| 130 | 51 | const uint32_t count = uint32_t(shape.parent.size()); | |
| 131 | 51 | Block block; | |
| 132 | 51 | block.shape = std::move(shape); | |
| 133 | 51 | block.firstPrim = firstPrim; | |
| 134 | 51 | block.copies = uint32_t(targets.size()); | |
| 135 | 51 | block.explicitCopy.assign(targets.size(), false); | |
| 136 | 1128 | for (uint32_t copy = 0; copy < targets.size(); ++copy) { | |
| 137 | 1077 | Insert(targets[copy], firstPrim + copy * count); | |
| 138 | } | ||
| 139 | 51 | m_blockStarts[firstPrim] = uint32_t(m_blocks.size()); | |
| 140 | 51 | m_blocks.push_back(std::move(block)); | |
| 141 | 51 | m_lazy += (count - 1) * uint32_t(targets.size()); | |
| 142 | 51 | } | |
| 143 | |||
| 144 | 118465 | uint32_t Paths::Find(std::string_view path) const { | |
| 145 | 118465 | if (path.empty() || path.front() != '/' || path == "/" || path.back() == '/') { | |
| 146 | 6 | return kNone; | |
| 147 | } | ||
| 148 | 118459 | uint32_t prim = kRoot; | |
| 149 | 211827 | for (const std::string_view name : Split(path.substr(1), '/')) { | |
| 150 | 152623 | prim = Child(prim, name); | |
| 151 | 152623 | if (prim == kNone) { | |
| 152 | 59255 | return kNone; | |
| 153 | } | ||
| 154 | 59255 | } | |
| 155 | 59204 | return prim; | |
| 156 | } | ||
| 157 | |||
| 158 | 24090 | uint32_t Paths::FindParent(std::string_view path) const { | |
| 159 | 24090 | CheckPath(path); | |
| 160 | 24089 | const auto [parent, name] = SplitLast(path, '/'); | |
| 161 | 24089 | return parent.empty() ? kRoot : Find(parent); | |
| 162 | } | ||
| 163 | |||
| 164 | 35107 | void Paths::Insert(std::string_view path, uint32_t prim) { | |
| 165 | 35107 | CheckPath(path); | |
| 166 | 35104 | if (prim == kNone) { | |
| 167 | 1 | throw Error("Prim index {} is reserved", prim); | |
| 168 | } | ||
| 169 | 35103 | if (Contains(prim)) { | |
| 170 | 2 | throw Error("Prim {} is already at \"{}\"", prim, Path(prim)); | |
| 171 | } | ||
| 172 | 35102 | const auto [parentPath, text] = SplitLast(path, '/'); | |
| 173 | 35102 | if (!IsIdentifier(text)) { | |
| 174 | 2 | throw Error("\"{}\" in path \"{}\" isn't a name: names are letters, digits and _, and " | |
| 175 | "don't start with a digit", | ||
| 176 | 2 | text, path); | |
| 177 | } | ||
| 178 | 35102 | const uint32_t parent = parentPath.empty() ? kRoot : Find(parentPath); | |
| 179 | 35100 | if (parent == kNone && !parentPath.empty()) { | |
| 180 | 2 | throw Error("Path \"{}\" has no parent \"{}\"", path, parentPath); | |
| 181 | } | ||
| 182 | 35098 | if (Find(path) != kNone) { | |
| 183 | 2 | throw Error("Path \"{}\" already has a prim", path); | |
| 184 | } | ||
| 185 | 35096 | Materialize(parent); | |
| 186 | 35096 | Link(prim, parent, m_tokens->Intern(text)); | |
| 187 | 35096 | } | |
| 188 | |||
| 189 | 248 | void Paths::Erase(uint32_t prim) { | |
| 190 | 248 | if (!Contains(prim)) { | |
| 191 | ✗ | throw Error("No prim {} in the hierarchy", prim); | |
| 192 | } | ||
| 193 | 248 | Materialize(prim); | |
| 194 | 248 | Node& node = m_nodes[prim]; | |
| 195 | 248 | if (!node.children.empty()) { | |
| 196 | 1 | throw Error("Prim \"{}\" has {} children; erase them first", Path(prim), | |
| 197 | 2 | node.children.size()); | |
| 198 | } | ||
| 199 | 247 | std::vector<uint32_t>& siblings = | |
| 200 | 247 | node.parent == kRoot ? m_roots : m_nodes[node.parent].children; | |
| 201 | 247 | siblings.erase(std::ranges::find(siblings, prim)); | |
| 202 | 247 | m_children.erase(Key(node.parent, node.name)); | |
| 203 | 247 | m_nodes[prim] = {}; | |
| 204 | 247 | --m_size; | |
| 205 | 247 | } | |
| 206 | |||
| 207 | 11 | void Paths::Move(uint32_t prim, std::string_view path) { | |
| 208 | 11 | CheckPath(path); | |
| 209 | 11 | if (!Contains(prim)) { | |
| 210 | ✗ | throw Error("No prim {} in the hierarchy", prim); | |
| 211 | } | ||
| 212 | 11 | const auto [parentPath, text] = SplitLast(path, '/'); | |
| 213 | 11 | if (!IsIdentifier(text)) { | |
| 214 | 1 | throw Error("\"{}\" in path \"{}\" isn't a name: names are letters, digits and _, and " | |
| 215 | "don't start with a digit", | ||
| 216 | 1 | text, path); | |
| 217 | } | ||
| 218 | 10 | const uint32_t parent = parentPath.empty() ? kRoot : Find(parentPath); | |
| 219 | 10 | if (parent == kNone && !parentPath.empty()) { | |
| 220 | ✗ | throw Error("Path \"{}\" has no parent \"{}\"", path, parentPath); | |
| 221 | } | ||
| 222 | 15 | for (uint32_t at = parent; at != kRoot; at = Parent(at)) { | |
| 223 | 6 | if (at == prim) { | |
| 224 | 2 | throw Error("\"{}\" can't move into its own subtree at \"{}\"", Path(prim), path); | |
| 225 | } | ||
| 226 | } | ||
| 227 | 9 | if (Find(path) != kNone) { | |
| 228 | 1 | throw Error("Path \"{}\" already has a prim", path); | |
| 229 | } | ||
| 230 | 8 | Materialize(prim); | |
| 231 | 8 | Materialize(parent); | |
| 232 | 8 | const Token name = m_tokens->Intern(text); | |
| 233 | 8 | Node& node = m_nodes[prim]; | |
| 234 | 8 | std::vector<uint32_t>& siblings = | |
| 235 | 8 | node.parent == kRoot ? m_roots : m_nodes[node.parent].children; | |
| 236 | 8 | siblings.erase(std::ranges::find(siblings, prim)); | |
| 237 | 8 | m_children.erase(Key(node.parent, node.name)); | |
| 238 | 8 | m_children.emplace(Key(parent, name), prim); | |
| 239 | 8 | node.parent = parent; | |
| 240 | 8 | node.name = name; | |
| 241 | 8 | (parent == kRoot ? m_roots : m_nodes[parent].children).push_back(prim); | |
| 242 | 8 | std::vector<uint32_t> stack = {prim}; | |
| 243 | 21 | while (!stack.empty()) { | |
| 244 | 13 | Node& at = m_nodes[stack.back()]; | |
| 245 | 13 | stack.pop_back(); | |
| 246 | 13 | at.depth = at.parent == kRoot ? 0 : m_nodes[at.parent].depth + 1; | |
| 247 | 13 | stack.insert(stack.end(), at.children.begin(), at.children.end()); | |
| 248 | } | ||
| 249 | 8 | } | |
| 250 | |||
| 251 | 9985 | std::string Paths::Path(uint32_t prim) const { | |
| 252 | 9985 | std::vector<std::string_view> names; | |
| 253 | 24337 | for (uint32_t at = prim; at != kRoot; at = Parent(at)) { | |
| 254 | 28705 | names.push_back(Name(at)); | |
| 255 | } | ||
| 256 | 9984 | std::string path; | |
| 257 | 24336 | for (auto name = names.rbegin(); name != names.rend(); ++name) { | |
| 258 | 14352 | path += '/'; | |
| 259 | 14352 | path += *name; | |
| 260 | } | ||
| 261 | 9984 | return path; | |
| 262 | 9984 | } | |
| 263 | |||
| 264 | 18006 | std::string_view Paths::Name(uint32_t prim) const { | |
| 265 | 18006 | if (const std::optional<Lazy> lazy = Locate(prim); lazy && lazy->index > 0) { | |
| 266 | 60 | return m_tokens->Text(lazy->block->shape.name[lazy->index]); | |
| 267 | } | ||
| 268 | 17946 | return m_tokens->Text(Get(prim).name); | |
| 269 | } | ||
| 270 | |||
| 271 | 14461 | uint32_t Paths::Parent(uint32_t prim) const { | |
| 272 | 14461 | if (const std::optional<Lazy> lazy = Locate(prim); lazy && lazy->index > 0) { | |
| 273 | 48 | const uint32_t count = uint32_t(lazy->block->shape.parent.size()); | |
| 274 | 48 | return lazy->block->firstPrim + lazy->copy * count + lazy->block->shape.parent[lazy->index]; | |
| 275 | } | ||
| 276 | 14413 | return Get(prim).parent; | |
| 277 | } | ||
| 278 | |||
| 279 | 3 | uint32_t Paths::Depth(uint32_t prim) const { | |
| 280 | 3 | if (const std::optional<Lazy> lazy = Locate(prim); lazy && lazy->index > 0) { | |
| 281 | 1 | const uint32_t count = uint32_t(lazy->block->shape.parent.size()); | |
| 282 | 1 | return Get(lazy->block->firstPrim + lazy->copy * count).depth + | |
| 283 | 1 | lazy->block->shape.depth[lazy->index]; | |
| 284 | } | ||
| 285 | 2 | return Get(prim).depth; | |
| 286 | } | ||
| 287 | |||
| 288 | 1042 | std::vector<uint32_t> Paths::Children(uint32_t prim) const { | |
| 289 | 1042 | if (prim == kRoot) { | |
| 290 | 239 | return m_roots; | |
| 291 | } | ||
| 292 | 803 | if (const std::optional<Lazy> lazy = Locate(prim)) { | |
| 293 | 18 | const uint32_t first = | |
| 294 | 18 | lazy->block->firstPrim + lazy->copy * uint32_t(lazy->block->shape.parent.size()); | |
| 295 | 18 | std::vector<uint32_t> children; | |
| 296 | 35 | for (const uint32_t child : lazy->block->shape.children[lazy->index]) { | |
| 297 | 17 | children.push_back(first + child); | |
| 298 | } | ||
| 299 | 18 | return children; | |
| 300 | 18 | } | |
| 301 | 785 | return Get(prim).children; | |
| 302 | } | ||
| 303 | |||
| 304 | 376 | std::vector<uint32_t> Paths::Subtree(uint32_t prim) const { | |
| 305 | 376 | std::vector<uint32_t> order; | |
| 306 | 376 | std::vector<uint32_t> stack = {prim}; | |
| 307 | 376 | if (prim != kRoot && !Contains(prim)) { | |
| 308 | 1 | throw Error("No prim {} in the hierarchy", prim); | |
| 309 | } | ||
| 310 | 953 | while (!stack.empty()) { | |
| 311 | 578 | const uint32_t at = stack.back(); | |
| 312 | 578 | stack.pop_back(); | |
| 313 | 578 | if (at != kRoot) { | |
| 314 | 577 | order.push_back(at); | |
| 315 | } | ||
| 316 | 578 | const std::vector<uint32_t> children = Children(at); | |
| 317 | 578 | stack.insert(stack.end(), children.rbegin(), children.rend()); | |
| 318 | 578 | } | |
| 319 | 375 | return order; | |
| 320 | 377 | } | |
| 321 | |||
| 322 | } | ||
| 323 |