All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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 (borrowed_f && PyMethod_Check(borrowed_f.get())) {
108  func_object =
109  ((PyMethodObject*)borrowed_f.get())->im_func;
110  }
111  }
112 
113  // now, func_object is either NULL, or pointing at the method
114  // on the class or one of it's ancestors. m is holding the
115  // actual method that pythons mro would find. if that thing
116  // is not the same, it must be an override
117  if (func_object != ((PyMethodObject*)m.get())->im_func)
118  return Override(m);
119  }
120  }
121  PyErr_Clear(); // Don't leave an exception if there's no override.
122 
123  return Override(handle<>(detail::none()));
124  }
125 
126  Override GetPureOverride(char const *func) const {
127  TfPyLock pyLock;
128  Override ret = GetOverride(func);
129  if (!ret) {
130  // Raise a *python* exception when no virtual is found. This is
131  // because a subsequent attempt to call ret will result in a python
132  // exception, but a far less useful one. If we were to simply make
133  // a TfError here, it would be trumped by that python exception.
134  PyErr_SetString(PyExc_AttributeError, TfStringPrintf
135  ("Pure virtual method '%s' called -- "
136  "must provide a python implementation.",
137  func).c_str());
138  TfPyConvertPythonExceptionToTfErrors();
139  }
140  return ret;
141  }
142 
143  template <typename Ret>
144  TfPyCall<Ret> CallPureVirtual(char const *func) const {
145  TfPyLock lock;
146  return TfPyCall<Ret>(GetPureOverride(func));
147  }
148 
149  template <class Ret, class Cls, typename... Arg>
150  std::function<Ret (Arg...)>
151  CallVirtual(
152  char const *fname,
153  Ret (Cls::*defaultImpl)(Arg...));
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...) const) const;
160 
161 protected:
162  virtual ~TfPyPolymorphic();
163 
164 private:
165 
166  // Helper to bind a pointer-to-member-function and a pointer to an
167  // instance.
168  template <class Ret, class Cls, typename... Args>
169  struct _BindMemFn
170  {
171  using MemFn = typename std::conditional<
172  std::is_const<Cls>::value,
173  Ret (Cls::*)(Args...) const, Ret (Cls::*)(Args...)>::type;
174 
175  _BindMemFn(MemFn memFn, Cls *obj)
176  : _memFn(memFn)
177  , _obj(obj)
178  {}
179 
180  Ret
181  operator()(Args... args) const
182  {
183  return (_obj->*_memFn)(args...);
184  }
185 
186  private:
187  MemFn _memFn;
188  Cls *_obj;
189  };
190 };
191 
192 template <typename Derived>
193 TfPyPolymorphic<Derived>::~TfPyPolymorphic()
194 {
195 }
196 
197 template <typename Derived>
198 template <class Ret, class Cls, typename... Args>
199 inline
200 std::function<Ret (Args...)>
201 TfPyPolymorphic<Derived>::CallVirtual(
202  char const *fname,
203  Ret (Cls::*defaultImpl)(Args...))
204 {
205  static_assert(std::is_base_of<This, Cls>::value,
206  "This must be a base of Cls.");
207  TfPyLock lock;
208  if (Override o = GetOverride(fname))
209  return std::function<Ret (Args...)>(TfPyCall<Ret>(o));
210  return _BindMemFn<Ret, Cls, Args...>(
211  defaultImpl, static_cast<Cls *>(this));
212 }
213 
214 template <typename Derived>
215 template <class Ret, class Cls, typename... Args>
216 inline
217 std::function<Ret (Args...)>
218 TfPyPolymorphic<Derived>::CallVirtual(
219  char const *fname,
220  Ret (Cls::*defaultImpl)(Args...) const) const
221 {
222  static_assert(std::is_base_of<This, Cls>::value,
223  "This must be a base of Cls.");
224  TfPyLock lock;
225  if (Override o = GetOverride(fname))
226  return std::function<Ret (Args...)>(TfPyCall<Ret>(o));
227  return _BindMemFn<Ret, Cls const, Args...>(
228  defaultImpl, static_cast<Cls const *>(this));
229 }
230 
231 PXR_NAMESPACE_CLOSE_SCOPE
232 
233 // Specialize has_back_reference<> so that boost.python will pass
234 // PyObject* as the 1st argument to TfPyPolymorphic's ctor.
235 namespace boost { namespace python {
236  template <typename T>
237  struct has_back_reference< PXR_NS::TfPyPolymorphic<T> >
238  : mpl::true_ {};
239 }} // end namespace boost
240 
241 PXR_NAMESPACE_OPEN_SCOPE
242 
243 // Base case for internal Tf_PyMemberFunctionPointerUpcast.
244 template <typename Base, typename Fn>
245 struct Tf_PyMemberFunctionPointerUpcast;
246 
247 template <typename Base, typename Derived,
248  typename Ret, typename... Args>
249 struct Tf_PyMemberFunctionPointerUpcast< Base, Ret (Derived::*)(Args...) >
250 {
251  typedef Ret (Base::*Type)(Args...);
252 };
253 
254 template <typename Base, typename Derived,
255  typename Ret, typename... Args>
256 struct Tf_PyMemberFunctionPointerUpcast< Base, Ret (Derived::*)(Args...) const >
257 {
258  typedef Ret (Base::*Type)(Args...) const;
259 };
260 
261 template <typename Base, typename Fn>
262 typename Tf_PyMemberFunctionPointerUpcast<Base, Fn>::Type
263 TfPyProtectedVirtual( Fn fn )
264 {
265  typedef typename Tf_PyMemberFunctionPointerUpcast<Base, Fn>::Type Ret;
266 
267  return static_cast<Ret>(fn);
268 }
269 
270 PXR_NAMESPACE_CLOSE_SCOPE
271 
272 #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.