Loading...
Searching...
No Matches
predicateProgram.h
1//
2// Copyright 2023 Pixar
3//
4// Licensed under the Apache License, Version 2.0 (the "Apache License")
5// with the following modification; you may not use this file except in
6// compliance with the Apache License and the following modification to it:
7// Section 6. Trademarks. is deleted and replaced with:
8//
9// 6. Trademarks. This License does not grant permission to use the trade
10// names, trademarks, service marks, or product names of the Licensor
11// and its affiliates, except as required to comply with Section 4(c) of
12// the License and to reproduce the content of the NOTICE file.
13//
14// You may obtain a copy of the Apache License at
15//
16// http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software
19// distributed under the Apache License with the above modification is
20// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21// KIND, either express or implied. See the Apache License for the specific
22// language governing permissions and limitations under the Apache License.
23//
24#ifndef PXR_USD_SDF_PREDICATE_PROGRAM_H
25#define PXR_USD_SDF_PREDICATE_PROGRAM_H
26
27#include "pxr/pxr.h"
28#include "pxr/usd/sdf/api.h"
29
31#include "pxr/base/tf/functionTraits.h"
32#include "pxr/base/vt/value.h"
33
34#include "pxr/usd/sdf/predicateExpression.h"
35#include "pxr/usd/sdf/predicateLibrary.h"
36#include "pxr/usd/sdf/invoke.hpp"
37
38#include <initializer_list>
39#include <memory>
40#include <string>
41#include <vector>
42
43PXR_NAMESPACE_OPEN_SCOPE
44
45// fwd decl
46template <class DomainType>
48
49// fwd decl
50template <class DomainType>
54
67template <class DomainType>
69{
70public:
71 using PredicateFunction =
73
75 SdfLinkPredicateExpression<DomainType>(
76 SdfPredicateExpression const &expr,
78
80 explicit operator bool() const {
81 return !_ops.empty();
82 }
83
86 operator()(DomainType const &obj) const {
89 int nest = 0;
90 auto funcIter = _funcs.cbegin();
91 auto opIter = _ops.cbegin(), opEnd = _ops.cend();
92
93 // The current implementation favors short-circuiting over constance
94 // propagation. It might be beneficial to avoid short-circuiting when
95 // constancy isn't known, in hopes of establishing constancy. For
96 // example, if we have 'A or B', and 'A' evaluates to 'true' with
97 // MayVaryOverDescendants, we will skip evaluating B
98 // (short-circuit). This means we would miss the possibility of
99 // upgrading the constancy in case B returned 'true' with
100 // ConstantOverDescendants. This isn't a simple switch to flip though;
101 // we'd have to do some code restructuring here.
102 //
103 // For posterity, the rules for propagating constancy are the following,
104 // where A and B are the truth-values, and c(A), c(B), are whether or
105 // not the constancy is ConstantOverDescendants for A, B, respectively:
106 //
107 // c(A or B) = (A and c(A)) or (B and c(B)) or (c(A) and c(B))
108 // c(A and B) = (!A and c(A)) or (!B and c(B)) or (c(A) and c(B))
109
110 // Helper for short-circuiting "and" and "or" operators. Advance,
111 // ignoring everything until we reach the next Close that brings us to
112 // the starting nest level.
113 auto shortCircuit = [&]() {
114 const int origNest = nest;
115 for (; opIter != opEnd; ++opIter) {
116 switch(*opIter) {
117 case Call: ++funcIter; break; // Skip calls.
118 case Not: case And: case Or: break; // Skip operations.
119 case Open: ++nest; break;
120 case Close:
121 if (--nest == origNest) {
122 return;
123 }
124 break;
125 };
126 }
127 };
128
129 // Evaluate the predicate expression by processing operations and
130 // invoking predicate functions.
131 for (; opIter != opEnd; ++opIter) {
132 switch (*opIter) {
133 case Call:
134 result.SetAndPropagateConstancy((*funcIter++)(obj));
135 break;
136 case Not: result = !result; break;
137 case And: case Or: {
138 const bool decidingValue = *opIter != And;
139 // If the and/or result is already the deciding value,
140 // short-circuit. Otherwise the result is the rhs, so continue.
141 if (result == decidingValue) {
142 shortCircuit();
143 }
144 }
145 break;
146 case Open: ++nest; break;
147 case Close: --nest; break;
148 };
149 }
150 return result;
151 }
152
153private:
154 enum _Op { Call, Not, Open, Close, And, Or };
155 std::vector<_Op> _ops;
156 std::vector<PredicateFunction> _funcs;
157};
158
159
163template <class DomainType>
165SdfLinkPredicateExpression(SdfPredicateExpression const &expr,
167{
168 using Expr = SdfPredicateExpression;
169 using Program = SdfPredicateProgram<DomainType>;
170
171 // Walk expr and populate prog, binding calls with lib.
172
173 Program prog;
174 std::string errs;
175
176 auto exprToProgramOp = [](Expr::Op op) {
177 switch (op) {
178 case Expr::Call: return Program::Call;
179 case Expr::Not: return Program::Not;
180 case Expr::ImpliedAnd: case Expr::And: return Program::And;
181 case Expr::Or: return Program::Or;
182 };
183 return static_cast<typename Program::_Op>(-1);
184 };
185
186 auto translateLogic = [&](Expr::Op op, int argIndex) {
187 switch (op) {
188 case Expr::Not: // Not is postfix, RPN-style.
189 if (argIndex == 1) {
190 prog._ops.push_back(Program::Not);
191 }
192 break;
193 case Expr::ImpliedAnd: // Binary logic ops are infix to facilitate
194 case Expr::And: // short-circuiting.
195 case Expr::Or:
196 if (argIndex == 1) {
197 prog._ops.push_back(exprToProgramOp(op));
198 prog._ops.push_back(Program::Open);
199 }
200 else if (argIndex == 2) {
201 prog._ops.push_back(Program::Close);
202 }
203 break;
204 case Expr::Call:
205 break; // do nothing, handled in translateCall.
206 };
207 };
208
209 auto translateCall = [&](Expr::FnCall const &call) {
210 // Try to bind the call against library overloads. If successful,
211 // insert a call op and the function.
212 if (auto fn = lib._BindCall(call.funcName, call.args)) {
213 prog._funcs.push_back(std::move(fn));
214 prog._ops.push_back(Program::Call);
215 }
216 else {
217 if (!errs.empty()) {
218 errs += ", ";
219 }
220 errs += "Failed to bind call of " + call.funcName;
221 }
222 };
223
224 // Walk the expression and build the "compiled" program.
225 expr.Walk(translateLogic, translateCall);
226
227 if (!errs.empty()) {
228 prog = {};
229 TF_RUNTIME_ERROR(errs);
230 }
231 return prog;
232}
233
234PXR_NAMESPACE_CLOSE_SCOPE
235
236#endif // PXR_USD_SDF_PREDICATE_PROGRAM_H
Low-level utilities for informing users of various internal and external diagnostic conditions.
Represents a logical expression syntax tree consisting of predicate function calls joined by the logi...
SDF_API void Walk(TfFunctionRef< void(Op, int)> logic, TfFunctionRef< void(FnCall const &)> call) const
Walk this expression's syntax tree in depth-first order, calling call with the current function call ...
Represents the result of a predicate function: a pair of the boolean result and a Constancy token ind...
static SdfPredicateFunctionResult MakeConstant(bool value)
Create with value and 'ConstantOverDescendants'.
void SetAndPropagateConstancy(SdfPredicateFunctionResult other)
Set this result's value to other's value, and propagate constancy; if both this and other are Constan...
Represents a library of predicate functions for use with SdfPredicateExpression.
std::function< SdfPredicateFunctionResult(DomainType const &)> PredicateFunction
The type of a bound function, the result of binding passed arguments.
Represents a callable "program", the result of linking an SdfPredicateExpression with an SdfPredicate...
friend SdfPredicateProgram SdfLinkPredicateExpression(SdfPredicateExpression const &expr, SdfPredicateLibrary< DomainType > const &lib)
Link expr with lib and return a callable program that evaluates expr on given objects of the DomainTy...
SdfPredicateFunctionResult operator()(DomainType const &obj) const
Run the predicate program on obj, and return the result.
#define TF_RUNTIME_ERROR(fmt, args)
Issue a generic runtime error, but continue execution.
Definition: diagnostic.h:100