HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
predicateProgram.h
Go to the documentation of this file.
1 //
2 // Copyright 2023 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_USD_SDF_PREDICATE_PROGRAM_H
8 #define PXR_USD_SDF_PREDICATE_PROGRAM_H
9 
10 #include "pxr/pxr.h"
11 #include "pxr/usd/sdf/api.h"
12 
13 #include "pxr/base/tf/diagnostic.h"
15 #include "pxr/base/vt/value.h"
16 
19 
20 #include <initializer_list>
21 #include <memory>
22 #include <string>
23 #include <vector>
24 
26 
27 // fwd decl
28 template <class DomainType>
30 
31 // fwd decl
32 template <class DomainType>
36 
37 /// \class SdfPredicateProgram
38 ///
39 /// Represents a callable "program", the result of linking an
40 /// SdfPredicateExpression with an SdfPredicateLibrary via
41 /// SdfLinkPredicateExpression().
42 ///
43 /// The main public interface this class exposes is the function-call
44 /// operator(), accepting a single argument of type `DomainType`, as it is
45 /// specified to the template. Consider using `const Type &` as the
46 /// `DomainType` for both SdfPredicateProgram and SdfPredicateLibrary if it's
47 /// important that domain type instances aren't passed by-value.
48 ///
49 template <class DomainType>
51 {
52 public:
53  using PredicateFunction =
55 
56  friend SdfPredicateProgram
57  SdfLinkPredicateExpression<DomainType>(
58  SdfPredicateExpression const &expr,
60 
61  /// Return true if this program has any ops, false otherwise.
62  explicit operator bool() const {
63  return !_ops.empty();
64  }
65 
66  /// Run the predicate program on \p obj, and return the result.
68  operator()(DomainType const &obj) const {
71  int nest = 0;
72  auto funcIter = _funcs.cbegin();
73  auto opIter = _ops.cbegin(), opEnd = _ops.cend();
74 
75  // The current implementation favors short-circuiting over constance
76  // propagation. It might be beneficial to avoid short-circuiting when
77  // constancy isn't known, in hopes of establishing constancy. For
78  // example, if we have 'A or B', and 'A' evaluates to 'true' with
79  // MayVaryOverDescendants, we will skip evaluating B
80  // (short-circuit). This means we would miss the possibility of
81  // upgrading the constancy in case B returned 'true' with
82  // ConstantOverDescendants. This isn't a simple switch to flip though;
83  // we'd have to do some code restructuring here.
84  //
85  // For posterity, the rules for propagating constancy are the following,
86  // where A and B are the truth-values, and c(A), c(B), are whether or
87  // not the constancy is ConstantOverDescendants for A, B, respectively:
88  //
89  // c(A or B) = (A and c(A)) or (B and c(B)) or (c(A) and c(B))
90  // c(A and B) = (!A and c(A)) or (!B and c(B)) or (c(A) and c(B))
91 
92  // Helper for short-circuiting "and" and "or" operators. Advance,
93  // ignoring everything until we reach the next Close that brings us to
94  // the starting nest level.
95  auto shortCircuit = [&]() {
96  const int origNest = nest;
97  for (; opIter != opEnd; ++opIter) {
98  switch(*opIter) {
99  case Call: ++funcIter; break; // Skip calls.
100  case Not: case And: case Or: break; // Skip operations.
101  case Open: ++nest; break;
102  case Close:
103  if (--nest == origNest) {
104  return;
105  }
106  break;
107  };
108  }
109  };
110 
111  // Evaluate the predicate expression by processing operations and
112  // invoking predicate functions.
113  for (; opIter != opEnd; ++opIter) {
114  switch (*opIter) {
115  case Call:
116  result.SetAndPropagateConstancy((*funcIter++)(obj));
117  break;
118  case Not: result = !result; break;
119  case And: case Or: {
120  const bool decidingValue = *opIter != And;
121  // If the and/or result is already the deciding value,
122  // short-circuit. Otherwise the result is the rhs, so continue.
123  if (result == decidingValue) {
124  shortCircuit();
125  }
126  }
127  break;
128  case Open: ++nest; break;
129  case Close: --nest; break;
130  };
131  }
132  return result;
133  }
134 
135 private:
136  enum _Op { Call, Not, Open, Close, And, Or };
137  std::vector<_Op> _ops;
138  std::vector<PredicateFunction> _funcs;
139 };
140 
141 
142 /// Link \p expr with \p lib and return a callable program that evaluates \p
143 /// expr on given objects of the \p DomainType. If linking \p expr and \p lib
144 /// fails, issue a TF_RUNTIME_ERROR with a message, and return an empty program.
145 template <class DomainType>
149 {
150  using Expr = SdfPredicateExpression;
151  using Program = SdfPredicateProgram<DomainType>;
152 
153  // Walk expr and populate prog, binding calls with lib.
154 
155  Program prog;
156  std::string errs;
157 
158  auto exprToProgramOp = [](Expr::Op op) {
159  switch (op) {
160  case Expr::Call: return Program::Call;
161  case Expr::Not: return Program::Not;
162  case Expr::ImpliedAnd: case Expr::And: return Program::And;
163  case Expr::Or: return Program::Or;
164  };
165  return static_cast<typename Program::_Op>(-1);
166  };
167 
168  auto translateLogic = [&](Expr::Op op, int argIndex) {
169  switch (op) {
170  case Expr::Not: // Not is postfix, RPN-style.
171  if (argIndex == 1) {
172  prog._ops.push_back(Program::Not);
173  }
174  break;
175  case Expr::ImpliedAnd: // Binary logic ops are infix to facilitate
176  case Expr::And: // short-circuiting.
177  case Expr::Or:
178  if (argIndex == 1) {
179  prog._ops.push_back(exprToProgramOp(op));
180  prog._ops.push_back(Program::Open);
181  }
182  else if (argIndex == 2) {
183  prog._ops.push_back(Program::Close);
184  }
185  break;
186  case Expr::Call:
187  break; // do nothing, handled in translateCall.
188  };
189  };
190 
191  auto translateCall = [&](Expr::FnCall const &call) {
192  // Try to bind the call against library overloads. If successful,
193  // insert a call op and the function.
194  if (auto fn = lib._BindCall(call.funcName, call.args)) {
195  prog._funcs.push_back(std::move(fn));
196  prog._ops.push_back(Program::Call);
197  }
198  else {
199  if (!errs.empty()) {
200  errs += ", ";
201  }
202  errs += "Failed to bind call of " + call.funcName;
203  }
204  };
205 
206  // Walk the expression and build the "compiled" program.
207  expr.Walk(translateLogic, translateCall);
208 
209  if (!errs.empty()) {
210  prog = {};
211  TF_RUNTIME_ERROR(errs);
212  }
213  return prog;
214 }
215 
217 
218 #endif // PXR_USD_SDF_PREDICATE_PROGRAM_H
typename SdfPredicateLibrary< DomainType >::PredicateFunction PredicateFunction
SDF_API void Walk(TfFunctionRef< void(Op, int)> logic, TfFunctionRef< void(FnCall const &)> call) const
**But if you need a result
Definition: thread.h:622
OutGridT const XformOp bool bool
#define TF_RUNTIME_ERROR
void SetAndPropagateConstancy(SdfPredicateFunctionResult other)
static SdfPredicateFunctionResult MakeConstant(bool value)
Create with value and 'ConstantOverDescendants'.
SdfPredicateProgram< DomainType > SdfLinkPredicateExpression(SdfPredicateExpression const &expr, SdfPredicateLibrary< DomainType > const &lib)
std::function< SdfPredicateFunctionResult(DomainType const &)> PredicateFunction
The type of a bound function, the result of binding passed arguments.
SdfPredicateFunctionResult operator()(DomainType const &obj) const
Run the predicate program on obj, and return the result.
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74