Loading...
Searching...
No Matches
arrayEdit.h
Go to the documentation of this file.
1//
2// Copyright 2025 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7
8#ifndef PXR_BASE_VT_ARRAY_EDIT_H
9#define PXR_BASE_VT_ARRAY_EDIT_H
10
12
13#include "pxr/pxr.h"
14#include "pxr/base/vt/api.h"
15#include "pxr/base/vt/array.h"
17#include "pxr/base/vt/streamOut.h"
18#include "pxr/base/vt/traits.h"
19
20#include "pxr/base/arch/hints.h"
22#include "pxr/base/tf/functionRef.h"
23#include "pxr/base/tf/hash.h"
24#include "pxr/base/tf/span.h"
26
27#include <iosfwd>
28#include <type_traits>
29
30PXR_NAMESPACE_OPEN_SCOPE
31
32template <class ELEM>
33class VtArrayEditBuilder; // fwd
34
51template <class ELEM>
53{
54public:
57
58 using ElementType = typename Array::ElementType;
59
62 VtArrayEdit() = default;
63
65 friend bool operator==(VtArrayEdit const &x, VtArrayEdit const &y) {
66 return std::tie(x._literals, x._ops) == std::tie(y._literals, y._ops);
67 }
69 friend bool operator!=(VtArrayEdit const &x, VtArrayEdit const &y) {
70 return !(x == y);
71 }
72
76 bool IsIdentity() const {
77 return _ops.IsEmpty();
78 }
79
82 return _literals;
83 }
84
89 return _literals;
90 }
91
98 if (IsIdentity()) {
99 return weaker;
100 }
101 return _ComposeEdits(weaker);
102 }
103
106 if (IsIdentity()) {
107 return std::move(weaker);
108 }
109 return _ComposeEdits(std::move(weaker));
110 }
111
113 VtArrayEdit ComposeOver(VtArrayEdit const &weaker) const & {
114 if (IsIdentity()) {
115 return weaker;
116 }
117 return _ComposeEdits(weaker);
118 };
119
122 if (IsIdentity()) {
123 return std::move(weaker);
124 }
125 return _ComposeEdits(std::move(weaker));
126 }
127
129 Array ComposeOver(Array const &weaker) const {
130 if (IsIdentity()) {
131 return weaker;
132 }
133 return _ApplyEdits(weaker);
134 }
135
137 Array ComposeOver(Array &&weaker) const {
138 if (IsIdentity()) {
139 return std::move(weaker);
140 }
141 return _ApplyEdits(std::move(weaker));
142 }
143
170 friend std::ostream &
171 operator<<(std::ostream &out, const VtArrayEdit self) {
172 auto streamElem = [&](int64_t index) -> std::ostream & {
173 return VtStreamOut(self._literals[index], out);
174 };
175 return Vt_ArrayEditStreamImpl(
176 self._ops, self._literals.size(), streamElem, out);
177 }
178
184 template <class UnaryOp>
185 std::ostream &
186 StreamCustom(std::ostream &out, UnaryOp &&unaryOp) const {
187 auto streamElem = [&](int64_t index) -> std::ostream & {
188 return VtStreamOut(
189 std::forward<UnaryOp>(unaryOp)(_literals[index]),
190 out);
191 };
192 return Vt_ArrayEditStreamImpl(_ops, _literals.size(), streamElem, out);
193 }
194
195private:
196 friend class VtArrayEditBuilder<ELEM>;
197 friend struct Vt_ArrayEditHashAccess;
198
199 VT_API friend
200 std::ostream &Vt_ArrayEditStreamImpl(
201 Vt_ArrayEditOps const &ops, size_t literalsSize,
202 TfFunctionRef<std::ostream &(int64_t index)> elemToStr,
203 std::ostream &out);
204
205 using _Ops = Vt_ArrayEditOps;
206
207 Array _ApplyEdits(Array &&weaker) const;
208 Array _ApplyEdits(Array const &weaker) const {
209 return _ApplyEdits(Array {weaker});
210 }
211
212 VtArrayEdit _ComposeEdits(VtArrayEdit &&weaker) &&;
213 VtArrayEdit _ComposeEdits(VtArrayEdit const &weaker) &&;
214
215 VtArrayEdit _ComposeEdits(VtArrayEdit &&weaker) const & {
216 return VtArrayEdit(*this)._ComposeEdits(std::move(weaker));
217 }
218 VtArrayEdit _ComposeEdits(VtArrayEdit const &weaker) const & {
219 return VtArrayEdit(*this)._ComposeEdits(weaker);
220 }
221
222 Array _literals;
223 _Ops _ops;
224};
225
226VT_API
227std::ostream &Vt_ArrayEditStreamImpl(
228 Vt_ArrayEditOps const &ops, size_t literalsSize,
229 TfFunctionRef<std::ostream &(int64_t index)> streamElem,
230 std::ostream &out);
231
232struct Vt_ArrayEditHashAccess
233{
234 template <class HashState, class Edit>
235 static void Append(HashState &h, Edit const &edit) {
236 h.Append(edit._literals, edit._ops);
237 }
238};
239
240template <class HashState, class ELEM>
241std::enable_if_t<VtIsHashable<ELEM>()>
242TfHashAppend(HashState &h, VtArrayEdit<ELEM> const &edit) {
243 Vt_ArrayEditHashAccess::Append(h, edit);
244}
245
246template <class ELEM>
248VtArrayEdit<ELEM>::_ApplyEdits(Array &&weaker) const
249{
250 TRACE_FUNCTION();
251
252 // weaker is an array that we edit.
253 Array result = std::move(weaker);
254 Array const &cresult = result;
255
256 Array const &literals = _literals;
257 const auto numLiterals = literals.size();
258
259 // Each insert and erase below shifts the elements that follow it, so an
260 // edit containing k of them costs O(k * n). Writes and insert & erase at
261 // the end are cheap. Measured on a 100k-element array with 1000 inserts at
262 // index 0: about 7 ms for int elements and about 220 ms for 128-byte
263 // elements. With k in the tens it is well under a millisecond even for
264 // large arrays, so this has not been worth addressing yet.
265 //
266 // Two approaches were considered. Batching a run of inserts or erases into
267 // a single pass only works when the indexes within the run are monotone,
268 // since otherwise each index is relative to a different intermediate array.
269 // Removing the O(k * n) behavior in general requires constant-time indexing
270 // together with constant-time positional insertion, which is not possible;
271 // the achievable bound is O(n + k log n) using an order-statistic structure
272 // over the insert positions, and OpWriteRef and OpInsertRef complicate that
273 // because they read array contents at intermediate points in the editing
274 // sequence. Either is worth revisiting if an insert/erase edit pattern
275 // appears with k in the hundreds or more over a large array.
276
277 _ops.ForEachValid(numLiterals, cresult.size(),
278 [&](_Ops::Op op, int64_t a1, int64_t a2) {
279 switch (op) {
280 case _Ops::OpWriteLiteral:
281 result[a2] = literals[a1];
282 break;
283 case _Ops::OpWriteRef: // a1: result index -> a2: result index.
284 result[a2] = cresult[a1];
285 break;
286 case _Ops::OpInsertLiteral: // a1: literal index -> a2: result index.
287 result.insert(result.cbegin() + a2, literals[a1]);
288 break;
289 case _Ops::OpInsertRef: // a1: result index -> a2: result index.
290 result.insert(result.cbegin() + a2, cresult[a1]);
291 break;
292 case _Ops::OpEraseRef: // a1: result index, (a2: unused)
293 result.erase(result.cbegin() + a1);
294 break;
295 case _Ops::OpMinSize: // a1: minimum size, (a2: unused)
296 if (result.size() < static_cast<size_t>(a1)) {
297 result.resize(a1);
298 }
299 break;
300 case _Ops::OpMinSizeFill: // a1: minimum size, a2: literal index.
301 if (result.size() < static_cast<size_t>(a1)) {
302 result.resize(a1, literals[a2]);
303 }
304 break;
305 case _Ops::OpSetSize: // a1: explicit size, (a2: unused)
306 result.resize(a1);
307 break;
308 case _Ops::OpSetSizeFill: // a1: explicit size, a2: literal index.
309 result.resize(a1, literals[a2]);
310 break;
311 case _Ops::OpMaxSize: // a1: maximum size, a2: unused
312 if (result.size() > static_cast<size_t>(a1)) {
313 result.resize(a1);
314 }
315 break;
316 };
317 });
318 return result;
319}
320
321template <class ELEM>
324{
325 TRACE_FUNCTION();
326
327 // Both this and weaker consist of edits. We compose the edits and we can
328 // steal our resources.
329
330 // Composing an edit over itself: we move from *this below, which would
331 // leave weaker moved-from before we read it. Compose over a copy instead.
332 if (ARCH_UNLIKELY(this == &weaker)) {
333 VtArrayEdit weakerCopy = weaker;
334 return std::move(*this)._ComposeEdits(std::move(weakerCopy));
335 }
336
337 // Composition is a plain concatenation: weaker's ops run first, then ours.
338 // Deduplicating literals, dropping unreferenced literals and merging
339 // adjacent op runs are all done by VtArrayEditBuilder::Optimize(), which
340 // callers may invoke on the result. They are deliberately not done here,
341 // since composing N edits would then rescan the whole accumulated op stream
342 // N times.
343
344 VtArrayEdit result = std::move(*this);
345
346 // Place weaker's literals ahead of ours, so weaker's literal indexes remain
347 // valid and only ours need adjusting.
348 const int64_t numWeakerLiterals = weaker._literals.size();
349 result._literals.insert(
350 result._literals.begin(),
351 weaker._literals.begin(), weaker._literals.end());
352 result._ops.OffsetLiteralIndexes(numWeakerLiterals);
353
354 result._ops._ins.insert(result._ops._ins.begin(),
355 weaker._ops._ins.begin(),
356 weaker._ops._ins.end());
357
358 return result;
359}
360
361template <class ELEM>
364{
365 TRACE_FUNCTION();
366
367 // Both this and weaker consist of edits. We compose the edits and we can
368 // steal both our resources and weaker's.
369
370 // As in the other overload, composing an edit over itself has to go through
371 // a copy, since we move from *this below.
372 if (ARCH_UNLIKELY(this == &weaker)) {
373 VtArrayEdit weakerCopy = weaker;
374 return std::move(*this)._ComposeEdits(std::move(weakerCopy));
375 }
376
377 // Composition is a plain concatenation: weaker's ops run first, then ours.
378 // See the comment in the other _ComposeEdits() overload regarding why no
379 // further optimization happens here.
380
381 VtArrayEdit result = std::move(*this);
382
383 const int64_t numWeakerLiterals = weaker._literals.size();
384
385 // Append our literals to weaker's, so weaker's literal indexes remain valid
386 // and only ours need adjusting.
387 weaker._literals.insert(
388 weaker._literals.end(),
389 std::make_move_iterator(result._literals.begin()),
390 std::make_move_iterator(result._literals.end()));
391
392 result._ops.OffsetLiteralIndexes(numWeakerLiterals);
393
394 // Append the stronger ops to weaker.
395 weaker._ops._ins.insert(
396 weaker._ops._ins.end(),
397 std::make_move_iterator(result._ops._ins.begin()),
398 std::make_move_iterator(result._ops._ins.end()));
399
400 return std::move(weaker);
401}
402
403// Specialize traits for VtArrayEdit.
404template <typename T>
405struct VtIsArrayEdit<VtArrayEdit<T>> : public std::true_type {};
406
407// VtArrayEdit can transform if the underlying element type can.
408template <class ELEM>
411
412// VtArrayEdit can compose over itself, VtArray, and the VtBackground.
413template <class T>
414struct VtValueTypeCanCompose<VtArrayEdit<T>> : std::true_type {};
415
416PXR_NAMESPACE_CLOSE_SCOPE
417
418#endif // PXR_BASE_VT_ARRAY_EDIT_H
Low-level utilities for informing users of various internal and external diagnostic conditions.
This class provides a non-owning reference to a type-erased callable object with a specified signatur...
Definition functionRef.h:19
Represents a range of contiguous elements.
Definition span.h:71
A builder type that produces instances of VtArrayEdit representing sequences of array edit operations...
An array edit represents a sequence of per-element modifications to a VtArray.
Definition arrayEdit.h:53
bool IsIdentity() const
Return true if this edit is the identity edit.
Definition arrayEdit.h:76
friend bool operator!=(VtArrayEdit const &x, VtArrayEdit const &y)
Inequality comparison.
Definition arrayEdit.h:69
friend bool operator==(VtArrayEdit const &x, VtArrayEdit const &y)
Equality comparison.
Definition arrayEdit.h:65
TfSpan< const ElementType > GetLiterals() const
Return a view of the literal elements that this edit makes use of.
Definition arrayEdit.h:81
TfSpan< ElementType > GetMutableLiterals()
Return a mutable view of the literal elements that this edit makes use of.
Definition arrayEdit.h:88
Array ComposeOver(Array &&weaker) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition arrayEdit.h:137
VtArray< ELEM > Array
Shorthand for the corresponding VtArray type.
Definition arrayEdit.h:56
std::ostream & StreamCustom(std::ostream &out, UnaryOp &&unaryOp) const
Insert self to the stream out, but call unaryOp on each contained value-type element and pass the res...
Definition arrayEdit.h:186
VtArrayEdit ComposeOver(VtArrayEdit &&weaker) &&
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition arrayEdit.h:105
friend std::ostream & operator<<(std::ostream &out, const VtArrayEdit self)
Insert self to the stream out using the following format:
Definition arrayEdit.h:171
Array ComposeOver(Array const &weaker) const
Apply the edits in *this to weaker and return the resulting array.
Definition arrayEdit.h:129
VtArrayEdit()=default
Construct an identity array edit that performs no edits.
VtArrayEdit ComposeOver(VtArrayEdit const &weaker) &&
Compose this edit over weaker and return a new result representing the function composition,...
Definition arrayEdit.h:97
VtArrayEdit ComposeOver(VtArrayEdit const &weaker) const &
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition arrayEdit.h:113
VtArrayEdit ComposeOver(VtArrayEdit &&weaker) const &
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition arrayEdit.h:121
Represents an arbitrary dimensional rectangular container class.
Definition array.h:213
ELEM ElementType
Type this array holds.
Definition array.h:218
size_t size() const
Return the total number of elements in this array.
Definition array.h:474
iterator insert(const_iterator pos, value_type const &value)
Insert a copy of a single element at pos into the array.
Definition array.h:648
iterator end()
Returns a non-const iterator to the end of the array.
Definition array.h:368
iterator begin()
Return a non-const iterator to the start of the array.
Definition array.h:365
Compiler hints.
A trait to detect instantiations of VtArrayEdit, specialized in arrayEdit.h.
Definition traits.h:26
A trait indicating whether VtValue compose-over functionality can be registered for a type.
Definition traits.h:137
A trait indicating whether VtValue transform functionality can be registered for a type.
Definition traits.h:147