chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
506 / 592
85.5%
Branches:
509 / 705
72.2%
Functions:
41 / 49
83.7%

stage/structure/structure.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/stage/structure/structure.h"
3
4 #include <algorithm>
5 #include <cstring>
6 #include <ranges>
7 #include <set>
8 #include <unordered_map>
9 #include <utility>
10
11 #include "chrono_batch_render/scene/schema/columns.h"
12 #include "chrono_batch_render/stage/structure/packing.h"
13 #include "chrono_batch_render/util/error.h"
14 #include "chrono_batch_render/util/log.h"
15 #include "chrono_batch_render/util/span.h"
16 #include "chrono_batch_render/util/text.h"
17
18 namespace chrono::batch_render {
19
20 171 std::vector<uint32_t> RowWriter::Prims() const {
21
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 169 times.
171 if (!m_prims.empty()) {
22 2 return {m_prims.begin(), m_prims.end()};
23 }
24 169 std::vector<uint32_t> prims;
25
1/1
✓ Branch 1 taken 169 times.
169 prims.reserve(m_block.PrimCount());
26
2/2
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 169 times.
338 for (uint32_t copy = 0; copy < m_block.copies; ++copy) {
27
2/2
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 169 times.
338 for (uint32_t index = 0; index < m_block.count; ++index) {
28
1/1
✓ Branch 1 taken 169 times.
169 prims.push_back(m_block.Prim(copy, index));
29 }
30 }
31 169 return prims;
32 169 }
33
34 34461 const BlockTable& RowWriter::Rows(SchemaId table) const {
35 34461 const auto found = std::ranges::find(m_block.tables, table, &BlockTable::table);
36
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34461 times.
34461 if (found == m_block.tables.end()) {
37
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("These prims have no {} data", Info(table).name);
38 }
39 34461 return *found;
40 }
41
42 34425 uint32_t RowWriter::RowCount(const BlockTable& rows) const {
43
2/2
✓ Branch 0 taken 34326 times.
✓ Branch 1 taken 99 times.
34425 return rows.count != kNoRow ? rows.count : rows.perCopy * m_block.copies;
44 }
45
46 33210 void RowWriter::Fill(ColumnRef column, std::span<const std::byte> cell) {
47 33210 const BlockTable& rows = Rows(column.schema);
48
2/2
✓ Branch 0 taken 33209 times.
✓ Branch 1 taken 1 time.
33210 if (RowCount(rows) > 0) {
49 33209 m_structure.m_store.Fill(column, rows.first, RowCount(rows), cell);
50 }
51 33210 }
52
53 2210 void RowWriter::SetToken(ColumnRef column, std::string_view text) {
54 2210 const uint32_t id = m_structure.m_tokens.Intern(text).id;
55 2210 Fill(column, std::as_bytes(std::span(&id, 1)));
56 2210 }
57
58 9469 void RowWriter::SetArray(ColumnRef column, std::span<const std::byte> bytes, uint32_t count) {
59
2/2
✓ Branch 0 taken 1215 times.
✓ Branch 1 taken 8254 times.
9469 if (count == 0) {
60 8254 return;
61 }
62 1215 ColumnStore& store = m_structure.m_store;
63 1215 ArrayPool& arrays = store.Arrays(column.column->type);
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1215 times.
1215 if (bytes.size() != size_t(count) * arrays.ItemBytes()) {
65
0/1
✗ Branch 1 not taken.
✗ throw Error("{} bytes for {} items of {}'s {}, whose items are {} bytes", bytes.size(),
66
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ count, Info(column.schema).name, column.column->field, arrays.ItemBytes());
67 }
68 1215 const ArrayRef array = arrays.Allocate(count);
69 1215 arrays.Write(array, bytes);
70 1215 const BlockTable& rows = Rows(column.schema);
71
1/2
✓ Branch 0 taken 1215 times.
✗ Branch 1 not taken.
1215 if (RowCount(rows) == 0) {
72 return;
73 }
74 1215 const uint32_t last = rows.first + RowCount(rows);
75
2/2
✓ Branch 0 taken 1224 times.
✓ Branch 1 taken 1215 times.
2439 for (uint32_t row = rows.first; row < last; ++row) {
76
2/2
✓ Branch 0 taken 1215 times.
✓ Branch 1 taken 9 times.
1224 store.SetArray(column, row, row == rows.first ? array : arrays.Share(array));
77 }
78 }
79
80 1658 void RowWriter::SetTokens(ColumnRef column, std::span<const std::string> texts) {
81 1658 const std::vector<uint32_t> ids = InternTokens(m_structure.m_tokens, texts);
82
1/1
✓ Branch 1 taken 1658 times.
1658 SetArray(column, std::as_bytes(std::span(ids)), uint32_t(ids.size()));
83 1658 }
84
85 4060 void RowWriter::SetTargets(ColumnRef column, std::span<const std::string> paths) {
86 4060 const std::vector<uint32_t> targets = m_structure.ResolveTargets(paths);
87
1/1
✓ Branch 1 taken 4060 times.
4060 m_structure.MarkTargets(targets);
88
1/1
✓ Branch 1 taken 4060 times.
4060 SetArray(column, std::as_bytes(std::span(targets)), uint32_t(targets.size()));
89
2/2
✓ Branch 1 taken 4024 times.
✓ Branch 2 taken 36 times.
4060 if (std::ranges::find(targets, kNoPrim) == targets.end()) {
90 4024 return;
91 }
92
1/1
✓ Branch 1 taken 36 times.
36 const BlockTable& rows = Rows(column.schema);
93
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 36 times.
72 for (uint32_t copy = 0; copy < m_block.copies; ++copy) {
94
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 36 times.
72 for (uint32_t index = 0; index < m_block.count; ++index) {
95
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 const uint32_t prim = m_prims.empty() ? m_block.Prim(copy, index) : m_prims[copy];
96
1/1
✓ Branch 1 taken 36 times.
36 const uint32_t row = m_structure.m_prims.FindRow(prim, column.schema);
97 36 m_structure.m_pending.push_back({prim, m_structure.m_prims.Generation(prim), column,
98
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 IsMultipleApi(column.schema) ? rows.first + copy : row,
99
1/1
✓ Branch 1 taken 36 times.
36 std::vector<std::string>(paths.begin(), paths.end())});
100 }
101 }
102 4168 }
103
104 1016 void RowWriter::SetPrimvars(ColumnRef column, std::span<const Primvar> primvars) {
105 1016 const std::vector<int32_t> table = PackPrimvars(m_structure.m_tokens, primvars);
106
1/1
✓ Branch 1 taken 1016 times.
1016 SetArray(column, std::as_bytes(std::span(table)), uint32_t(table.size()));
107 1016 }
108
109 501 Structure::Structure(ColumnStore& store) : m_store(store), m_paths(m_tokens), m_prims(store) {}
110
111 43752 uint32_t Structure::Find(std::string_view path) const {
112 43752 return m_paths.Find(path);
113 }
114
115 236 std::vector<uint32_t> Structure::Select(std::string_view pattern, SchemaId schema) const {
116
3/6
✓ Branch 0 taken 236 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 236 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 236 times.
236 if (pattern.empty() || pattern.front() != '/' || pattern == "/") {
117
0/1
✗ Branch 2 not taken.
✗ throw Error("Pattern \"{}\" isn't an absolute path", pattern);
118 }
119 236 std::vector<uint32_t> level = {Paths::kRoot};
120
4/4
✓ Branch 1 taken 236 times.
✓ Branch 4 taken 236 times.
✓ Branch 6 taken 295 times.
✓ Branch 7 taken 236 times.
531 for (const std::string_view part : Split(pattern.substr(1), '/')) {
121 295 std::vector<uint32_t> next;
122
2/2
✓ Branch 0 taken 301 times.
✓ Branch 1 taken 295 times.
596 for (const uint32_t parent : level) {
123
3/3
✓ Branch 1 taken 301 times.
✓ Branch 3 taken 3517 times.
✓ Branch 4 taken 301 times.
3818 for (const uint32_t child : m_paths.Children(parent)) {
124
4/4
✓ Branch 1 taken 3517 times.
✓ Branch 4 taken 3517 times.
✓ Branch 6 taken 1175 times.
✓ Branch 7 taken 2342 times.
3517 if (Glob(part, m_paths.Name(child))) {
125
1/1
✓ Branch 1 taken 1175 times.
1175 next.push_back(child);
126 }
127 301 }
128 }
129 295 level = std::move(next);
130 295 }
131
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 205 times.
236 if (schema != SchemaId::None) {
132
1/1
✓ Branch 1 taken 31 times.
31 std::erase_if(level, [&](uint32_t prim) {
133
4/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 164 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 time.
167 return !IsA(m_prims.Record(prim).schema, schema) && !m_prims.HasApi(prim, schema);
134 });
135 }
136 236 return level;
137 ✗ }
138
139 24075 uint32_t Structure::NewParent(std::string_view path) const {
140
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 24073 times.
24075 if (const uint32_t existing = m_paths.Find(path); existing != Paths::kNone) {
141
3/4
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 7 taken 2 times.
4 throw Error("\"{}\" is already a {}", path, Info(m_prims.Record(existing).schema).name);
142 }
143 24073 m_paths.FindParent(path);
144 24072 const auto [parentPath, name] = SplitLast(path, '/');
145
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 24072 times.
24072 if (!IsIdentifier(name)) {
146 ✗ throw Error("\"{}\" in path \"{}\" isn't a name: names are letters, digits and _, and "
147 "don't start with a digit",
148
0/1
✗ Branch 2 not taken.
✗ name, path);
149 }
150
2/2
✓ Branch 0 taken 1169 times.
✓ Branch 1 taken 22903 times.
24072 if (parentPath.empty()) {
151 return kNoPrim;
152 }
153 1169 const uint32_t parent = m_paths.Find(parentPath);
154
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1167 times.
1169 if (parent == Paths::kNone) {
155
1/1
✓ Branch 2 taken 2 times.
2 throw Error("\"{}\" has no parent \"{}\"", path, parentPath);
156 }
157 return parent;
158 }
159
160 2676 PrimBlock Structure::Allocate(SchemaId schema, std::span<const std::string> paths, uint64_t apis,
161 bool attachable) {
162
2/2
✓ Branch 0 taken 2345 times.
✓ Branch 1 taken 331 times.
2676 CheckStoreLock(m_store.Lock(), "Editing a scene's prims");
163 2675 std::set<std::string_view> seen;
164 2675 std::vector<uint32_t> parents;
165
2/2
✓ Branch 0 taken 23995 times.
✓ Branch 1 taken 2671 times.
26666 for (const std::string& path : paths) {
166
3/3
✓ Branch 1 taken 23995 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 23994 times.
23995 if (!seen.insert(path).second) {
167
1/1
✓ Branch 2 taken 1 time.
1 throw Error("\"{}\" is defined twice", path);
168 }
169
2/2
✓ Branch 1 taken 23991 times.
✓ Branch 4 taken 23991 times.
23994 parents.push_back(NewParent(path));
170 }
171 2671 const PrimBlock block =
172
3/3
✓ Branch 1 taken 2671 times.
✓ Branch 4 taken 2671 times.
✓ Branch 7 taken 2668 times.
5342 m_prims.Add({{schema}, {kNoPrim}, {apis}}, parents, kGlobalWorld, attachable);
173
2/2
✓ Branch 0 taken 23987 times.
✓ Branch 1 taken 2668 times.
26655 for (uint32_t copy = 0; copy < block.copies; ++copy) {
174
1/1
✓ Branch 1 taken 23987 times.
23987 const uint32_t prim = block.Prim(copy, 0);
175 23987 try {
176
1/1
✓ Branch 1 taken 23987 times.
23987 m_paths.Insert(paths[copy], prim);
177 ✗ } catch (...) {
178
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ for (uint32_t undo = 0; undo < copy; ++undo) {
179
0/1
✗ Branch 1 not taken.
✗ m_paths.Erase(block.Prim(undo, 0));
180 }
181
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (uint32_t undo = 0; undo < block.copies; ++undo) {
182
0/1
✗ Branch 1 not taken.
✗ m_prims.Remove(block.Prim(undo, 0));
183 }
184 ✗ throw;
185 ✗ }
186
1/1
✓ Branch 1 taken 23987 times.
23987 Logged(prim, true);
187 }
188 2668 ++m_epoch;
189 5336 return block;
190 8020 }
191
192 216 PrimBlock Structure::Define(SchemaId schema, std::span<const std::string> paths) {
193
1/1
✓ Branch 1 taken 211 times.
427 return Define(schema, paths, [&](RowWriter& writer) { WriteFallbacks(schema, writer); });
194 }
195
196 ✗ void Structure::RemoveBlock(const PrimBlock& block) {
197
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (uint32_t prim = block.firstPrim + block.PrimCount(); prim-- > block.firstPrim;) {
198
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (m_prims.Alive(prim)) {
199 ✗ RemovePrim(prim);
200 }
201 }
202 ✗ }
203
204 24493 void Structure::Logged(uint32_t prim, bool added, SchemaId api) {
205
2/2
✓ Branch 0 taken 8144 times.
✓ Branch 1 taken 16349 times.
24493 CheckStoreLock(m_store.Lock(), "Editing a scene's prims");
206 97972 m_log.push_back(
207 24493 {m_store.Stamp(), prim, m_prims.Generation(prim), m_prims.Record(prim).world, added, api});
208 24493 }
209
210 27 std::vector<uint32_t> Structure::Applicable(std::span<const uint32_t> prims, SchemaId api,
211 bool multiple) const {
212
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
27 if (!IsApi(api)) {
213
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("{} isn't an API schema", Info(api).name);
214 }
215
3/4
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
27 if (IsMultipleApi(api) && !multiple) {
216
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("{} is applied many times, so it needs an instance name", Info(api).name);
217 }
218
3/4
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
27 if (!IsMultipleApi(api) && multiple) {
219
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("{} is applied once, without an instance name", Info(api).name);
220 }
221 27 std::vector<uint32_t> targets;
222 27 std::set<uint32_t> seen;
223
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 25 times.
52 for (const uint32_t prim : prims) {
224
2/3
✓ Branch 1 taken 27 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 27 times.
27 if (!m_prims.Alive(prim)) {
225
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", prim);
226 }
227
1/1
✓ Branch 1 taken 27 times.
27 const SchemaId schema = m_prims.Record(prim).schema;
228
5/6
✓ Branch 1 taken 27 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 25 times.
✓ Branch 6 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
27 if (!AppliesTo(api, schema) && !Includes(schema, api)) {
229 ✗ throw Error("{} doesn't apply to {}, a {}", Info(api).name, m_paths.Path(prim),
230
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
8 Info(schema).name);
231 }
232
6/8
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 21 times.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 25 times.
✓ Branch 10 taken 25 times.
✗ Branch 11 not taken.
25 if ((multiple || !m_prims.HasApi(prim, api)) && seen.insert(prim).second) {
233
1/1
✓ Branch 1 taken 25 times.
25 targets.push_back(prim);
234 }
235 }
236 25 return targets;
237 27 }
238
239 ✗ void Structure::Apply(std::span<const uint32_t> prims, SchemaId api) {
240
0/1
✗ Branch 1 not taken.
✗ Apply(prims, api, [&](RowWriter& writer) { WriteFallbacks(api, writer); });
241 ✗ }
242
243 6 void Structure::Unapply(std::span<const uint32_t> prims, SchemaId api,
244 std::optional<std::string_view> instance) {
245
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if (!IsApi(api)) {
246
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("{} isn't an API schema", Info(api).name);
247 }
248
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
6 if (instance && !IsMultipleApi(api)) {
249
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ throw Error("{} is applied once, without an instance name", Info(api).name);
250 }
251 6 const bool multiple = IsMultipleApi(api);
252 6 std::vector<uint32_t> targets;
253
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 for (const uint32_t prim : prims) {
254
2/3
✓ Branch 1 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
6 if (!m_prims.Alive(prim)) {
255
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", prim);
256 }
257
1/1
✓ Branch 1 taken 6 times.
6 const SchemaId schema = m_prims.Record(prim).schema;
258
4/5
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 5 times.
6 if (!multiple && Includes(schema, api)) {
259 3 throw Error("{} is built into {}, so {} can't lose it", Info(api).name,
260
3/4
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 7 taken 1 time.
4 Info(schema).name, m_paths.Path(prim));
261 }
262
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 4 times.
106 for (const SchemaId outer : kApiSchemas) {
263
8/9
✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✓ Branch 3 taken 5 times.
✓ Branch 5 taken 97 times.
✓ Branch 7 taken 17 times.
✓ Branch 8 taken 80 times.
✓ Branch 9 taken 16 times.
✓ Branch 10 taken 1 time.
119 if (!multiple && outer != api && m_prims.HasApi(prim, outer) &&
264
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 17 times.
34 HasApi(Info(outer).apis, api)) {
265 3 throw Error("{} comes with {}, so {} can't lose it while it has {}", Info(api).name,
266
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
✓ Branch 9 taken 1 time.
5 Info(outer).name, m_paths.Path(prim), Info(outer).name);
267 }
268 }
269
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (multiple) {
270
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const ApiInstance& entry : m_prims.Instances(prim, api)) {
271
0/1
✗ Branch 1 not taken.
✗ const std::string_view name = m_tokens.Text(Token{entry.instance});
272
0/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ if ((!instance || name == *instance) && BuiltinInstance(schema, api, name)) {
273 ✗ throw Error("{}:{} is built into {}, so {} can't lose it", Info(api).name, name,
274
0/4
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Info(schema).name, m_paths.Path(prim));
275 }
276 }
277 }
278
3/5
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
4 if (m_prims.HasApi(prim, api) && std::ranges::find(targets, prim) == targets.end()) {
279
1/1
✓ Branch 1 taken 4 times.
4 targets.push_back(prim);
280 }
281 }
282
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (targets.empty()) {
283 ✗ return;
284 }
285
2/3
✓ Branch 1 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
4 if (m_store.Pinned(api)) {
286 ✗ throw Error("{} data is mapped or attached, so it can't be removed until the mapping "
287 "closes or the attachment is released",
288
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Info(api).name);
289 }
290 4 std::set<uint32_t> released;
291
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (const uint32_t prim : targets) {
292
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (!multiple) {
293
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 released.insert(m_prims.Row(prim, api));
294 4 continue;
295 }
296
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const ApiInstance& entry : m_prims.Instances(prim, api)) {
297
0/5
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (!instance || m_tokens.Text(Token{entry.instance}) == *instance) {
298
0/1
✗ Branch 1 not taken.
✗ released.insert(entry.row);
299 }
300 }
301 }
302 4 std::erase_if(m_pending, [&](const PendingTargets& pending) {
303
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ return pending.column.schema == api && released.contains(pending.row);
304 });
305
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (!multiple) {
306
1/1
✓ Branch 1 taken 4 times.
4 m_prims.Unapply(targets, api);
307 } else {
308 ✗ std::vector<uint32_t> names;
309
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const uint32_t prim : targets) {
310
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const ApiInstance& entry : m_prims.Instances(prim, api)) {
311
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ if (released.contains(entry.row) &&
312
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ std::ranges::find(names, entry.instance) == names.end()) {
313
0/1
✗ Branch 1 not taken.
✗ names.push_back(entry.instance);
314 }
315 }
316 }
317
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ for (const uint32_t name : names) {
318
0/1
✗ Branch 1 not taken.
✗ m_prims.UnapplyInstance(targets, api, name);
319 }
320 ✗ }
321
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (const uint32_t prim : targets) {
322
1/1
✓ Branch 1 taken 4 times.
4 Logged(prim, false, api);
323 }
324 4 ++m_epoch;
325 6 }
326
327 241 void Structure::RemovePrim(uint32_t prim) {
328 241 ReleaseFiles(prim);
329
1/2
✓ Branch 1 taken 241 times.
✗ Branch 2 not taken.
241 if (m_paths.Contains(prim)) {
330 241 m_paths.Erase(prim);
331 }
332 241 Logged(prim, false);
333 241 m_prims.Remove(prim);
334 241 ++m_epoch;
335 241 }
336
337 4420 void Structure::MarkTargets(std::span<const uint32_t> targets) {
338
2/2
✓ Branch 0 taken 989 times.
✓ Branch 1 taken 4420 times.
5409 for (const uint32_t target : targets) {
339
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 944 times.
989 if (target == kNoPrim) {
340 45 continue;
341 }
342
2/2
✓ Branch 0 taken 718 times.
✓ Branch 1 taken 226 times.
944 if (target >= m_targeted.size()) {
343 718 m_targeted.resize(size_t(target) + 1);
344 }
345 944 m_targeted[target] = true;
346 }
347 4420 }
348
349 ✗ void Structure::MarkUntrackedTargets() {
350 ✗ m_untrackedTargets = true;
351 ✗ }
352
353 6 std::vector<Structure::Referrer> Structure::Referrers(std::span<const uint32_t> removed) {
354 6 struct Cell {
355 uint32_t prim;
356 ColumnRef column;
357 uint32_t row;
358 };
359
360 6 std::vector<Cell> cells;
361 6 std::vector<device::Download> reads;
362
1/1
✓ Branch 1 taken 6 times.
6 const std::set<uint32_t> gone(removed.begin(), removed.end());
363
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 6 times.
48 for (uint32_t prim = 0; prim < m_prims.Size(); ++prim) {
364
5/5
✓ Branch 1 taken 42 times.
✓ Branch 3 taken 41 times.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 34 times.
42 if (!m_prims.Alive(prim) || gone.contains(prim)) {
365 8 continue;
366 }
367
1/1
✓ Branch 1 taken 34 times.
34 const PrimRecord& record = m_prims.Record(prim);
368
3/3
✓ Branch 1 taken 34 times.
✓ Branch 3 taken 112 times.
✓ Branch 4 taken 34 times.
146 for (const SchemaId table : PrimTables(record.schema, record.apis)) {
369
1/1
✓ Branch 1 taken 112 times.
112 const Span<const ColumnInfo> columns = Columns(table);
370
2/2
✓ Branch 0 taken 445 times.
✓ Branch 1 taken 112 times.
557 for (uint32_t index = 0; index < columns.size(); ++index) {
371
2/2
✓ Branch 0 taken 397 times.
✓ Branch 1 taken 48 times.
445 if (columns[index].type != ColumnType::PrimArray) {
372 397 continue;
373 }
374 48 const ColumnRef column{table, &columns[index], index};
375 48 std::vector<uint32_t> rows;
376
3/3
✓ Branch 1 taken 48 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 44 times.
48 if (IsMultipleApi(table)) {
377
3/3
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 4 times.
12 for (const ApiInstance& entry : m_prims.Instances(prim, table)) {
378
1/1
✓ Branch 1 taken 8 times.
8 rows.push_back(entry.row);
379 }
380 } else {
381
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 rows.push_back(m_prims.Row(prim, table));
382 }
383
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 48 times.
100 for (const uint32_t row : rows) {
384
1/1
✓ Branch 1 taken 52 times.
52 const ArrayRef array = m_store.Array(column, row);
385
3/4
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
52 if (array.address == 0 || array.count == 0) {
386 32 continue;
387 }
388
1/1
✓ Branch 1 taken 20 times.
20 cells.push_back({prim, column, row});
389
2/2
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
60 reads.push_back(m_store.GetDevice().GetQueue().ReadAsync(
390
1/1
✓ Branch 1 taken 20 times.
20 m_store.Arrays(ColumnType::PrimArray)
391
1/1
✓ Branch 1 taken 20 times.
40 .Range(array)
392
1/1
✓ Branch 1 taken 20 times.
20 .Slice(0, uint64_t(array.count) * sizeof(uint32_t))));
393 }
394 48 }
395 34 }
396 }
397 6 std::vector<Referrer> referrers;
398
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 6 times.
26 for (size_t cell = 0; cell < cells.size(); ++cell) {
399
1/1
✓ Branch 1 taken 20 times.
20 reads[cell].Wait();
400
3/3
✓ Branch 1 taken 20 times.
✓ Branch 4 taken 20 times.
✓ Branch 7 taken 20 times.
20 std::vector<uint32_t> targets(reads[cell].Bytes().size() / sizeof(uint32_t));
401
1/1
✓ Branch 1 taken 20 times.
20 std::memcpy(targets.data(), reads[cell].Bytes().data(), targets.size() * sizeof(uint32_t));
402
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 15 times.
42 if (std::ranges::any_of(targets, [&](uint32_t target) { return gone.contains(target); })) {
403
1/1
✓ Branch 1 taken 5 times.
5 referrers.push_back(
404 5 {cells[cell].prim, cells[cell].column, cells[cell].row, std::move(targets)});
405 }
406 20 }
407 12 return referrers;
408 11 }
409
410 237 void Structure::Remove(uint32_t prim) {
411
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 237 times.
237 if (!m_prims.Alive(prim)) {
412
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", prim);
413 }
414 237 const std::vector<uint32_t> subtree = m_paths.Subtree(prim);
415
2/2
✓ Branch 0 taken 243 times.
✓ Branch 1 taken 234 times.
477 for (const uint32_t at : subtree) {
416
1/1
✓ Branch 1 taken 240 times.
243 m_prims.CheckRemovable(at);
417 }
418
3/4
✓ Branch 0 taken 234 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 228 times.
234 const bool targeted = m_untrackedTargets || std::ranges::any_of(subtree, [&](uint32_t at) {
419
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 232 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 6 times.
240 return at < m_targeted.size() && m_targeted[at];
420 234 });
421
422 11 struct Retarget {
423 uint32_t prim;
424 uint32_t row;
425 ColumnRef column;
426 std::vector<std::string> paths;
427 };
428
429 234 std::vector<Retarget> retargets;
430
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 228 times.
234 if (targeted) {
431
1/1
✓ Branch 1 taken 6 times.
6 m_store.Flush();
432
3/3
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 6 times.
11 for (const Referrer& referrer : Referrers(subtree)) {
433 5 const auto pending = std::ranges::find_if(m_pending, [&](const PendingTargets& entry) {
434
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ return entry.row == referrer.row && entry.column.schema == referrer.column.schema &&
435
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ entry.column.index == referrer.column.index;
436 });
437 5 std::vector<std::string> paths;
438
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (pending != m_pending.end()) {
439
0/1
✗ Branch 1 not taken.
✗ paths = pending->paths;
440 } else {
441
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
11 for (const uint32_t target : referrer.targets) {
442
3/5
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
6 if (target != kNoPrim && m_prims.Alive(target)) {
443
1/1
✓ Branch 1 taken 6 times.
12 paths.push_back(m_paths.Path(target));
444 }
445 }
446 }
447 5 retargets.push_back({referrer.prim, referrer.row, referrer.column, std::move(paths)});
448 11 }
449 }
450
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 234 times.
474 for (const uint32_t at : std::views::reverse(subtree)) {
451
1/1
✓ Branch 1 taken 240 times.
240 RemovePrim(at);
452
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 232 times.
240 if (at < m_targeted.size()) {
453 8 m_targeted[at] = false;
454 }
455 }
456
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 234 times.
239 for (const Retarget& retarget : retargets) {
457
1/1
✓ Branch 1 taken 5 times.
5 SetTargets(retarget.prim, retarget.row, retarget.column, retarget.paths);
458
2/2
✓ Branch 1 taken 5 times.
✓ Branch 4 taken 5 times.
5 m_store.StampWorld(m_prims.Record(retarget.prim).world);
459 }
460 242 }
461
462 23 void Structure::Retype(uint32_t prim, SchemaId schema) {
463
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
23 if (!m_prims.Alive(prim)) {
464
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", prim);
465 }
466 23 const PrimRecord& record = m_prims.Record(prim);
467
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 20 times.
23 if (record.schema == schema) {
468 3 return;
469 }
470
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
40 if (Info(schema).kind != SchemaKind::Concrete) {
471 ✗ throw Error("{} can't become a {}, which isn't a concrete schema", m_paths.Path(prim),
472
0/4
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
✗ Info(schema).name);
473 }
474 20 const SchemaId from = record.schema;
475
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
20 uint64_t apis = Info(schema).apis & ~kMultipleApis;
476 20 std::vector<std::string> dropped;
477
2/2
✓ Branch 0 taken 440 times.
✓ Branch 1 taken 20 times.
460 for (const SchemaId api : kApiSchemas) {
478
5/9
✓ Branch 1 taken 440 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 425 times.
✓ Branch 6 taken 15 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 15 times.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
455 if (!HasApi(record.apis, api) || HasApi(Info(from).apis, api) || HasApi(apis, api)) {
479 440 continue;
480 }
481
0/6
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ if (AppliesTo(api, schema) || Includes(schema, api)) {
482
0/1
✗ Branch 1 not taken.
✗ apis |= ApiBit(api);
483 } else {
484
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ dropped.emplace_back(Info(api).name);
485 }
486 }
487
1/1
✓ Branch 1 taken 20 times.
20 const std::vector<SchemaId> before = PrimTables(from, record.apis);
488
1/1
✓ Branch 1 taken 20 times.
20 const std::vector<SchemaId> after = PrimTables(schema, apis);
489 20 std::set<std::pair<SchemaId, uint32_t>> released;
490
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 20 times.
94 for (const SchemaId table : before) {
491
2/2
✓ Branch 1 taken 50 times.
✓ Branch 2 taken 24 times.
74 if (std::ranges::find(after, table) != after.end()) {
492 50 continue;
493 }
494
2/3
✓ Branch 1 taken 24 times.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
24 if (!IsMultipleApi(table)) {
495
2/2
✓ Branch 1 taken 24 times.
✓ Branch 4 taken 24 times.
24 released.insert({table, m_prims.Row(prim, table)});
496 24 continue;
497 }
498
0/3
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ for (const ApiInstance& entry : m_prims.Instances(prim, table)) {
499
0/1
✗ Branch 1 not taken.
✗ released.insert({table, entry.row});
500 }
501 }
502 20 std::erase_if(m_pending, [&](const PendingTargets& pending) {
503 ✗ return released.contains({pending.column.schema, pending.row});
504 });
505
1/1
✓ Branch 1 taken 20 times.
20 Logged(prim, false);
506 20 PrimBlock block;
507 20 block.firstPrim = prim;
508 20 block.count = 1;
509 20 block.copies = 1;
510
1/1
✓ Branch 1 taken 20 times.
40 block.tables = m_prims.Retype(prim, schema, apis);
511
1/1
✓ Branch 1 taken 20 times.
20 RowWriter writer(*this, block, {}, true);
512
1/1
✓ Branch 1 taken 20 times.
20 WriteFallbacks(schema, writer);
513
1/1
✓ Branch 1 taken 20 times.
20 Logged(prim, true);
514 20 ++m_epoch;
515
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 20 times.
40 chrono::batch_render::Log(LogLevel::Debug, "{} changed from a {} to a {}", m_paths.Path(prim),
516
3/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 17 times.
✓ Branch 3 taken 20 times.
40 Info(from).name, Info(schema).name);
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (!dropped.empty()) {
518
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ chrono::batch_render::Log(LogLevel::Warning, "{} lost {}, which doesn't apply to a {}",
519
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ m_paths.Path(prim), Join(dropped), Info(schema).name);
520 }
521 20 }
522
523 15 void Structure::Move(uint32_t prim, std::string_view path) {
524
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
15 if (!m_prims.Alive(prim)) {
525
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", prim);
526 }
527 15 const uint32_t parent = m_paths.FindParent(path);
528
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 7 times.
15 if (parent == Paths::kNone && path.rfind('/') != 0) {
529
1/1
✓ Branch 2 taken 1 time.
1 throw Error("Path \"{}\" has no parent", path);
530 }
531 14 const PrimRecord& record = m_prims.Record(prim);
532
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 const uint32_t world = parent == Paths::kRoot ? kGlobalWorld : m_prims.Record(parent).world;
533 14 const bool isWorld = IsA(record.schema, SchemaId::World);
534
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 11 times.
14 if (isWorld ? world != kGlobalWorld : world != record.world) {
535 3 throw Error("\"{}\" can't move to \"{}\": prims stay in their world, and worlds don't nest",
536
2/2
✓ Branch 2 taken 3 times.
✓ Branch 5 taken 3 times.
6 m_paths.Path(prim), path);
537 }
538 11 const std::vector<uint32_t> before = m_paths.Subtree(prim);
539
1/1
✓ Branch 1 taken 8 times.
11 m_paths.Move(prim, path);
540
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 8 times.
21 for (const uint32_t at : before) {
541
1/1
✓ Branch 1 taken 13 times.
13 Logged(at, false);
542
1/1
✓ Branch 1 taken 13 times.
13 m_prims.Renew(at);
543 }
544
1/1
✓ Branch 1 taken 8 times.
8 m_prims.Reparent(prim, parent);
545 8 std::vector<std::string> moved;
546
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 8 times.
21 for (const uint32_t at : before) {
547
1/1
✓ Branch 1 taken 13 times.
13 Logged(at, true);
548
1/1
✓ Branch 1 taken 13 times.
26 moved.push_back(m_paths.Path(at));
549 }
550
1/1
✓ Branch 1 taken 8 times.
8 ResolvePending(moved);
551 8 ++m_epoch;
552 11 }
553
554 304 void Structure::SetTargets(uint32_t prim, uint32_t row, ColumnRef column,
555 std::span<const std::string> paths) {
556 304 std::erase_if(m_pending, [&](const PendingTargets& pending) {
557
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 return pending.row == row && pending.column.schema == column.schema &&
558
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ pending.column.index == column.index;
559 });
560 304 const std::vector<uint32_t> targets = ResolveTargets(paths);
561
1/1
✓ Branch 1 taken 304 times.
304 MarkTargets(targets);
562
1/1
✓ Branch 1 taken 304 times.
304 ArrayPool& arrays = m_store.Arrays(column.column->type);
563
1/1
✓ Branch 1 taken 304 times.
304 const ArrayRef array = arrays.Allocate(uint32_t(targets.size()));
564
1/1
✓ Branch 1 taken 304 times.
304 arrays.Write(array, std::as_bytes(std::span(targets)));
565
1/1
✓ Branch 1 taken 304 times.
304 m_store.SetArray(column, row, array);
566
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 295 times.
304 if (std::ranges::find(targets, kNoPrim) != targets.end()) {
567 9 m_pending.push_back({prim, m_prims.Generation(prim), column, row,
568
1/1
✓ Branch 1 taken 9 times.
9 std::vector<std::string>(paths.begin(), paths.end())});
569 }
570 313 }
571
572 4406 std::vector<uint32_t> Structure::ResolveTargets(std::span<const std::string> paths) const {
573 4406 std::vector<uint32_t> targets;
574
1/1
✓ Branch 1 taken 4406 times.
4406 targets.reserve(paths.size());
575
2/2
✓ Branch 0 taken 973 times.
✓ Branch 1 taken 4406 times.
5379 for (const std::string& path : paths) {
576
2/2
✓ Branch 1 taken 973 times.
✓ Branch 4 taken 973 times.
973 targets.push_back(m_paths.Find(path));
577 }
578 4406 return targets;
579 ✗ }
580
581 2676 void Structure::ResolvePending(std::span<const std::string> defined) {
582 2676 std::erase_if(m_pending, [&](const PendingTargets& pending) {
583
1/2
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
91 if (!m_prims.Alive(pending.prim) ||
584
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 91 times.
91 m_prims.Generation(pending.prim) != pending.generation) {
585 ✗ return true;
586 }
587 91 const bool named = std::ranges::any_of(defined, [&](const std::string& path) {
588 91 return std::ranges::find(pending.paths, path) != pending.paths.end();
589 });
590
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 49 times.
91 if (!named) {
591 return false;
592 }
593 42 const std::vector<uint32_t> targets = ResolveTargets(pending.paths);
594
1/1
✓ Branch 1 taken 42 times.
42 MarkTargets(targets);
595
1/1
✓ Branch 1 taken 42 times.
42 ArrayPool& arrays = m_store.Arrays(pending.column.column->type);
596
1/1
✓ Branch 1 taken 42 times.
42 const ArrayRef array = arrays.Allocate(uint32_t(targets.size()));
597
1/1
✓ Branch 1 taken 42 times.
42 arrays.Write(array, std::as_bytes(std::span(targets)));
598
1/1
✓ Branch 1 taken 42 times.
42 m_store.SetArray(pending.column, pending.row, array);
599 42 return std::ranges::find(targets, kNoPrim) == targets.end();
600 42 });
601 2676 }
602
603 55 PrimBlock Structure::Clone(uint32_t source, std::span<const std::string> targets,
604 const CloneDesc& desc) {
605
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
55 if (!m_prims.Alive(source)) {
606
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", source);
607 }
608 55 const std::vector<uint32_t> sources = m_paths.Subtree(source);
609
1/1
✓ Branch 1 taken 55 times.
55 const std::string root = m_paths.Path(source);
610 55 std::vector<uint32_t> parents;
611 55 std::vector<std::string> paths;
612
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 54 times.
55 if (desc.moveSource) {
613
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const uint32_t prim : sources) {
614
1/1
✓ Branch 1 taken 1 time.
1 m_prims.CheckRemovable(prim);
615 }
616
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 parents.push_back(m_prims.Record(source).parent);
617
1/1
✓ Branch 1 taken 1 time.
1 paths.push_back(root);
618 }
619 55 std::set<std::string_view> seen;
620
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 51 times.
134 for (const std::string& target : targets) {
621
3/3
✓ Branch 1 taken 83 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 82 times.
83 if (!seen.insert(target).second) {
622
1/1
✓ Branch 2 taken 1 time.
1 throw Error("\"{}\" is a target twice", target);
623 }
624
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 81 times.
82 if (target.starts_with(root + "/")) {
625
1/1
✓ Branch 2 taken 1 time.
1 throw Error("\"{}\" is inside the clone's source \"{}\"", target, root);
626 }
627
2/2
✓ Branch 1 taken 79 times.
✓ Branch 4 taken 79 times.
84 parents.push_back(NewParent(target));
628
1/1
✓ Branch 1 taken 79 times.
79 paths.push_back(target);
629 }
630
631 51 BlockShape shape;
632 51 std::unordered_map<uint32_t, uint32_t> position;
633 51 std::vector<uint32_t> worlds;
634
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 51 times.
179 for (uint32_t i = 0; i < sources.size(); ++i) {
635
1/1
✓ Branch 1 taken 128 times.
128 const PrimRecord& record = m_prims.Record(sources[i]);
636
1/1
✓ Branch 1 taken 128 times.
128 position[sources[i]] = i;
637
1/1
✓ Branch 1 taken 128 times.
128 shape.schemas.push_back(record.schema);
638
4/4
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 51 times.
✓ Branch 3 taken 77 times.
✓ Branch 6 taken 128 times.
128 shape.parents.push_back(i == 0 ? kNoPrim : position.at(record.parent));
639
1/1
✓ Branch 1 taken 128 times.
128 shape.apis.push_back(record.apis);
640
3/3
✓ Branch 1 taken 128 times.
✓ Branch 3 taken 31 times.
✓ Branch 4 taken 97 times.
128 if (IsA(record.schema, SchemaId::World)) {
641
1/1
✓ Branch 1 taken 31 times.
31 worlds.push_back(record.world);
642 }
643 }
644
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 50 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
51 bool reusable = desc.moveSource && !worlds.empty() && worlds.back() + 1 == m_prims.WorldCount();
645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 for (uint32_t i = 1; i < worlds.size(); ++i) {
646
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ reusable = reusable && worlds[i] == worlds[i - 1] + 1;
647 }
648
649
1/1
✓ Branch 1 taken 51 times.
51 PathShape pathShape = m_paths.Shape(source);
650 51 const PrimBlock block =
651
3/3
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 50 times.
✓ Branch 3 taken 50 times.
51 m_prims.Add(shape, parents, reusable ? worlds.front() : kGlobalWorld, desc.attachable);
652
2/2
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 50 times.
381 for (const BlockTable& table : block.tables) {
653 331 std::vector<uint32_t> rows;
654
2/2
✓ Branch 0 taken 1169 times.
✓ Branch 1 taken 331 times.
1500 for (const uint32_t prim : sources) {
655
3/3
✓ Branch 1 taken 1169 times.
✓ Branch 3 taken 566 times.
✓ Branch 4 taken 603 times.
1169 if (const uint32_t row = m_prims.FindRow(prim, table.table); row != kNoRow) {
656
1/1
✓ Branch 1 taken 566 times.
566 rows.push_back(row);
657 }
658 }
659
1/1
✓ Branch 1 taken 331 times.
331 m_store.CloneRows(table.table, rows, table.first, block.copies);
660 331 }
661
1/1
✓ Branch 1 taken 50 times.
50 CloneArrays(block, sources);
662
663 50 std::vector<std::pair<uint32_t, std::string>> owned;
664
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 50 times.
175 for (uint32_t index = 0; index < sources.size(); ++index) {
665 125 const auto [first, last] = m_fileOwners.equal_range(sources[index]);
666
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 125 times.
126 for (auto owner = first; owner != last; ++owner) {
667
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (uint32_t copy = 0; copy < block.copies; ++copy) {
668
1/1
✓ Branch 2 taken 1 time.
1 owned.emplace_back(block.Prim(copy, index), owner->second);
669 }
670 }
671 }
672
3/3
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 50 times.
51 for (const auto& [prim, path] : owned) {
673
1/1
✓ Branch 1 taken 1 time.
1 OwnFile(prim, path);
674 }
675
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 49 times.
50 if (desc.moveSource) {
676
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 for (const uint32_t prim : std::views::reverse(sources)) {
677
1/1
✓ Branch 1 taken 1 time.
1 RemovePrim(prim);
678 }
679 }
680
1/1
✓ Branch 2 taken 50 times.
50 m_paths.InsertClones(std::move(pathShape), paths, block.firstPrim);
681
2/2
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 50 times.
219 for (uint32_t prim = block.firstPrim; prim < block.firstPrim + block.PrimCount(); ++prim) {
682
1/1
✓ Branch 1 taken 169 times.
169 Logged(prim, true);
683 }
684 50 ++m_epoch;
685 50 return block;
686 150 }
687
688 50 void Structure::CloneArrays(const PrimBlock& block, std::span<const uint32_t> sources) {
689 50 m_store.Flush();
690 50 std::unordered_map<uint32_t, uint32_t> position;
691
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 50 times.
175 for (uint32_t i = 0; i < sources.size(); ++i) {
692
1/1
✓ Branch 1 taken 125 times.
125 position[sources[i]] = i;
693 }
694
2/2
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 50 times.
381 for (const BlockTable& table : block.tables) {
695 331 std::vector<uint32_t> rows;
696
2/2
✓ Branch 0 taken 1169 times.
✓ Branch 1 taken 331 times.
1500 for (const uint32_t prim : sources) {
697
3/3
✓ Branch 1 taken 1169 times.
✓ Branch 3 taken 566 times.
✓ Branch 4 taken 603 times.
1169 if (const uint32_t row = m_prims.FindRow(prim, table.table); row != kNoRow) {
698
1/1
✓ Branch 1 taken 566 times.
566 rows.push_back(row);
699 }
700 }
701
1/1
✓ Branch 1 taken 331 times.
331 CloneTableArrays(table.table, rows, table.first, table.perCopy, block, position);
702 331 }
703
1/1
✓ Branch 1 taken 50 times.
50 CloneInstances(block, sources, position);
704 50 }
705
706 50 void Structure::CloneInstances(const PrimBlock& block, std::span<const uint32_t> sources,
707 const std::unordered_map<uint32_t, uint32_t>& position) {
708
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 50 times.
175 for (uint32_t i = 0; i < sources.size(); ++i) {
709 125 const uint64_t apis = m_prims.Record(sources[i]).apis;
710 125 std::vector<uint32_t> copies;
711
2/2
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 125 times.
294 for (uint32_t copy = 0; copy < block.copies; ++copy) {
712
1/1
✓ Branch 1 taken 169 times.
169 copies.push_back(block.Prim(copy, i));
713 }
714
2/2
✓ Branch 0 taken 2750 times.
✓ Branch 1 taken 125 times.
2875 for (const SchemaId api : kApiSchemas) {
715
6/6
✓ Branch 1 taken 2750 times.
✓ Branch 3 taken 250 times.
✓ Branch 4 taken 2500 times.
✓ Branch 6 taken 250 times.
✓ Branch 8 taken 246 times.
✓ Branch 9 taken 4 times.
2750 if (!IsMultipleApi(api) || !HasApi(apis, api)) {
716 2746 continue;
717 }
718
1/1
✓ Branch 1 taken 4 times.
4 const std::span<const ApiInstance> listed = m_prims.Instances(sources[i], api);
719
1/1
✓ Branch 1 taken 4 times.
4 const std::vector<ApiInstance> instances(listed.begin(), listed.end());
720
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 for (const ApiInstance& entry : std::views::reverse(instances)) {
721
1/1
✓ Branch 1 taken 7 times.
7 const BlockTable rows = m_prims.ApplyInstance(copies, api, entry.instance);
722 7 const uint32_t source = entry.row;
723
1/1
✓ Branch 1 taken 7 times.
7 m_store.CloneRows(api, std::span(&source, 1), rows.first, block.copies);
724
1/1
✓ Branch 1 taken 7 times.
7 m_prims.StageLinks(copies, api);
725
1/1
✓ Branch 1 taken 7 times.
7 CloneTableArrays(api, std::span(&source, 1), rows.first, 1, block, position);
726 }
727 4 }
728 125 }
729 50 }
730
731 338 void Structure::CloneTableArrays(SchemaId table, std::span<const uint32_t> rows, uint32_t first,
732 uint32_t perCopy, const PrimBlock& block,
733 const std::unordered_map<uint32_t, uint32_t>& position) {
734
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1543 times.
✓ Branch 2 taken 1205 times.
✓ Branch 3 taken 338 times.
3086 for (uint32_t index = 0; index < Info(table).columnCount; ++index) {
735
2/2
✓ Branch 1 taken 829 times.
✓ Branch 2 taken 376 times.
1205 const ColumnRef column{table, &Columns(table)[index], index};
736
2/2
✓ Branch 0 taken 829 times.
✓ Branch 1 taken 376 times.
1205 if (!IsArray(column.column->type)) {
737 829 continue;
738 }
739 376 ArrayPool& arrays = m_store.Arrays(column.column->type);
740
2/2
✓ Branch 0 taken 491 times.
✓ Branch 1 taken 376 times.
867 for (uint32_t j = 0; j < rows.size(); ++j) {
741 491 const ArrayRef array = m_store.Array(column, rows[j]);
742
2/2
✓ Branch 0 taken 369 times.
✓ Branch 1 taken 122 times.
491 if (array.address == 0) {
743 369 continue;
744 }
745 122 const bool exclusive = m_store.Exclusive(column, rows[j]);
746 122 std::vector<uint32_t> targets;
747
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 109 times.
122 if (column.column->type == ColumnType::PrimArray) {
748
2/2
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
39 targets = Items<uint32_t>(arrays.Read(array));
749 }
750 122 const bool inside = std::ranges::any_of(
751 13 targets, [&](uint32_t target) { return position.contains(target); });
752
2/2
✓ Branch 1 taken 169 times.
✓ Branch 2 taken 122 times.
291 for (uint32_t copy = 0; copy < block.copies; ++copy) {
753 169 const uint32_t row = first + copy * perCopy + j;
754
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 155 times.
169 if (inside) {
755
1/1
✓ Branch 1 taken 14 times.
14 std::vector<uint32_t> rebased = targets;
756
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 14 times.
30 for (uint32_t& target : rebased) {
757
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 2 times.
16 if (const auto at = position.find(target); at != position.end()) {
758 14 target = block.Prim(copy, at->second);
759 }
760 }
761
1/1
✓ Branch 1 taken 14 times.
14 MarkTargets(rebased);
762
1/1
✓ Branch 1 taken 14 times.
14 const ArrayRef owned = arrays.Allocate(uint32_t(rebased.size()));
763
1/1
✓ Branch 1 taken 14 times.
14 arrays.Write(owned, std::as_bytes(std::span(rebased)));
764
1/1
✓ Branch 1 taken 14 times.
14 m_store.SetArray(column, row, owned);
765
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 154 times.
169 } else if (exclusive) {
766
1/1
✓ Branch 1 taken 1 time.
1 const ArrayRef owned = arrays.Allocate(array.count);
767
1/1
✓ Branch 1 taken 1 time.
1 const uint64_t bytes = uint64_t(array.count) * arrays.ItemBytes();
768
1/1
✓ Branch 1 taken 1 time.
1 device::Queue& queue = m_store.GetDevice().GetQueue();
769
1/1
✓ Branch 1 taken 1 time.
1 device::CommandEncoder encoder = queue.CreateEncoder();
770
4/4
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
1 encoder.Copy(arrays.Range(array).Slice(0, bytes),
771
1/1
✓ Branch 1 taken 1 time.
1 arrays.Range(owned).Slice(0, bytes));
772
2/2
✓ Branch 1 taken 1 time.
✓ Branch 4 taken 1 time.
1 queue.Submit(encoder.Finish());
773
1/1
✓ Branch 1 taken 1 time.
1 m_store.SetArray(column, row, owned);
774 1 } else {
775
2/2
✓ Branch 1 taken 154 times.
✓ Branch 4 taken 154 times.
154 m_store.SetArray(column, row, arrays.Share(array));
776 }
777 }
778 122 }
779 }
780 338 }
781
782 7 void Structure::AddFile(std::string path, FileBytes bytes, std::optional<uint32_t> owner) {
783
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!bytes) {
784
0/1
✗ Branch 2 not taken.
✗ throw Error("Adding file \"{}\": it has no bytes", path);
785 }
786
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
7 if (owner && !m_prims.Alive(*owner)) {
787
0/1
✗ Branch 2 not taken.
✗ throw Error("Prim {} was removed", *owner);
788 }
789 7 m_files[path].bytes = std::move(bytes);
790
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 if (owner) {
791 4 OwnFile(*owner, path);
792 }
793 7 }
794
795 ✗ void Structure::RemoveFile(std::string_view path) {
796 ✗ const auto found = m_files.find(path);
797
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (found == m_files.end()) {
798 ✗ return;
799 }
800 ✗ m_files.erase(found);
801 ✗ std::erase_if(m_fileOwners, [&](const auto& owner) { return owner.second == path; });
802 }
803
804 24 FileBytes Structure::FindFile(std::string_view path) const {
805 24 const auto found = m_files.find(path);
806
3/4
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
24 return found == m_files.end() ? nullptr : found->second.bytes;
807 }
808
809 10 std::vector<std::pair<std::string, FileBytes>> Structure::OwnedFiles(uint32_t prim) const {
810 10 std::vector<std::pair<std::string, FileBytes>> files;
811 10 const auto [first, last] = m_fileOwners.equal_range(prim);
812
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 for (auto owner = first; owner != last; ++owner) {
813
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if (const auto file = m_files.find(owner->second); file != m_files.end()) {
814
0/1
✗ Branch 2 not taken.
✗ files.emplace_back(owner->second, file->second.bytes);
815 }
816 }
817 10 return files;
818 ✗ }
819
820 5 void Structure::OwnFile(uint32_t prim, const std::string& path) {
821 5 ++m_files.at(path).owners;
822 5 m_fileOwners.emplace(prim, path);
823 5 }
824
825 241 void Structure::ReleaseFiles(uint32_t prim) {
826 241 const auto [first, last] = m_fileOwners.equal_range(prim);
827
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 241 times.
243 for (auto owner = first; owner != last; ++owner) {
828 2 const auto file = m_files.find(owner->second);
829
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
2 if (file != m_files.end() && --file->second.owners == 0) {
830 1 m_files.erase(file);
831 }
832 }
833 241 m_fileOwners.erase(first, last);
834 241 }
835
836 }
837