GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
145 / 153
94.8%
Branches:
139 / 236
58.9%
Functions:
19 / 21
90.5%

scene/edit_history.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file edit_history.cpp
3 /// @brief Recording edits as steps of before and after states, and applying them either way.
4 #include "ernie/scene/edit_history.h"
5
6 #include "ernie/util/text.h"
7
8 #include <algorithm>
9 #include <stdexcept>
10
11 namespace ernie {
12
13 9 void EditHistory::Set(Scene& scene, const Path& path,
14 std::vector<std::pair<std::string, Value>> edits, bool open) {
15 9 const PrimId prim = scene.Require(path);
16 9 Step step{.path = path, .open = open};
17 9 try {
18
3/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 8 times.
17 for (auto& [name, value] : edits) {
19
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 Cell before = Capture(scene, prim, name);
20
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 time.
9 scene.Set(prim, name, std::move(value));
21 8 step.attributes.push_back({name, std::move(before), Capture(scene, prim, name)});
22 9 }
23 1 } catch (...) {
24 // What applied before the rejected edit stays undoable.
25
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (!step.attributes.empty()) {
26
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Push(std::move(step));
27 }
28 1 throw;
29 1 }
30 10 const auto same = [&](const Step& last) {
31
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
4 return !last.structural && last.open && last.path == path &&
32 2 std::ranges::equal(last.attributes, step.attributes, {}, &AttributeEdit::name,
33 2 &AttributeEdit::name);
34 8 };
35
7/10
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
8 if (open && open_ && next_ == steps_.size() && !steps_.empty() && same(steps_.back())) {
36
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (std::size_t i = 0; i < step.attributes.size(); ++i) {
37 2 steps_.back().attributes[i].after = std::move(step.attributes[i].after);
38 }
39 2 return;
40 }
41
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 Push(std::move(step));
42
3/8
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
17 }
43
44 1 void EditHistory::Clear(Scene& scene, const Path& path, std::string_view attribute) {
45 1 const PrimId prim = scene.Require(path);
46 1 const std::string name(attribute);
47
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 Cell before = Capture(scene, prim, name);
48
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 scene.Clear(prim, attribute);
49
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 Push({.path = path, .attributes = {{name, std::move(before), Capture(scene, prim, name)}}});
50
6/18
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
✓ Branch 13 taken 1 time.
✓ Branch 14 taken 1 time.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
3 }
51
52 1 void EditHistory::Define(Scene& scene, const Path& path, std::string_view type) {
53
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
1 if (scene.Has(path)) {
54
0/4
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ throw std::invalid_argument("'" + path.String() + "' has a prim already");
55 }
56 1 scene.Define(path, type);
57
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 Push({.path = path, .after = Snapshot(scene, path), .structural = true});
58
1/8
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 }
59
60 2 bool EditHistory::Remove(Scene& scene, const Path& path) {
61
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
2 if (!scene.Has(path)) {
62 return false;
63 }
64
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 Step step{.path = path, .before = Snapshot(scene, path), .structural = true};
65
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 scene.Remove(path);
66
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 Push(std::move(step));
67 1 return true;
68 1 }
69
70 2 void EditHistory::Add(Scene& scene, const Scene& source, const Path& path) {
71
3/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
4 if (!source.Has(path) || scene.Has(path)) {
72
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
2 throw std::invalid_argument("Cannot add '" + path.String() +
73
2/4
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
3 "': the source has no prim there or the scene has one");
74 }
75
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 Step step{.path = path, .after = Snapshot(source, path), .structural = true};
76
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 Restore(scene, step.after);
77
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 Push(std::move(step));
78 1 }
79
80 1 void EditHistory::BeginGroup() {
81
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if (groupDepth_++ == 0) {
82 1 group_ = nextGroup_++;
83 }
84 1 }
85
86 1 void EditHistory::EndGroup() {
87
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 if (groupDepth_ > 0 && --groupDepth_ == 0) {
88 1 group_ = 0;
89 }
90 1 }
91
92 10 bool EditHistory::Undo(Scene& scene) {
93 10 return Move(scene, false);
94 }
95
96 3 bool EditHistory::Redo(Scene& scene) {
97 3 return Move(scene, true);
98 }
99
100 2 std::string EditHistory::UndoLabel() const {
101
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 return CanUndo() ? Label(steps_[next_ - 1]) : std::string();
102 }
103
104 ✗ std::string EditHistory::RedoLabel() const {
105
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return CanRedo() ? Label(steps_[next_]) : std::string();
106 }
107
108 ✗ void EditHistory::Reset() {
109 ✗ *this = EditHistory();
110 ✗ }
111
112 25 EditHistory::Cell EditHistory::Capture(const Scene& scene, PrimId prim,
113 const std::string& attribute) {
114 25 Cell cell;
115
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 const std::span<const TimeSample> samples = scene.Samples(prim, attribute);
116
1/2
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
25 cell.samples.assign(samples.begin(), samples.end());
117
3/4
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 23 times.
25 if (const Tensor* array = scene.Read(prim, attribute)) {
118
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 cell.array = *array;
119 } else {
120
1/2
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
23 cell.value = scene.Get(prim, attribute);
121 }
122 25 return cell;
123 ✗ }
124
125 14 void EditHistory::Apply(Scene& scene, PrimId prim, const std::string& attribute, const Cell& cell) {
126
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 13 times.
14 if (!cell.samples.empty()) {
127 1 scene.ClearSamples(prim, attribute);
128
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 for (const TimeSample& sample : cell.samples) {
129
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
2 sample.tensor.Empty() ? scene.SetAt(prim, attribute, sample.time, sample.value)
130
2/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
2 : scene.WriteAt(prim, attribute, sample.time, sample.tensor);
131 }
132
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 } else if (!cell.array.Empty()) {
133
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 scene.Write(prim, attribute, cell.array);
134
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
12 } else if (cell.value) {
135 9 scene.Set(prim, attribute, *cell.value);
136 } else {
137 3 scene.Clear(prim, attribute);
138 }
139 14 }
140
141 3 std::vector<EditHistory::PrimSnapshot> EditHistory::Snapshot(const Scene& scene, const Path& path) {
142 3 std::vector<PrimSnapshot> prims;
143
2/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
6 std::vector<Path> pending{path};
144
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 while (!pending.empty()) {
145 5 const Path next = std::move(pending.back());
146 5 pending.pop_back();
147
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 const PrimId prim = scene.Require(next);
148
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 PrimSnapshot& snapshot = prims.emplace_back();
149
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 snapshot.path = next;
150
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 snapshot.type = std::string(scene.TypeName(prim));
151
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 snapshot.apis = scene.AppliedApis(prim);
152
3/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 5 times.
11 for (const std::string& name : scene.Attributes(next)) {
153
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 snapshot.attributes.emplace(name, Capture(scene, prim, name));
154 5 }
155
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 const std::vector<Path> children = scene.Children(next);
156
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 pending.insert(pending.end(), children.rbegin(), children.rend());
157 5 }
158 3 return prims;
159
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
6 }
160
161 6 void EditHistory::Restore(Scene& scene, const std::vector<PrimSnapshot>& prims) {
162
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 for (const PrimSnapshot& snapshot : prims) {
163 5 const PrimId prim = scene.Define(snapshot.path, snapshot.type);
164
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 for (const std::string& api : snapshot.apis) {
165 1 scene.ApplyApi(prim, api);
166 }
167
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 5 times.
11 for (const auto& [name, cell] : snapshot.attributes) {
168 6 Apply(scene, prim, name, cell);
169 }
170 }
171 6 }
172
173 14 void EditHistory::Apply(Scene& scene, const Step& step, bool forward) {
174
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (step.structural) {
175 5 scene.Remove(step.path);
176
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 Restore(scene, forward ? step.after : step.before);
177 11 return;
178 }
179 // Edits of a prim removed since are skipped.
180 9 const std::optional<PrimId> prim = scene.Find(step.path);
181
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
9 if (!prim) {
182 return;
183 }
184
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 for (std::size_t i = 0; i < step.attributes.size(); ++i) {
185
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 const AttributeEdit& edit = step.attributes[forward ? i : step.attributes.size() - 1 - i];
186
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 Apply(scene, *prim, edit.name, forward ? edit.after : edit.before);
187 }
188 }
189
190 10 void EditHistory::Push(Step step) {
191 10 steps_.resize(next_);
192
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 step.group = groupDepth_ > 0 ? group_ : 0;
193 10 open_ = step.open;
194 10 steps_.push_back(std::move(step));
195 10 next_ = steps_.size();
196 10 }
197
198 13 bool EditHistory::Move(Scene& scene, bool forward) {
199 13 open_ = false;
200
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 1 time.
13 if (forward ? !CanRedo() : !CanUndo()) {
201 return false;
202 }
203
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 const std::uint64_t group = steps_[forward ? next_ : next_ - 1].group;
204 14 do {
205
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 if (forward) {
206 4 Apply(scene, steps_[next_++], true);
207 } else {
208 10 Apply(scene, steps_[--next_], false);
209 }
210
9/10
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 12 times.
17 } while (group != 0 && (forward ? CanRedo() && steps_[next_].group == group
211
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
2 : CanUndo() && steps_[next_ - 1].group == group));
212 return true;
213 }
214
215 2 std::string EditHistory::Label(const Step& step) {
216
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if (step.structural) {
217 1 return (step.before.empty() ? "Add "
218 1 : step.after.empty() ? "Remove "
219 : "Change ") +
220
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 step.path.String();
221 }
222
2/4
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
2 return step.path.String() + " " + Join(step.attributes, " ", &AttributeEdit::name);
223 }
224
225 } // namespace ernie
226