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