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, _ins,
78 std::forward<Fn>(fn));
79 }
80
81 // Invoke fn(Op, arg1, arg2) for each instruction as-is, with no index
82 // normalization or range checking.
83 template <class Fn>
84 void ForEach(Fn &&fn) const {
85 return _ForEachImpl(-1, -1, _ins, std::forward<Fn>(fn));
86 }
87
88 // Invoke fn(Op, arg1, arg2) for each instruction as-is, with no index
89 // normalization or range checking, and passing mutable references for arg1
90 // and arg2 to fn(). This lets fn() modify indexes if desired.
91 template <class Fn>
92 void ModifyEach(Fn &&fn) {
93 return _ForEachImpl(-1, -1, _ins, 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 Ins, class Fn>
206 void _ForEachImpl(size_t numLiterals, size_t initialSize,
207 Ins &&ins, Fn &&fn) const {
208
209 // Walk and call fn with each.
210 const auto begin = std::begin(std::forward<Ins>(ins));
211 auto iter = std::begin(std::forward<Ins>(ins));
212 const auto end = std::end(std::forward<Ins>(ins));
213
214 while (iter != end) {
215 OpAndCount oc = _ToOpAndCount(*iter);
216
217 if (!IsValidOp(oc.op)) {
218 _IssueInvalidOpError(oc, std::distance(begin, iter));
219 return;
220 }
221
222 const int arity = GetArity(oc.op);
223
224 // Check sufficient args.
225 if (std::distance(++iter, end) < oc.count * arity) {
226 _IssueInvalidInsError(
227 oc, std::distance(begin, iter),
228 oc.count * arity, std::distance(iter, end));
229 }
230
231 // Do each set of args.
232 for (; oc.count--; iter += arity) {
233 int64_t a1 = iter[0];
234 int64_t a2 = arity > 1 ? iter[1] : -1;
235
236 // Normalize and check indexes if requested.
237 switch (oc.op) {
238 default:
239 _IssueInvalidOpError(oc, std::distance(begin, iter));
240 return;
241 case OpWriteLiteral:
242 if (!_CheckLiteralIndex(a1, numLiterals) ||
243 !_NormalizeAndCheckRefIndex(a2, initialSize)) {
244 continue;
245 }
246 break;
247 case OpWriteRef:
248 if (!_NormalizeAndCheckRefIndex(a1, initialSize) ||
249 !_NormalizeAndCheckRefIndex(a2, initialSize)) {
250 continue;
251 }
252 break;
253 case OpInsertLiteral:
254 if (!_CheckLiteralIndex(a1, numLiterals) ||
255 !_NormalizeAndCheckInsertIndex(a2, initialSize)) {
256 continue;
257 }
258 _UpdateWorkingSize(initialSize, initialSize + 1);
259 break;
260 case OpInsertRef:
261 if (!_NormalizeAndCheckRefIndex(a1, initialSize) ||
262 !_NormalizeAndCheckInsertIndex(a2, initialSize)) {
263 continue;
264 }
265 _UpdateWorkingSize(initialSize, initialSize + 1);
266 break;
267 case OpEraseRef:
268 if (!_NormalizeAndCheckRefIndex(a1, initialSize)) {
269 continue;
270 }
271 _UpdateWorkingSize(initialSize, initialSize - 1);
272 break;
273
274 case OpMinSizeFill:
275 if (!_CheckLiteralIndex(a2, numLiterals)) {
276 continue;
277 } // intentional fall-thru
278 case OpMinSize:
279 if (!_CheckSizeArg(oc.op, a1)) {
280 continue;
281 }
282 _UpdateWorkingSize(
283 initialSize,
284 std::max(initialSize, static_cast<size_t>(a1)));
285 break;
286
287 case OpSetSizeFill:
288 if (!_CheckLiteralIndex(a2, numLiterals)) {
289 continue;
290 } // intentional fall-thru
291 case OpSetSize:
292 if (!_CheckSizeArg(oc.op, a1)) {
293 continue;
294 }
295 _UpdateWorkingSize(initialSize, a1);
296 break;
297
298 case OpMaxSize:
299 if (!_CheckSizeArg(oc.op, a1)) {
300 continue;
301 }
302 _UpdateWorkingSize(
303 initialSize,
304 std::min(initialSize, static_cast<size_t>(a1)));
305 break;
306
307 };
308
309 // Invoke caller.
310 std::forward<Fn>(fn)(oc.op, a1, a2);
311 }
312 }
313 }
314
315 // Instructions and arguments are stored together in order. For example the
316 // following operation sequence:
317 //
318 // resize 1024
319 // write <literal 0> to [2]
320 // write <literal 1> to [4]
321 // write [5] to [6]
322 // erase [9]
323 // erase [9]
324 //
325 // Would be encoded as the following 64-bit quantities, each denoted by [].
326 //
327 // [1 OpSetSize] [1024] [2 OpWriteLiteral] [0] [2] [1] [4] [1 OpWriteRef]
328 // [5] [6] [2 OpErase] [9] [9].
329 //
330 // The meaning of the 64-bit quantities that follow an op are determined by
331 // the op itself. For example, in the case of [2 OpWriteLiteral], there are
332 // four quantities that follow, two for each instruction: a literal index
333 // and a destination index. The GetArity() member function returns the
334 // arity for a given op. Currently either 1 or 2.
335 std::vector<int64_t> _ins;
336
337};
338
339PXR_NAMESPACE_CLOSE_SCOPE
340
341#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