chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
295 / 342
86.3%
Branches:
258 / 373
69.2%
Functions:
47 / 54
87.0%

import/importer.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/import/importer.h"
3
4 #include <algorithm>
5 #include <atomic>
6 #include <chrono>
7 #include <condition_variable>
8 #include <exception>
9 #include <fstream>
10 #include <iterator>
11 #include <map>
12 #include <mutex>
13 #include <optional>
14 #include <span>
15 #include <thread>
16 #include <utility>
17
18 #include <fmt/format.h>
19
20 #include "chrono_batch_render/import/gltf.h"
21 #include "chrono_batch_render/import/obj.h"
22 #include "chrono_batch_render/import/usd.h"
23 #include "chrono_batch_render/scene/scene.h"
24 #include "chrono_batch_render/scene/scene_impl.h"
25 #include "chrono_batch_render/util/error.h"
26 #include "chrono_batch_render/util/log.h"
27 #include "chrono_batch_render/util/text.h"
28 #include "chrono_batch_render/util/thread_pool.h"
29
30 namespace chrono::batch_render {
31 namespace {
32
33 using Importers = std::vector<std::shared_ptr<Importer>>;
34
35 struct Registry {
36 std::mutex mutex;
37 std::map<std::string, Importers, std::less<>> importers;
38 };
39
40 4 ThreadPool& Readers() {
41
5/7
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 9 taken 2 times.
6 static ThreadPool pool(std::clamp(std::thread::hardware_concurrency() / 2, 1u, 4u));
42 4 return pool;
43 }
44
45 69 Registry& Registered() {
46 3 static Registry registry = [] {
47
1/1
✓ Branch 1 taken 3 times.
3 std::map<std::string, Importers, std::less<>> importers;
48 12 for (const std::shared_ptr<Importer>& importer :
49
6/13
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
✓ Branch 7 taken 3 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
30 {CreateGltfImporter(), CreateObjImporter(), CreateUsdImporter()}) {
50
3/3
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 21 times.
✓ Branch 4 taken 9 times.
30 for (const std::string& extension : importer->Extensions()) {
51
3/3
✓ Branch 1 taken 21 times.
✓ Branch 4 taken 21 times.
✓ Branch 7 taken 21 times.
42 importers[Lowercase(extension)].push_back(importer);
52 9 }
53 12 }
54 3 return Registry{.importers = std::move(importers)};
55
4/5
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 66 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 3 times.
72 }();
56 69 return registry;
57 }
58
59 2 const char* TypeName(const ImportValue& value) {
60 2 constexpr const char* kNames[] = {"a bool", "an integer", "a number", "text"};
61 2 return kNames[value.index()];
62 }
63
64 17 ImportValues ResolveOptions(const Importer& importer, const ImportValues& given,
65 const std::string& file) {
66 17 const std::vector<ImportOption> options = importer.Options();
67 17 ImportValues values;
68
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 17 times.
31 for (const ImportOption& option : options) {
69
2/2
✓ Branch 1 taken 14 times.
✓ Branch 4 taken 14 times.
14 values[option.name] = option.value;
70 }
71
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 13 times.
19 for (const auto& [name, value] : given) {
72 6 const auto found = std::ranges::find(options, name, &ImportOption::name);
73
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (found == options.end()) {
74 2 std::vector<std::string> names;
75
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (const ImportOption& option : options) {
76
1/1
✓ Branch 1 taken 4 times.
4 names.push_back(option.name);
77 }
78 2 throw Error("Importing \"{}\": the {} importer has no \"{}\" option; it offers {}",
79
4/6
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
4 file, importer.Name(), name, names.empty() ? "none" : Join(names));
80 2 }
81
1/1
✓ Branch 1 taken 4 times.
4 ImportValue used = value;
82
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 if (std::holds_alternative<int64_t>(value) &&
83
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::holds_alternative<double>(found->value)) {
84 2 used = double(std::get<int64_t>(value));
85 }
86
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if (used.index() != found->value.index()) {
87 1 throw Error("Importing \"{}\": the {} importer's \"{}\" option is {}, not {}", file,
88
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 importer.Name(), name, TypeName(found->value), TypeName(value));
89 }
90
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (const std::string* text = std::get_if<std::string>(&used);
91
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
2 text && !found->choices.empty() &&
92
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 std::ranges::find(found->choices, *text) == found->choices.end()) {
93 1 throw Error(
94 "Importing \"{}\": the {} importer's \"{}\" option is one of {}, not \"{}\"", file,
95
3/3
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 8 taken 1 time.
2 importer.Name(), name, Join(found->choices), *text);
96 }
97
1/1
✓ Branch 1 taken 2 times.
2 values[name] = std::move(used);
98 4 }
99 13 return values;
100 17 }
101
102 38 std::string NormalName(std::string_view name) {
103
1/1
✓ Branch 2 taken 38 times.
152 return std::filesystem::path(name).lexically_normal().generic_string();
104 }
105
106 }
107
108 7 std::filesystem::path ImportSource::Locate(std::string_view name) const {
109
3/3
✓ Branch 2 taken 7 times.
✓ Branch 5 taken 7 times.
✓ Branch 8 taken 7 times.
28 return (file.parent_path() / std::filesystem::path(name)).lexically_normal();
110 }
111
112 38 FileBytes ImportSource::Find(std::string_view name) const {
113 76 const auto found = files.find(NormalName(name));
114
3/4
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
38 return found == files.end() ? nullptr : found->second;
115 }
116
117 15 FileBytes ImportSource::Read(std::string_view name) const {
118
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 4 times.
15 if (FileBytes bytes = Find(name)) {
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 return bytes;
120 11 }
121 4 const std::filesystem::path located = Locate(name);
122
1/1
✓ Branch 1 taken 4 times.
4 std::ifstream stream(located, std::ios::binary);
123
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if (!stream) {
124
2/2
✓ Branch 2 taken 1 time.
✓ Branch 5 taken 1 time.
2 throw Error("Reading \"{}\": it isn't in memory and can't be opened", located.string());
125 }
126
1/1
✓ Branch 1 taken 3 times.
3 const std::vector<char> text((std::istreambuf_iterator<char>(stream)),
127
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
3 std::istreambuf_iterator<char>());
128
1/1
✓ Branch 1 taken 3 times.
3 const auto bytes = std::as_bytes(std::span(text));
129
1/1
✓ Branch 1 taken 3 times.
3 return std::make_shared<const std::vector<std::byte>>(bytes.begin(), bytes.end());
130 7 }
131
132 10 FileBytes ImportSource::ReadEntry() const {
133
1/1
✓ Branch 2 taken 9 times.
39 return Read(file.filename().generic_string());
134 }
135
136 17 Importer::~Importer() = default;
137
138 10 std::vector<ImportOption> Importer::Options() const {
139 10 return {};
140 }
141
142 class ImportState : public std::enable_shared_from_this<ImportState> {
143 public:
144 18 ImportState(std::string reader = {}, std::string source = {}, std::string path = {},
145 const ImportDesc& desc = {})
146 18 : m_reader(std::move(reader)), m_source(std::move(source)), m_path(std::move(path)),
147
1/1
✓ Branch 1 taken 18 times.
18 m_callback(desc.progress), m_up(desc.up), m_metersPerUnit(desc.metersPerUnit),
148
1/3
✓ Branch 2 taken 18 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
36 m_options(desc.options) {}
149
150 8 std::optional<UpAxis> Up() const {
151 8 return m_up;
152 }
153
154 8 std::optional<float> MetersPerUnit() const {
155 8 return m_metersPerUnit;
156 }
157
158 3 std::optional<ImportValue> Option(std::string_view name) const {
159 3 const auto found = m_options.find(name);
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 return found == m_options.end() ? std::nullopt : std::optional(found->second);
161 }
162
163 55 void Report(std::string_view stage, uint64_t done, uint64_t total) {
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
55 if (m_cancelled) {
165
0/1
✗ Branch 2 not taken.
✗ throw CancelledFailure();
166 }
167
1/1
✓ Branch 2 taken 55 times.
55 ImportProgress progress{std::string(stage), done, total};
168 55 {
169
1/1
✓ Branch 1 taken 55 times.
55 const std::lock_guard lock(m_mutex);
170
1/1
✓ Branch 1 taken 55 times.
55 m_progress = progress;
171 ✗ }
172
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 49 times.
55 if (m_callback) {
173
1/1
✓ Branch 1 taken 6 times.
6 m_callback(progress);
174 }
175 55 }
176
177 38119 bool Cancelled() const {
178 38119 return m_cancelled;
179 }
180
181 ✗ ImportProgress Progress() const {
182 ✗ const std::lock_guard lock(m_mutex);
183
0/1
✗ Branch 1 not taken.
✗ return m_progress;
184 ✗ }
185
186 2 void Cancel() {
187
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (Finished()) {
188 return;
189 }
190 2 m_cancelled = true;
191
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (Readers().Cancel(this)) {
192
0/5
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Finish({}, std::make_exception_ptr(CancelledFailure()));
193 }
194 2 Log(LogLevel::Info, "cancelled importing {} at \"{}\"", m_source, m_path);
195 }
196
197 18 void Read(const ImportReader& reader) {
198 18 const auto start = std::chrono::steady_clock::now();
199 18 try {
200
2/2
✓ Branch 1 taken 18 times.
✓ Branch 4 taken 18 times.
18 ImportContext context(shared_from_this());
201
1/1
✓ Branch 1 taken 15 times.
18 ImportPlan plan = reader.read(context);
202
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (m_cancelled) {
203
1/1
✓ Branch 2 taken 2 times.
2 throw CancelledFailure();
204 }
205 13 m_readMs = Milliseconds(start);
206 26 Log(LogLevel::Debug, "{} read {} into {} prims in {:.1f} ms", m_reader, m_source,
207
2/2
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
13 plan.PrimCount(), m_readMs);
208
2/5
✓ Branch 2 taken 13 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
28 Finish(std::move(plan), nullptr);
209
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
33 } catch (...) {
210
7/12
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 8 taken 5 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 5 times.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 3 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
10 Finish({}, m_cancelled ? std::make_exception_ptr(CancelledFailure())
211 : std::current_exception());
212 5 }
213 18 }
214
215 3 bool Finished() const {
216 3 const std::lock_guard lock(m_mutex);
217 3 return m_finished;
218 3 }
219
220 ✗ bool Applied() const {
221
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return m_applied;
222 }
223
224 31 Prim Apply(Scene& scene) {
225 31 {
226 31 std::unique_lock lock(m_mutex);
227
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
32 m_done.wait(lock, [this] { return m_finished; });
228 ✗ }
229
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 13 times.
31 if (!m_applied) {
230 18 m_applied = true;
231 18 try {
232
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 13 times.
18 if (m_error) {
233
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
15 std::rethrow_exception(m_error);
234 }
235 13 const auto start = std::chrono::steady_clock::now();
236
1/1
✓ Branch 1 taken 13 times.
13 m_root = m_plan->Apply(scene, m_path);
237 26 Log(LogLevel::Info,
238 "{} imported {} at \"{}\" as {} prims, read in {:.1f} ms and defined in "
239 "{:.1f} ms",
240
2/2
✓ Branch 2 taken 13 times.
✓ Branch 5 taken 13 times.
13 m_reader, m_source, m_path, m_plan->PrimCount(), m_readMs, Milliseconds(start));
241 5 } catch (...) {
242
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 m_error = std::current_exception();
243 5 }
244
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 5 times.
18 m_plan.reset();
245 }
246
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 26 times.
31 if (m_error) {
247
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
10 std::rethrow_exception(m_error);
248 }
249 26 return m_root;
250 }
251
252 private:
253 4 CancelledError CancelledFailure() const {
254 4 return CancelledError("Importing {} at \"{}\": cancelled", m_source, m_path);
255 }
256
257 26 static double Milliseconds(std::chrono::steady_clock::time_point start) {
258 26 return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start)
259 26 .count();
260 }
261
262 18 void Finish(std::optional<ImportPlan> plan, std::exception_ptr error) {
263 18 {
264 18 const std::lock_guard lock(m_mutex);
265 18 m_plan = std::move(plan);
266 18 m_error = std::move(error);
267 18 m_finished = true;
268 18 }
269 18 m_done.notify_all();
270 18 }
271
272 std::string m_reader;
273 std::string m_source;
274 std::string m_path;
275 ImportProgressCallback m_callback;
276 std::optional<UpAxis> m_up;
277 std::optional<float> m_metersPerUnit;
278 ImportValues m_options;
279 std::atomic<bool> m_cancelled = false;
280 mutable std::mutex m_mutex;
281 std::condition_variable m_done;
282 ImportProgress m_progress;
283 bool m_finished = false;
284 std::optional<ImportPlan> m_plan;
285 std::exception_ptr m_error;
286 double m_readMs = 0;
287 bool m_applied = false;
288 Prim m_root;
289 };
290
291 6 CancelledError::~CancelledError() = default;
292
293 ✗ ImportContext::ImportContext() : m_state(std::make_shared<ImportState>()) {}
294
295 18 ImportContext::ImportContext(std::shared_ptr<ImportState> state) : m_state(std::move(state)) {}
296
297 55 void ImportContext::Report(std::string_view stage, uint64_t done, uint64_t total) {
298 55 m_state->Report(stage, done, total);
299 55 }
300
301 8 std::optional<UpAxis> ImportContext::Up() const {
302 8 return m_state->Up();
303 }
304
305 8 std::optional<float> ImportContext::MetersPerUnit() const {
306 8 return m_state->MetersPerUnit();
307 }
308
309 3 std::optional<ImportValue> ImportContext::Option(std::string_view name) const {
310 3 return m_state->Option(name);
311 }
312
313 38119 bool ImportContext::Cancelled() const {
314 38119 return m_state->Cancelled();
315 }
316
317 50 void ImportPlan::Add(std::string path, DefineStep define) {
318 50 m_steps.push_back({std::move(path), std::move(define)});
319 100 }
320
321 3 std::string ImportPlan::AddFile(std::string_view name, FileBytes bytes) {
322 3 static std::atomic<uint64_t> plans = 0;
323
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if (m_id == 0) {
324 2 m_id = ++plans;
325 }
326 3 std::string path = fmt::format("memory://{}/{}", m_id, name);
327
1/1
✓ Branch 1 taken 3 times.
3 m_files.emplace_back(path, std::move(bytes));
328 3 return path;
329 ✗ }
330
331 28 uint32_t ImportPlan::PrimCount() const {
332 28 return uint32_t(m_steps.size());
333 }
334
335 13 Prim ImportPlan::Apply(Scene& scene, std::string_view root) const {
336 13 bool definedRoot = false;
337 13 try {
338
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 13 times.
60 for (const Step& step : m_steps) {
339
1/1
✓ Branch 1 taken 47 times.
47 step.define(scene, step.path);
340
3/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 34 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
60 definedRoot = definedRoot || step.path == root;
341 }
342
1/1
✓ Branch 1 taken 13 times.
13 const std::optional<Prim> prim = scene.Find(root);
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!prim) {
344
0/1
✗ Branch 2 not taken.
✗ throw Error("Importing at \"{}\": the importer defined nothing there", root);
345 }
346
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 13 times.
16 for (const auto& [path, bytes] : m_files) {
347
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 6 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
9 scene.AddFile(path, bytes, *prim);
348 }
349 13 return *prim;
350 ✗ } catch (...) {
351
0/5
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ if (const std::optional<Prim> prim = scene.Find(root); definedRoot && prim) {
352
0/1
✗ Branch 1 not taken.
✗ scene.Remove(*prim);
353 }
354 ✗ throw;
355 ✗ }
356 }
357
358 1 bool ImportJob::Ready() const {
359
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
1 return !m_state || m_state->Finished();
360 }
361
362 ✗ ImportProgress ImportJob::Progress() const {
363
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return m_state ? m_state->Progress() : ImportProgress{};
364 }
365
366 2 void ImportJob::Cancel() {
367
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (m_state) {
368 2 m_state->Cancel();
369 }
370 2 }
371
372 15 Prim ImportJob::Wait() {
373
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (!m_state) {
374
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Waiting on an import job that was never started");
375 }
376 15 return m_state->Apply(*m_scene);
377 }
378
379 8 void RegisterImporter(std::shared_ptr<Importer> importer) {
380
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!importer) {
381
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("Registering an importer: the importer is null");
382 }
383 8 const std::string name = importer->Name();
384
1/1
✓ Branch 1 taken 8 times.
8 const std::vector<std::string> extensions = importer->Extensions();
385
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (extensions.empty()) {
386
0/1
✗ Branch 2 not taken.
✗ throw Error("Registering the {} importer: it reads no extensions", name);
387 }
388
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 for (const std::string& extension : extensions) {
389
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if (extension.size() < 2 || extension.front() != '.') {
390 ✗ throw Error("Registering the {} importer: \"{}\" isn't an extension like \".obj\"",
391
0/1
✗ Branch 2 not taken.
✗ name, extension);
392 }
393 }
394 8 std::vector<std::pair<std::string, std::string>> joined;
395 8 {
396
1/1
✓ Branch 1 taken 8 times.
8 Registry& registry = Registered();
397
1/1
✓ Branch 1 taken 8 times.
8 const std::lock_guard lock(registry.mutex);
398
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 for (const std::string& extension : extensions) {
399
1/1
✓ Branch 1 taken 8 times.
8 const std::string key = Lowercase(extension);
400
1/1
✓ Branch 1 taken 8 times.
8 Importers& list = registry.importers[key];
401
1/1
✓ Branch 1 taken 8 times.
8 std::erase_if(list, [&](const std::shared_ptr<Importer>& other) {
402
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 time.
8 return other == importer || other->Name() == name;
403 });
404
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
8 if (!list.empty()) {
405
2/2
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 3 times.
6 joined.emplace_back(key, list.front()->Name());
406 }
407
1/1
✓ Branch 1 taken 8 times.
8 list.insert(list.begin(), importer);
408 8 }
409 ✗ }
410
3/3
✓ Branch 1 taken 3 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 8 times.
11 for (const auto& [extension, previous] : joined) {
411
1/1
✓ Branch 1 taken 3 times.
3 Log(LogLevel::Info,
412 "the {} importer reads \"{}\" files by default now, ahead of the {} importer", name,
413 extension, previous);
414 }
415
2/2
✓ Branch 1 taken 8 times.
✓ Branch 4 taken 8 times.
8 Log(LogLevel::Debug, "registered the {} importer for {}", name, Join(extensions));
416 8 }
417
418 7 void UnregisterImporter(std::string_view extension, std::string_view name) {
419 7 std::vector<std::string> removed;
420 7 {
421
1/1
✓ Branch 1 taken 7 times.
7 Registry& registry = Registered();
422
1/1
✓ Branch 1 taken 7 times.
7 const std::lock_guard lock(registry.mutex);
423
1/1
✓ Branch 1 taken 7 times.
14 const auto found = registry.importers.find(Lowercase(extension));
424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (found == registry.importers.end()) {
425 ✗ return;
426 }
427
1/1
✓ Branch 1 taken 7 times.
7 std::erase_if(found->second, [&](const std::shared_ptr<Importer>& importer) {
428
6/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 2 times.
13 if (!name.empty() && importer->Name() != name) {
429 return false;
430 }
431 14 removed.push_back(importer->Name());
432 7 return true;
433 });
434
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if (found->second.empty()) {
435 5 registry.importers.erase(found);
436 }
437 ✗ }
438
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 for (const std::string& importer : removed) {
439
1/1
✓ Branch 1 taken 7 times.
7 Log(LogLevel::Debug, "unregistered the {} importer for \"{}\" files", importer,
440
1/1
✓ Branch 1 taken 7 times.
14 Lowercase(extension));
441 }
442 7 }
443
444 30 std::shared_ptr<Importer> FindImporter(std::string_view extension) {
445 30 const std::vector<std::shared_ptr<Importer>> importers = FindImporters(extension);
446
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
30 return importers.empty() ? nullptr : importers.front();
447 30 }
448
449 49 std::vector<std::shared_ptr<Importer>> FindImporters(std::string_view extension) {
450 49 Registry& registry = Registered();
451 49 const std::lock_guard lock(registry.mutex);
452
1/1
✓ Branch 1 taken 49 times.
98 const auto found = registry.importers.find(Lowercase(extension));
453
3/3
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 43 times.
✓ Branch 3 taken 43 times.
49 return found == registry.importers.end() ? Importers{} : found->second;
454 49 }
455
456 5 std::vector<std::string> ImporterExtensions() {
457 5 Registry& registry = Registered();
458 5 const std::lock_guard lock(registry.mutex);
459 5 std::vector<std::string> extensions;
460
3/3
✓ Branch 1 taken 35 times.
✓ Branch 3 taken 35 times.
✓ Branch 4 taken 5 times.
40 for (const auto& [extension, importers] : registry.importers) {
461
1/1
✓ Branch 1 taken 35 times.
35 extensions.push_back(extension);
462 }
463 5 return extensions;
464 5 }
465
466 ✗ ImportJob Scene::Import(std::string_view path, const std::filesystem::path& file) {
467
0/1
✗ Branch 1 not taken.
✗ return Import(path, ImportSource{.file = file}, {});
468 ✗ }
469
470 ✗ ImportJob Scene::Import(std::string_view path, const std::filesystem::path& file,
471 const ImportDesc& desc) {
472
0/1
✗ Branch 2 not taken.
✗ return Import(path, ImportSource{.file = file}, desc);
473 }
474
475 ✗ ImportJob Scene::Import(std::string_view path, const ImportSource& source) {
476 ✗ return Import(path, source, {});
477 ✗ }
478
479 18 ImportJob Scene::Import(std::string_view path, const ImportSource& source, const ImportDesc& desc) {
480 36 const std::string extension = source.file.extension().string();
481
4/4
✓ Branch 1 taken 18 times.
✓ Branch 4 taken 18 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 12 times.
42 const bool inMemory = source.Find(source.file.filename().generic_string()) != nullptr;
482
1/1
✓ Branch 1 taken 18 times.
18 Log(LogLevel::Info, "importing \"{}\"{} at \"{}\" as a \"{}\" file", source.file.string(),
483
3/3
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 18 times.
37 inMemory ? " from memory" : "", path, extension);
484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 std::shared_ptr<Importer> importer = desc.importer;
485
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (importer) {
486
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ Log(LogLevel::Debug, "the {} importer was given for \"{}\"", importer->Name(),
487
0/2
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✗ source.file.string());
488 } else {
489
2/3
✓ Branch 1 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
36 importer = FindImporter(extension);
490
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 17 times.
18 if (!importer) {
491
1/1
✓ Branch 1 taken 1 time.
1 const std::vector<std::string> extensions = ImporterExtensions();
492 1 throw UnsupportedError(
493 "Importing \"{}\" at \"{}\": no importer reads \"{}\" files; the registered "
494 "importers read {}",
495
1/1
✓ Branch 1 taken 1 time.
2 source.file.string(), path, extension,
496
3/5
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 7 taken 1 time.
✓ Branch 10 taken 1 time.
3 extensions.empty() ? "nothing" : Join(extensions));
497 1 }
498
4/4
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 17 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 1 time.
41 Log(LogLevel::Debug, "the {} importer reads \"{}\" files", importer->Name(), extension);
499 }
500
1/1
✓ Branch 1 taken 17 times.
17 ImportDesc resolved = desc;
501
2/2
✓ Branch 1 taken 17 times.
✓ Branch 4 taken 13 times.
34 resolved.options = ResolveOptions(*importer, desc.options, source.file.string());
502 13 return StartImport(
503 path,
504
4/5
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 13 times.
41 ImportReader{.name = fmt::format("the {} importer", importer->Name()),
505 .read =
506
5/8
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
32 [importer, root = std::string(path), source](ImportContext& context) {
507 13 return importer->Read(root, source, context);
508 }},
509
3/3
✓ Branch 1 taken 13 times.
✓ Branch 4 taken 13 times.
✓ Branch 7 taken 11 times.
41 resolved, fmt::format("\"{}\"", source.file.string()));
510 35 }
511
512 5 ImportJob Scene::Import(std::string_view path, const ImportReader& reader, const ImportDesc& desc) {
513 5 Log(LogLevel::Info, "importing from {} at \"{}\"", reader.name, path);
514
1/1
✓ Branch 2 taken 4 times.
9 return StartImport(path, reader, desc, fmt::format("from {}", reader.name));
515 }
516
517 18 ImportJob Scene::StartImport(std::string_view path, const ImportReader& reader,
518 const ImportDesc& desc, std::string source) {
519
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (!reader.read) {
520
0/1
✗ Branch 2 not taken.
✗ throw Error("Importing at \"{}\": the reader has no read function", path);
521 }
522 18 auto state =
523
1/1
✓ Branch 2 taken 18 times.
18 std::make_shared<ImportState>(reader.name, std::move(source), std::string(path), desc);
524
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
18 if (desc.synchronous) {
525
1/1
✓ Branch 1 taken 16 times.
16 state->Read(reader);
526
1/1
✓ Branch 1 taken 13 times.
16 state->Apply(*this);
527
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 return ImportJob(Ref<Scene>(this), std::move(state));
528 }
529
5/6
✓ Branch 2 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
8 Readers().Add(state.get(), [reader, state] { state->Read(reader); });
530
5/11
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 3 times.
✗ Branch 14 not taken.
13 m_impl->AddCommitStep([this, state] {
531
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ if (!state->Finished()) {
532 return false;
533 }
534
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (!state->Applied()) {
535 ✗ try {
536
0/1
✗ Branch 1 not taken.
✗ state->Apply(*this);
537
0/3
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ } catch (const CancelledError&) {
538 ✗ } catch (const std::exception& error) {
539
0/1
✗ Branch 2 not taken.
✗ Log(LogLevel::Error, "{}", error.what());
540 ✗ }
541 }
542 return true;
543 });
544
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 return ImportJob(Ref<Scene>(this), std::move(state));
545 15 }
546
547 }
548