pyPolymorphic.h
Go to the documentation of this file.
1 //
2 // Copyright 2016 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_BASE_TF_PY_POLYMORPHIC_H
25 #define PXR_BASE_TF_PY_POLYMORPHIC_H
26 
28 
29 #include "pxr/pxr.h"
30 
31 #include "pxr/base/tf/pyOverride.h"
32 
33 #include "pxr/base/tf/refPtr.h"
34 #include "pxr/base/tf/weakPtr.h"
35 #include "pxr/base/tf/diagnostic.h"
36 #include "pxr/base/tf/pyCall.h"
37 #include "pxr/base/tf/pyLock.h"
38 #include "pxr/base/tf/type.h"
39 
40 #include <boost/python/object/class_detail.hpp>
41 #include <boost/python/wrapper.hpp>
42 #include <boost/type_traits.hpp>
43 #include <boost/python/has_back_reference.hpp>
44 
45 #include <functional>
46 #include <type_traits>
47 
48 // TODO: All this stuff with holding onto the class needs to go away.
49 
50 PXR_NAMESPACE_OPEN_SCOPE
51 
52 template <typename Derived>
53 struct TfPyPolymorphic :
54  public TfType::PyPolymorphicBase,
55  public boost::python::wrapper<Derived>
56 {
57  typedef TfPyPolymorphic<Derived> This;
58  typedef TfPyOverride Override;
59 
60  Override GetOverride(char const *func) const {
61  TfPyLock pyLock;
62 
63  using namespace boost::python;
64 
65  // don't use boost::python::wrapper::get_override(), as it can return
66  // the wrong result. instead, implement our own version which does
67  // better
68 
69  PyObject * m_self = detail::wrapper_base_::get_owner(*this);
70  if (m_self) {
71 
72  // using pythons mro, get the attribute string that represents
73  // the named function. this will return something valid if it exists
74  // in this or any ancestor class
75  if (handle<> m = handle<>(
76  allow_null(
77  PyObject_GetAttrString(
78  m_self, const_cast<char*>(func))))
79  )
80  {
81  // now get the typehandle to the class. we will use this to
82  // determine if this method exists on the derived class
83  type_handle typeHandle =
84  objects::registered_class_object(
85  typeid(Derived));
86  PyTypeObject* class_object = typeHandle.get();
87 
88  PyObject* func_object = 0;
89 
90  if (
91  PyMethod_Check(m.get())
92  && ((PyMethodObject*)m.get())->im_self == m_self
93  && class_object->tp_dict != 0
94  )
95  {
96  // look for the method on the class object.
97  handle<> borrowed_f(
98  allow_null(
99  PyObject_GetAttrString(
100  (PyObject *)class_object,
101  const_cast<char*>(func))));
102 
103  // Don't leave an exception if there's no base class method
104  PyErr_Clear();
105 
106  // do the appropriate conversion, if possible
107 #if PY_MAJOR_VERSION > 2
108  if (borrowed_f && PyCallable_Check(borrowed_f.get())) {
109  func_object = borrowed_f.get();
110  }
111 #else
112  if (borrowed_f && PyMethod_Check(borrowed_f.get())) {
113  func_object =
114  ((PyMethodObject*)borrowed_f.get())->im_func;
115  }
116 #endif
117  }
118 
119  // now, func_object is either NULL, or pointing at the method
120  // on the class or one of it's ancestors. m is holding the
121  // actual method that pythons mro would find. if that thing
122  // is not the same, it must be an override
123  if (func_object != ((PyMethodObject*)m.get())->im_func)
124  return Override(m);
125  }
126  }
127  PyErr_Clear(); // Don't leave an exception if there's no override.
128 
129  return Override(handle<>(detail::none()));
130  }
131 
132  Override GetPureOverride(char const *func) const {
133  TfPyLock pyLock;
134  Override ret = GetOverride(func);
135  if (!ret) {
136  // Raise a *python* exception when no virtual is found. This is
137  // because a subsequent attempt to call ret will result in a python
138  // exception, but a far less useful one. If we were to simply make
139  // a TfError here, it would be trumped by that python exception.
140  PyErr_SetString(PyExc_AttributeError, TfStringPrintf
141  ("Pure virtual method '%s' called -- "
142  "must provide a python implementation.",
143  func).c_str());
144  TfPyConvertPythonExceptionToTfErrors();
145  }
146  return ret;
147  }
148 
149  template <typename Ret>
150  TfPyCall<Ret> CallPureVirtual(char const *func) const {
151  TfPyLock lock;
152  return TfPyCall<Ret>(GetPureOverride(func));
153  }
154 
155  template <class Ret, class Cls, typename... Arg>
156  std::function<Ret (Arg...)>
157  CallVirtual(
158  char const *fname,
159  Ret (Cls::*defaultImpl)(Arg...));
160 
161  template <class Ret, class Cls, typename... Arg>
162  std::function<Ret (Arg...)>
163  CallVirtual(
164  char const *fname,
165  Ret (Cls::*defaultImpl)(Arg...) const) const;
166 
167 protected:
168  virtual ~TfPyPolymorphic();
169 
170 private:
171 
172  // Helper to bind a pointer-to-member-function and a pointer to an
173  // instance.
174  template <class Ret, class Cls, typename... Args>
175  struct _BindMemFn
176  {
177  using MemFn = typename std::conditional<
178  std::is_const<Cls>::value,
179  Ret (Cls::*)(Args...) const, Ret (Cls::*)(Args...)>::type;
180 
181  _BindMemFn(MemFn memFn, Cls *obj)
182  : _memFn(memFn)
183  , _obj(obj)
184  {}
185 
186  Ret
187  operator()(Args... args) const
188  {
189  return (_obj->*_memFn)(args...);
190  }
191 
192  private:
193  MemFn _memFn;
194  Cls *_obj;
195  };
196 };
197 
198 template <typename Derived>
199 TfPyPolymorphic<Derived>::~TfPyPolymorphic()
200 {
201 }
202 
203 template <typename Derived>
204 template <class Ret, class Cls, typename... Args>
205 inline
206 std::function<Ret (Args...)>
207 TfPyPolymorphic<Derived>::CallVirtual(
208  char const *fname,
209  Ret (Cls::*defaultImpl)(Args...))
210 {
211  static_assert(std::is_base_of<This, Cls>::value,
212  "This must be a base of Cls.");
213  TfPyLock lock;
214  if (Override o = GetOverride(fname))
215  return std::function<Ret (Args...)>(TfPyCall<Ret>(o));
216  return _BindMemFn<Ret, Cls, Args...>(
217  defaultImpl, static_cast<Cls *>(this));
218 }
219 
220 template <typename Derived>
221 template <class Ret, class Cls, typename... Args>
222 inline
223 std::function<Ret (Args...)>
224 TfPyPolymorphic<Derived>::CallVirtual(
225  char const *fname,
226  Ret (Cls::*defaultImpl)(Args...) const) const
227 {
228  static_assert(std::is_base_of<This, Cls>::value,
229  "This must be a base of Cls.");
230  TfPyLock lock;
231  if (Override o = GetOverride(fname))
232  return std::function<Ret (Args...)>(TfPyCall<Ret>(o));
233  return _BindMemFn<Ret, Cls const, Args...>(
234  defaultImpl, static_cast<Cls const *>(this));
235 }
236 
237 PXR_NAMESPACE_CLOSE_SCOPE
238 
239 // Specialize has_back_reference<> so that boost.python will pass
240 // PyObject* as the 1st argument to TfPyPolymorphic's ctor.
241 namespace boost { namespace python {
242  template <typename T>
243  struct has_back_reference< PXR_NS::TfPyPolymorphic<T> >
244  : mpl::true_ {};
245 }} // end namespace boost
246 
247 PXR_NAMESPACE_OPEN_SCOPE
248 
249 // Base case for internal Tf_PyMemberFunctionPointerUpcast.
250 template <typename Base, typename Fn>
251 struct Tf_PyMemberFunctionPointerUpcast;
252 
253 template <typename Base, typename Derived,
254  typename Ret, typename... Args>
255 struct Tf_PyMemberFunctionPointerUpcast< Base, Ret (Derived::*)(Args...) >
256 {
257  typedef Ret (Base::*Type)(Args...);
258 };
259 
260 template <typename Base, typename Derived,
261  typename Ret, typename... Args>
262 struct Tf_PyMemberFunctionPointerUpcast< Base, Ret (Derived::*)(Args...) const >
263 {
264  typedef Ret (Base::*Type)(Args...) const;
265 };
266 
267 template <typename Base, typename Fn>
268 typename Tf_PyMemberFunctionPointerUpcast<Base, Fn>::Type
269 TfPyProtectedVirtual( Fn fn )
270 {
271  typedef typename Tf_PyMemberFunctionPointerUpcast<Base, Fn>::Type Ret;
272 
273  return static_cast<Ret>(fn);
274 }
275 
276 PXR_NAMESPACE_CLOSE_SCOPE
277 
278 #endif // PXR_BASE_TF_PY_POLYMORPHIC_H
TF_API std::string TfStringPrintf(const char *fmt,...)
Returns a string formed by a printf()-like specification.
Utilities for calling python callables.
Low-level utilities for informing users of various internal and external diagnostic conditions.
Pointer storage with deletion detection.
A reimplementation of boost::python::override.
Definition: pyOverride.h:114
Provide a way to call a Python callable.
Definition: pyCall.h:57
Reference counting.