chrono_batch_render/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
121 / 134
90.3%
Branches:
118 / 164
72.0%
Functions:
15 / 15
100.0%

stage/schema/runtime.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 #include "chrono_batch_render/stage/schema/runtime.h"
3
4 #include <array>
5 #include <atomic>
6 #include <deque>
7 #include <memory>
8 #include <mutex>
9
10 #include "chrono_batch_render/util/error.h"
11 #include "chrono_batch_render/util/log.h"
12
13 namespace chrono::batch_render {
14
15 namespace {
16
17 constexpr uint32_t kMaxRuntimeSchemas = 4096;
18
19 struct RuntimeSchema {
20 RuntimeSchemaDesc desc;
21 std::deque<std::string> strings;
22 SchemaInfo info{};
23 std::vector<ColumnInfo> columns;
24 std::vector<SchemaId> slots;
25 std::vector<RuntimeValue> fallbacks;
26 std::vector<RuntimeInheritance> inherited;
27
28 279 std::string_view Keep(std::string text) {
29 279 return strings.emplace_back(std::move(text));
30 }
31 };
32
33 struct Registry {
34 std::mutex mutex;
35 std::array<std::unique_ptr<RuntimeSchema>, kMaxRuntimeSchemas> schemas;
36 std::atomic<uint32_t> count{0};
37 };
38
39 1711325 Registry& RuntimeRegistry() {
40
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1711319 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
1711325 static Registry registry;
41 1711325 return registry;
42 }
43
44 8286 const RuntimeSchema& Lookup(SchemaId schema) {
45 8286 const uint32_t index = uint32_t(schema) - uint32_t(SchemaId::Count);
46 8286 Registry& registry = RuntimeRegistry();
47
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8286 times.
8286 if (index >= registry.count.load(std::memory_order_acquire)) {
48
0/1
✗ Branch 2 not taken.
✗ throw Error("No schema {}", uint32_t(schema));
49 }
50 8286 return *registry.schemas[index];
51 }
52
53 131 bool Accepts(ColumnType type, const RuntimeValue& value) {
54
5/8
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 54 times.
✗ Branch 7 not taken.
131 switch (type) {
55 8 case ColumnType::Bool:
56 8 return std::holds_alternative<bool>(value);
57 24 case ColumnType::Int:
58 24 case ColumnType::Enum:
59 24 return std::holds_alternative<int32_t>(value);
60 24 case ColumnType::Float:
61 24 return std::holds_alternative<float>(value);
62 ✗ case ColumnType::Float2:
63 ✗ return std::holds_alternative<Float2>(value);
64 21 case ColumnType::Float3:
65 21 return std::holds_alternative<Float3>(value);
66 ✗ case ColumnType::Float4:
67 ✗ return std::holds_alternative<Float4>(value);
68 54 case ColumnType::Token:
69 54 case ColumnType::Asset:
70 54 return std::holds_alternative<std::string>(value);
71 default:
72 return false;
73 }
74 }
75
76 9 bool Same(const RuntimeSchemaDesc& a, const RuntimeSchemaDesc& b) {
77
4/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 time.
9 if (a.name != b.name || a.base != b.base || a.doc != b.doc ||
78
4/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
18 a.columns.size() != b.columns.size() || a.inherited != b.inherited) {
79 1 return false;
80 }
81
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 6 times.
20 for (size_t i = 0; i < a.columns.size(); ++i) {
82
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 const RuntimeColumnDesc& x = a.columns[i];
83 14 const RuntimeColumnDesc& y = b.columns[i];
84
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (x.field != y.field || x.type != y.type || x.role != y.role ||
85
4/6
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
14 x.fallback != y.fallback || x.displayName != y.displayName || x.group != y.group ||
86
4/8
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
26 x.doc != y.doc || x.minimum != y.minimum || x.maximum != y.maximum) {
87 2 return false;
88 }
89 }
90 return true;
91 }
92
93 38 void Check(const RuntimeSchemaDesc& desc) {
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
38 if (desc.name.empty()) {
95
0/2
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ throw Error("A runtime schema needs a name");
96 }
97
3/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 37 times.
✓ Branch 4 taken 1 time.
38 if (desc.base == SchemaId::None || IsApi(desc.base) ||
98
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
74 Info(desc.base).kind == SchemaKind::Api) {
99
1/1
✓ Branch 2 taken 1 time.
1 throw Error("Runtime schema {} needs a typed base schema", desc.name);
100 }
101
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 35 times.
97 for (size_t i = 0; i < desc.columns.size(); ++i) {
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 const RuntimeColumnDesc& column = desc.columns[i];
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (column.field.empty()) {
104
0/1
✗ Branch 2 not taken.
✗ throw Error("{}'s input {} has no name", desc.name, i);
105 }
106
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 61 times.
62 if (FindColumn(desc.base, column.field)) {
107 2 throw Error("{}'s input {} hides {}'s attribute of that name", desc.name, column.field,
108
2/3
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 1 time.
2 Info(desc.base).name);
109 }
110
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 61 times.
112 for (size_t j = 0; j < i; ++j) {
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
51 if (desc.columns[j].field == column.field) {
112
0/1
✗ Branch 2 not taken.
✗ throw Error("{} has two inputs called {}", desc.name, column.field);
113 }
114 }
115
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 60 times.
61 if (!Accepts(column.type, column.fallback)) {
116
1/1
✓ Branch 2 taken 1 time.
1 throw Error("{}'s input {} has a fallback of the wrong type", desc.name, column.field);
117 }
118 }
119
2/2
✓ Branch 1 taken 70 times.
✓ Branch 2 taken 35 times.
105 for (const auto& [field, value] : desc.inherited) {
120 70 const ColumnRef column = FindColumn(desc.base, field);
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (!column) {
122 ✗ throw Error("{} sets {}, which {} doesn't have", desc.name, field,
123
0/3
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Info(desc.base).name);
124 }
125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (!Accepts(column.column->type, value)) {
126
0/1
✗ Branch 2 not taken.
✗ throw Error("{} sets {} to a value of the wrong type", desc.name, field);
127 }
128 }
129 35 }
130
131 27 std::unique_ptr<RuntimeSchema> Build(const RuntimeSchemaDesc& desc, SchemaId id) {
132 27 auto schema = std::make_unique<RuntimeSchema>();
133
1/1
✓ Branch 1 taken 27 times.
27 schema->desc = desc;
134
3/3
✓ Branch 1 taken 27 times.
✓ Branch 3 taken 54 times.
✓ Branch 4 taken 27 times.
81 for (const SchemaId slot : Slots(desc.base)) {
135
1/1
✓ Branch 1 taken 54 times.
54 schema->slots.push_back(slot);
136 }
137
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 5 times.
27 if (!desc.columns.empty()) {
138
1/1
✓ Branch 1 taken 22 times.
22 schema->slots.push_back(id);
139 }
140
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 27 times.
72 for (const RuntimeColumnDesc& column : desc.columns) {
141
2/2
✓ Branch 1 taken 45 times.
✓ Branch 4 taken 45 times.
135 schema->columns.push_back({
142
2/2
✓ Branch 1 taken 45 times.
✓ Branch 4 taken 45 times.
45 .field = schema->Keep(column.field),
143
2/2
✓ Branch 1 taken 45 times.
✓ Branch 4 taken 45 times.
90 .name = schema->Keep("inputs:" + column.field),
144 45 .type = column.type,
145
1/1
✓ Branch 1 taken 45 times.
45 .role = column.role,
146 .flags = ColumnFlags::None,
147 .offset = 0,
148 .api = SchemaId::None,
149
2/2
✓ Branch 1 taken 45 times.
✓ Branch 4 taken 45 times.
90 .doc = schema->Keep(column.doc),
150 .unit = Unit::None,
151 .tokens = {},
152
2/2
✓ Branch 1 taken 45 times.
✓ Branch 4 taken 45 times.
90 .group = schema->Keep(column.group),
153 .displayName =
154
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
✓ Branch 3 taken 45 times.
✓ Branch 6 taken 45 times.
90 schema->Keep(column.displayName.empty() ? column.field : column.displayName),
155 45 .minimum = column.minimum,
156
1/1
✓ Branch 1 taken 45 times.
45 .maximum = column.maximum,
157 });
158
1/1
✓ Branch 1 taken 45 times.
45 schema->fallbacks.push_back(column.fallback);
159 }
160
3/3
✓ Branch 1 taken 64 times.
✓ Branch 3 taken 64 times.
✓ Branch 4 taken 27 times.
91 for (const auto& [field, value] : desc.inherited) {
161
1/1
✓ Branch 1 taken 64 times.
64 const ColumnRef column = FindColumn(desc.base, field);
162
1/1
✓ Branch 1 taken 64 times.
128 schema->inherited.push_back({column.schema, column.index, value});
163 }
164
2/3
✓ Branch 1 taken 27 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 27 times.
81 schema->info = {
165
2/2
✓ Branch 1 taken 27 times.
✓ Branch 4 taken 27 times.
27 .name = schema->Keep(desc.name),
166 27 .base = desc.base,
167 .kind = SchemaKind::Concrete,
168 .firstApi = 0,
169 .apiCount = 0,
170 .firstColumn = 0,
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 .columnCount = uint32_t(desc.columns.size()),
172 .dataBytes = 0,
173 .firstSlot = 0,
174 27 .slotCount = uint32_t(schema->slots.size()),
175 .api = kNoApi,
176
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
✓ Branch 3 taken 27 times.
54 .apis = Info(desc.base).apis,
177 .firstTarget = 0,
178 .targetCount = 0,
179
2/2
✓ Branch 1 taken 27 times.
✓ Branch 4 taken 27 times.
54 .doc = schema->Keep(desc.doc),
180 };
181 27 return schema;
182 128 }
183
184 }
185
186 1703004 uint32_t SchemaCount() {
187 1703004 return uint32_t(SchemaId::Count) + RuntimeRegistry().count.load(std::memory_order_acquire);
188 }
189
190 5843 const SchemaInfo& RuntimeInfo(SchemaId schema) {
191 5843 return Lookup(schema).info;
192 }
193
194 1937 Span<const ColumnInfo> RuntimeColumns(SchemaId schema) {
195 1937 const RuntimeSchema& found = Lookup(schema);
196 1937 return Span<const ColumnInfo>(found.columns.data(), found.columns.size());
197 }
198
199 415 Span<const SchemaId> RuntimeSlots(SchemaId schema) {
200 415 const RuntimeSchema& found = Lookup(schema);
201 415 return Span<const SchemaId>(found.slots.data(), found.slots.size());
202 }
203
204 54 const RuntimeValue& RuntimeFallback(SchemaId schema, uint32_t column) {
205 54 return Lookup(schema).fallbacks.at(column);
206 }
207
208 28 Span<const RuntimeInheritance> RuntimeInherited(SchemaId schema) {
209 28 const RuntimeSchema& found = Lookup(schema);
210 28 return Span<const RuntimeInheritance>(found.inherited.data(), found.inherited.size());
211 }
212
213 46 SchemaId FindSchemaByName(std::string_view name) {
214
2/2
✓ Branch 1 taken 3827 times.
✓ Branch 2 taken 33 times.
3860 for (uint32_t index = SchemaCount(); index-- > 0;) {
215
4/4
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 3673 times.
✓ Branch 2 taken 3814 times.
✓ Branch 3 taken 13 times.
7654 if (Info(SchemaId(index)).name == name) {
216 return SchemaId(index);
217 }
218 }
219 return SchemaId::None;
220 }
221
222 38 SchemaId RegisterSchema(const RuntimeSchemaDesc& desc, bool replace) {
223 38 Check(desc);
224 35 Registry& registry = RuntimeRegistry();
225 35 const std::lock_guard lock(registry.mutex);
226
3/3
✓ Branch 1 taken 35 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 25 times.
35 if (const SchemaId existing = FindSchemaByName(desc.name); existing != SchemaId::None) {
227
5/5
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 9 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 6 times.
10 if (IsRuntime(existing) && Same(Lookup(existing).desc, desc)) {
228 return existing;
229 }
230
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4 if (!replace || !IsRuntime(existing)) {
231
1/1
✓ Branch 2 taken 2 times.
2 throw Error("A schema called {} already exists", desc.name);
232 }
233 }
234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 const uint32_t index = registry.count.load(std::memory_order_relaxed);
235
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (index >= kMaxRuntimeSchemas) {
236 ✗ throw Error("Registering {} would pass the limit of {} runtime schemas", desc.name,
237
0/1
✗ Branch 2 not taken.
✗ kMaxRuntimeSchemas);
238 }
239 27 const SchemaId id = SchemaId(uint32_t(SchemaId::Count) + index);
240
1/1
✓ Branch 1 taken 27 times.
27 registry.schemas[index] = Build(desc, id);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 registry.count.store(index + 1, std::memory_order_release);
242 54 Log(LogLevel::Debug, "registered schema {}, a {} with {} inputs", desc.name,
243
2/3
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
✓ Branch 3 taken 27 times.
56 Info(desc.base).name, desc.columns.size());
244 27 return id;
245 33 }
246
247 }
248