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