Loading...
Searching...
No Matches
arrayEditOps.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_OPS_H
9#define PXR_BASE_VT_ARRAY_EDIT_OPS_H
10
12
13#include "pxr/pxr.h"
14#include "pxr/base/vt/api.h"
15
16#include "pxr/base/tf/hash.h"
17
18#include <cstdint>
19#include <cstdlib>
20#include <cstring>
21#include <limits>
22#include <vector>
23
24PXR_NAMESPACE_OPEN_SCOPE
25
26// A helper class used in the implementation of VtArrayEdit.
27class Vt_ArrayEditOps
28{
29public:
30 static constexpr int64_t EndIndex = std::numeric_limits<int64_t>::min();
31
32 // The supported operations.
33 //
34 // This enum's underlying type is int64_t because MSVC won't pack it
35 // correctly when used as a bitfield in a struct. For example, on MSVC if
36 // we make the underlying type uint8_t, then struct Foo { int64_t x:56; Op:8
37 // }; has size 16. Making the underlying type int64_t fixes this.
38 enum Op : int64_t {
39 OpWriteLiteral, // write <literal> to [index]
40 OpWriteRef, // write [index1] to [index2]
41 OpInsertLiteral, // insert <literal> at [index]
42 OpInsertRef, // insert [index1] at [index2]
43 OpEraseRef, // erase [index]
44 OpMinSize, // minsize <size>
45 OpMinSizeFill, // minsize <size> <literal>
46 OpSetSize, // resize <size>
47 OpSetSizeFill, // resize <size> <literal>
48 OpMaxSize, // maxsize <size>
49 };
50 static constexpr uint8_t NumOps = OpMaxSize + 1;
51
52 static constexpr bool IsValidOp(Op op) {
53 return op >= static_cast<Op>(0) && op < NumOps;
54 }
55
56 static constexpr int GetArity(Op op) {
57 if (op == OpWriteLiteral || op == OpWriteRef ||
58 op == OpInsertLiteral || op == OpInsertRef ||
59 op == OpMinSizeFill || op == OpSetSizeFill) {
60 return 2;
61 }
62 return 1;
63 }
64
65 // Repetitions of a given op.
66 struct OpAndCount {
67 int64_t count:56;
68 Op op:8;
69 };
70 static_assert(sizeof(OpAndCount) == sizeof(int64_t));
71
72 // Invoke fn(Op, arg1, arg2) for each valid instruction. Normalize index
73 // args according to initialSize (if negative or EndIndex). Invalid
74 // instructions with out-of-bounds indexes are skipped.
75 template <class Fn>
76 void ForEachValid(size_t numLiterals, size_t initialSize, Fn &&fn) const {
77 return _ForEachImpl(numLiterals, initialSize, std::forward<Fn>(fn));
78 }
79
80 // Invoke fn(Op, arg1, arg2) for each instruction as-is, with no index
81 // normalization or range checking.
82 template <class Fn>
83 void ForEach(Fn &&fn) const {
84 return _ForEachImpl(-1, -1, std::forward<Fn>(fn));
85 }
86
87 // Invoke fn(Op, arg1, arg2) for each instruction as-is, with no index
88 // normalization or range checking, and passing mutable references for arg1
89 // and arg2 to fn(). This lets fn() modify indexes if desired. Note that
90 // for single-argument Ops, modifications to arg2 are ignored.
91 template <class Fn>
92 void ModifyEach(Fn &&fn) {
93 return _ModifyImpl(std::forward<Fn>(fn));
94 }
95
96 // Return true if there are no ops, else false.
97 bool IsEmpty() const {
98 return _ins.empty();
99 }
100
101private:
102 template <class ELEM>
103 friend class VtArrayEdit;
104
105 template <class ELEM>
106 friend class VtArrayEditBuilder;
107
108 friend class Vt_ArrayEditOpsBuilder;
109
110 template <class HashState>
111 friend void TfHashAppend(HashState &h, Vt_ArrayEditOps const &self) {
112 h.Append(self._ins);
113 }
114
115 friend bool
116 operator==(Vt_ArrayEditOps const &l, Vt_ArrayEditOps const &r) {
117 return l._ins == r._ins;
118 }
119 friend bool
120 operator!=(Vt_ArrayEditOps const &l, Vt_ArrayEditOps const &r) {
121 return !(l == r);
122 }
123
124 static OpAndCount _ToOpAndCount(int64_t i64) {
125 OpAndCount oc;
126 memcpy(&oc, &i64, sizeof(oc));
127 return oc;
128 }
129
130 static int64_t _ToInt64(OpAndCount oc) {
131 int64_t i64;
132 memcpy(&i64, &oc, sizeof(i64));
133 return i64;
134 }
135
136 static constexpr size_t _DisableBoundsCheck = size_t(-1);
137
138 VT_API
139 void _IssueInvalidInsError(
140 OpAndCount oc, size_t offset, size_t required, size_t actual) const;
141
142 VT_API
143 void _IssueInvalidOpError(OpAndCount oc, size_t offset) const;
144
145 VT_API static void _LiteralOutOfBounds(int64_t idx, size_t size);
146 VT_API static void _ReferenceOutOfBounds(int64_t idx, size_t size);
147 VT_API static void _InsertOutOfBounds(int64_t idx, size_t size);
148 VT_API static void _NegativeSizeArg(Op op, int64_t idx);
149
150 static bool _CheckLiteralIndex(int64_t idx, size_t size) {
151 if (size == _DisableBoundsCheck ||
152 (idx >= 0 && static_cast<size_t>(idx) < size)) {
153 return true;
154 }
155 _LiteralOutOfBounds(idx, size);
156 return false;
157 }
158
159 static bool _NormalizeAndCheckRefIndex(int64_t &idx, size_t size) {
160 if (size == _DisableBoundsCheck) {
161 return true;
162 }
163 if (idx < 0) {
164 idx += size;
165 }
166 if (idx >= 0 && static_cast<size_t>(idx) < size) {
167 return true;
168 }
169 _ReferenceOutOfBounds(idx, size);
170 return false;
171 }
172
173 static bool _NormalizeAndCheckInsertIndex(int64_t &idx, size_t size) {
174 if (size == _DisableBoundsCheck) {
175 return true;
176 }
177 if (idx == EndIndex) {
178 idx = size;
179 }
180 if (idx < 0) {
181 idx += size;
182 }
183 if (idx >= 0 && static_cast<size_t>(idx) <= size) {
184 return true;
185 }
186 _InsertOutOfBounds(idx, size);
187 return false;
188 }
189
190 static bool _CheckSizeArg(Op op, int64_t arg) {
191 if (arg < 0) {
192 _NegativeSizeArg(op, arg);
193 return false;
194 }
195 return true;
196 }
197
198 static void _UpdateWorkingSize(size_t &workingSize, size_t newSize) {
199 if (workingSize == _DisableBoundsCheck) {
200 return;
201 }
202 workingSize = newSize;
203 }
204
205 template <class Fn>
206 void _ForEachImpl(size_t numLiterals, size_t initialSize, Fn &&fn) const {
207
208 // Walk and call fn with each.
209 const auto begin = std::begin(_ins);
210 auto iter = std::begin(_ins);
211 const auto end = std::end(_ins);
212
213 while (iter != end) {
214 OpAndCount oc = _ToOpAndCount(*iter);
215
216 if (!IsValidOp(oc.op)) {
217 _IssueInvalidOpError(oc, std::distance(begin, iter));
218 return;
219 }
220
221 const int arity = GetArity(oc.op);
222
223 // Check sufficient args.
224 if (std::distance(++iter, end) < oc.count * arity) {
225 _IssueInvalidInsError(
226 oc, std::distance(begin, iter),
227 oc.count * arity, std::distance(iter, end));
228 }
229
230 // Do each set of args.
231 for (; oc.count--; iter += arity) {
232 int64_t a1 = iter[0];
233 int64_t a2 = arity > 1 ? iter[1] : -1;
234
235 // Normalize and check indexes if requested.
236 switch (oc.op) {
237 default:
238 _IssueInvalidOpError(oc, std::distance(begin, iter));
239 return;
240 case OpWriteLiteral:
241 if (!_CheckLiteralIndex(a1, numLiterals) ||
242 !_NormalizeAndCheckRefIndex(a2, initialSize)) {
243 continue;
244 }
245 break;
246 case OpWriteRef:
247 if (!_NormalizeAndCheckRefIndex(a1, initialSize) ||
248 !_NormalizeAndCheckRefIndex(a2, initialSize)) {
249 continue;
250 }
251 break;
252 case OpInsertLiteral:
253 if (!_CheckLiteralIndex(a1, numLiterals) ||
254 !_NormalizeAndCheckInsertIndex(a2, initialSize)) {
255 continue;
256 }
257 _UpdateWorkingSize(initialSize, initialSize + 1);
258 break;
259 case OpInsertRef:
260 if (!_NormalizeAndCheckRefIndex(a1, initialSize) ||
261 !_NormalizeAndCheckInsertIndex(a2, initialSize)) {
262 continue;
263 }
264 _UpdateWorkingSize(initialSize, initialSize + 1);
265 break;
266 case OpEraseRef:
267 if (!_NormalizeAndCheckRefIndex(a1, initialSize)) {
268 continue;
269 }
270 _UpdateWorkingSize(initialSize, initialSize - 1);
271 break;
272
273 case OpMinSizeFill:
274 if (!_CheckLiteralIndex(a2, numLiterals)) {
275 continue;
276 } // intentional fall-thru
277 case OpMinSize:
278 if (!_CheckSizeArg(oc.op, a1)) {
279 continue;
280 }
281 _UpdateWorkingSize(
282 initialSize,
283 std::max(initialSize, static_cast<size_t>(a1)));
284 break;
285
286 case OpSetSizeFill:
287 if (!_CheckLiteralIndex(a2, numLiterals)) {
288 continue;
289 } // intentional fall-thru
290 case OpSetSize:
291 if (!_CheckSizeArg(oc.op, a1)) {
292 continue;
293 }
294 _UpdateWorkingSize(initialSize, a1);
295 break;
296
297 case OpMaxSize:
298 if (!_CheckSizeArg(oc.op, a1)) {
299 continue;
300 }
301 _UpdateWorkingSize(
302 initialSize,
303 std::min(initialSize, static_cast<size_t>(a1)));
304 break;
305
306 };
307
308 // Invoke caller.
309 std::forward<Fn>(fn)(oc.op, a1, a2);
310 }
311 }
312 }
313
314 template <class Fn>
315 void _ModifyImpl(Fn &&fn) {
316
317 // Walk and call fn with each.
318 const auto begin = std::begin(_ins);
319 auto iter = std::begin(_ins);
320 const auto end = std::end(_ins);
321
322 while (iter != end) {
323 OpAndCount oc = _ToOpAndCount(*iter);
324
325 if (!IsValidOp(oc.op)) {
326 _IssueInvalidOpError(oc, std::distance(begin, iter));
327 return;
328 }
329
330 const int arity = GetArity(oc.op);
331
332 // Check sufficient args.
333 if (std::distance(++iter, end) < oc.count * arity) {
334 _IssueInvalidInsError(
335 oc, std::distance(begin, iter),
336 oc.count * arity, std::distance(iter, end));
337 }
338
339 // Do each set of args.
340 for (; oc.count--; iter += arity) {
341 int64_t invalid = -1;
342 int64_t &a1 = iter[0];
343 int64_t &a2 = arity > 1 ? iter[1] : invalid;
344
345 // Invoke caller.
346 std::forward<Fn>(fn)(oc.op, a1, a2);
347 }
348 }
349 }
350
351 // Instructions and arguments are stored together in order. For example the
352 // following operation sequence:
353 //
354 // resize 1024
355 // write <literal 0> to [2]
356 // write <literal 1> to [4]
357 // write [5] to [6]
358 // erase [9]
359 // erase [9]
360 //
361 // Would be encoded as the following 64-bit quantities, each denoted by [].
362 //
363 // [1 OpSetSize] [1024] [2 OpWriteLiteral] [0] [2] [1] [4] [1 OpWriteRef]
364 // [5] [6] [2 OpErase] [9] [9].
365 //
366 // The meaning of the 64-bit quantities that follow an op are determined by
367 // the op itself. For example, in the case of [2 OpWriteLiteral], there are
368 // four quantities that follow, two for each instruction: a literal index
369 // and a destination index. The GetArity() member function returns the
370 // arity for a given op. Currently either 1 or 2.
371 std::vector<int64_t> _ins;
372
373};
374
375PXR_NAMESPACE_CLOSE_SCOPE
376
377#endif // PXR_BASE_VT_ARRAY_EDIT_OPS_H
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:52