All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
pyPtrHelpers.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_PTR_HELPERS_H
8#define PXR_BASE_TF_PY_PTR_HELPERS_H
9
12
13#include "pxr/pxr.h"
14
15#include "pxr/base/tf/pyIdentity.h"
16#include "pxr/base/tf/pyObjectFinder.h"
17#include "pxr/base/tf/wrapTypeHelpers.h"
18
21#include "pxr/base/tf/refPtr.h"
23#include "pxr/base/tf/weakPtr.h"
25
26#include "pxr/external/boost/python/class.hpp"
27#include "pxr/external/boost/python/converter/from_python.hpp"
28#include "pxr/external/boost/python/converter/registered.hpp"
29#include "pxr/external/boost/python/converter/registrations.hpp"
30#include "pxr/external/boost/python/converter/registry.hpp"
31#include "pxr/external/boost/python/converter/rvalue_from_python_data.hpp"
32#include "pxr/external/boost/python/converter/to_python_function_type.hpp"
33#include "pxr/external/boost/python/def_visitor.hpp"
34#include "pxr/external/boost/python/handle.hpp"
35#include "pxr/external/boost/python/implicit.hpp"
36#include "pxr/external/boost/python/to_python_converter.hpp"
37
38#include <memory>
39#include <type_traits>
40
41PXR_NAMESPACE_OPEN_SCOPE
42
43//
44// Boost.Python def visitors for wrapping objects held by weak pointers. This
45// will create a read-only property on your class called 'expired' which is
46// true if the object has expired. This also adds an implementation for __eq__
47// and __ne__ which compare the pointers for equality and non-equality,
48// respectively.
49//
50// Example usage:
51//
52// class_<MyClass, MyClassPtr>("MyClass", no_init)
53// .def(TfPyWeakPtr())
54// .def(...)
55// ...
56//
57
58// Helper class to return or create a PyObject holder for a Ptr. This
59// can be specialized for custom behavior.
60template <typename Ptr>
61struct TfMakePyPtr {
62 typedef typename Ptr::DataType Pointee;
63 typedef pxr_boost::python::objects::pointer_holder<Ptr, Pointee> Holder;
64 typedef std::pair<PyObject*, bool> Result;
65
66 // Return an existing PyObject for the pointer paired with false or
67 // create and return a new PyObject paired with true. The PyObject
68 // ref count must have been incremented.
69 static Result Execute(Ptr const& p)
70 {
71 // null pointers -> python None.
72 if (!p.GetUniqueIdentifier())
73 return Result(pxr_boost::python::detail::none(), false);
74
75 // Force instantiation. We must do this before checking if we
76 // have a python identity, otherwise the identity might be set
77 // during instantiation and our caller will attempt to set it
78 // again, which isn't allowed.
79 get_pointer(p);
80
81 if (PyObject *id = Tf_PyGetPythonIdentity(p))
82 return Result(id, false);
83
84 // Just make a new python object holding this pointer.
85 // TODO: use existing to-python conversion?
86 PyObject *res = pxr_boost::python::objects::make_ptr_instance
87 <Pointee, Holder>::execute(p);
88 // If we got back Py_None, no new object was made, so make sure
89 // to pass back false in result.
90 return Result(res, res != Py_None);
91 }
92};
93
94namespace Tf_PyDefHelpers {
95
96using namespace pxr_boost::python;
97
98template <typename Ptr>
99struct _PtrInterface {
100 typedef typename Ptr::DataType Pointee;
101 using ConstPointee = std::add_const_t<Pointee>;
102 using NonConstPointee = std::remove_const_t<Pointee>;
103
104 template <typename U>
105 struct Rebind {
106 typedef typename Ptr::template Rebind<U>::Type Type;
107 };
108
109 typedef typename Rebind<ConstPointee>::Type ConstPtr;
110 typedef typename Rebind<NonConstPointee>::Type NonConstPtr;
111
112};
113
114template <typename PtrType>
115bool _IsPtrExpired(object const &self) {
116 try {
117 PtrType p = extract<PtrType>(self);
118 return !p;
119 } catch (pxr_boost::python::error_already_set const &) {
120 PyErr_Clear();
121 return true;
122 }
123}
124
125template <typename PtrType>
126bool _IsPtrValid(object const &self) {
127 return !_IsPtrExpired<PtrType>(self);
128}
129
130template <typename PtrType>
131bool _ArePtrsEqual(PtrType const &self,
132 PtrType const &other) { return self == other; }
133template <typename PtrType>
134bool _ArePtrsNotEqual(PtrType const &self,
135 PtrType const &other) { return self != other; }
136template <typename PtrType>
137bool _ArePtrsLessThan(PtrType const &self,
138 PtrType const &other) { return self < other; }
139
140
141// Default ownership policy does nothing.
142template <class PtrType>
143struct _PtrFromPythonConversionPolicy {
144 static void Apply(PtrType const &, PyObject *) { }
145};
146
147// Ownership policy for ref ptrs when going from python to c++ is to
148// transfer ownership (remove ownership from python if it has it).
149template <typename T>
150struct _PtrFromPythonConversionPolicy<TfRefPtr<T> > {
151 static void Apply(TfRefPtr<T> const &p, PyObject *obj) {
152 Tf_PyRemovePythonOwnership(p, obj);
153 }
154};
155
156template <class Ptr>
157struct _PtrFromPython {
158 typedef typename _PtrInterface<Ptr>::Pointee Pointee;
159 _PtrFromPython() {
160 converter::registry::insert(&convertible, &construct,
161 type_id<Ptr>());
162 }
163 private:
164 static void *convertible(PyObject *p) {
165 if (p == Py_None)
166 return p;
167 void *result = converter::get_lvalue_from_python
168 (p, converter::registered<Pointee>::converters);
169 return result;
170 }
171
172 static void construct(PyObject* source, converter::
173 rvalue_from_python_stage1_data* data) {
174 void* const storage = ((converter::rvalue_from_python_storage<Ptr>*)
175 data)->storage.bytes;
176 // Deal with the "None" case.
177 if (data->convertible == source)
178 new (storage) Ptr();
179 else {
180 Ptr ptr(static_cast<Pointee*>(data->convertible));
181 new (storage) Ptr(ptr);
182 _PtrFromPythonConversionPolicy<Ptr>::Apply(ptr, source);
183 // Set ptr's python object to source if the pointer is valid.
184 if (ptr)
185 Tf_PySetPythonIdentity(ptr, source);
186 }
187 data->convertible = storage;
188 }
189};
190
191// Converter from python to AnyWeakPtr. We use this converter to wrap
192// the weak-pointable object into an AnyWeakPtr when we don't know what
193// specific C++ type it has--for example, see wrapNotice.cpp.
194template <typename PtrType>
195struct _AnyWeakPtrFromPython {
196
197 _AnyWeakPtrFromPython() {
198 converter::registry::insert(&convertible, &construct,
199 type_id<TfAnyWeakPtr>());
200 }
201
202 static void *convertible(PyObject *p) {
203 if (p == Py_None)
204 return p;
205 void *result = converter::get_lvalue_from_python
206 (p, converter::registered
207 <typename _PtrInterface<PtrType>::Pointee>::converters);
208 return result;
209 }
210
211 static void construct(PyObject* source, converter::
212 rvalue_from_python_stage1_data* data) {
213 void* const storage = ((converter::rvalue_from_python_storage
214 <TfAnyWeakPtr>*)data)->storage.bytes;
215 // Deal with the "None" case.
216 if (data->convertible == source)
217 new (storage) TfAnyWeakPtr();
218 else {
219 typedef typename _PtrInterface<PtrType>::Pointee T;
220 T *ptr = static_cast<T*>(data->convertible);
221 PtrType smartPtr(ptr);
222 new (storage) TfAnyWeakPtr(smartPtr);
223
224 }
225 data->convertible = storage;
226 }
227};
228
229template <typename Ptr>
230struct _ConstPtrToPython {
231 typedef typename _PtrInterface<Ptr>::ConstPtr ConstPtr;
232 typedef typename _PtrInterface<Ptr>::NonConstPtr NonConstPtr;
233 _ConstPtrToPython() {
234 to_python_converter<ConstPtr, _ConstPtrToPython<Ptr> >();
235 }
236 static PyObject *convert(ConstPtr const &p) {
237 return incref(object(TfConst_cast<NonConstPtr>(p)).ptr());
238 }
239};
240
241template <typename Ptr>
242struct _PtrToPython {
243 _PtrToPython() {
244 to_python_converter<Ptr, _PtrToPython<Ptr> >();
245 }
246 static PyObject *convert(Ptr const &p) {
247 std::pair<PyObject*, bool> ret = TfMakePyPtr<Ptr>::Execute(p);
248 if (ret.second) {
249 Tf_PySetPythonIdentity(p, ret.first);
250 }
251 return ret.first;
252 }
253};
254
255template <typename SrcPtr, typename DstPtr>
256struct _ConvertPtrToPython {
257 _ConvertPtrToPython() {
258 to_python_converter<SrcPtr, _ConvertPtrToPython<SrcPtr, DstPtr> >();
259 }
260 static PyObject *convert(SrcPtr const &p) {
261 DstPtr dst = p;
262 return incref(object(dst).ptr());
263 }
264};
265
266template <typename Ptr>
267struct _PtrToPythonWrapper {
268
269 // We store the original to-python converter for our use. It's fine to be
270 // static, as there's only one to-python converter for a type T, and there's
271 // one instantiation of this template for each T.
272 static converter::to_python_function_t _originalConverter;
273
274 // This signature has to match to_python_function_t
275 static PyObject *Convert(void const *x) {
276 // See pxr/external/boost/python/converter/as_to_python_function.hpp
277 Ptr const &p = *static_cast<Ptr const *>(x);
278
279 std::pair<PyObject*, bool> ret = TfMakePyPtr<Ptr>::Execute(p);
280 if (ret.first == Py_None) {
281 // Fallback to the original converter.
282 Py_DECREF(ret.first);
283 ret.first = _originalConverter(x);
284 }
285 if (ret.second) {
286 Tf_PySetPythonIdentity(p, ret.first);
287 }
288 return ret.first;
289 }
290};
291template <typename T>
292converter::to_python_function_t
293_PtrToPythonWrapper<T>::_originalConverter = 0;
294
295struct WeakPtr : def_visitor<WeakPtr> {
296 friend class def_visitor_access;
297
298 template <typename WrapperPtrType, typename Wrapper, typename T>
299 static void _RegisterConversions(Wrapper *, T *) {
300 _RegisterConversionsHelper<WrapperPtrType, Wrapper, T>();
301 }
302
303 template <typename WrapperPtrType, typename Wrapper, typename T>
304 static void _RegisterConversionsHelper() {
305
306 static_assert(std::is_same<
307 typename _PtrInterface<WrapperPtrType>::Pointee,
308 Wrapper>::value,
309 "Pointee must be same type as Wrapper.");
310
311 typedef typename
312 _PtrInterface<WrapperPtrType>::template Rebind<T>::Type PtrType;
313
314 // Register the from-python conversion.
315 _PtrFromPython<PtrType>();
316
317 // Register AnyWeakPtr from python conversion.
318 _AnyWeakPtrFromPython<PtrType>();
319
320 // From python, can always make a const pointer from a non-const one.
321 implicitly_convertible<PtrType,
322 typename _PtrInterface<PtrType>::ConstPtr >();
323
324 // Register a conversion that casts away constness when going to python.
325 _ConstPtrToPython<PtrType>();
326
327 // Replace the existing to_python conversion for weakptr<wrapper> to do
328 // object id first. It would be great if we could get better support in
329 // boost python for doing this sort of thing.
330 //
331 // We do this for wrapper because this is the "wrapped type" -- the type
332 // for which boost python has already registered a to-python
333 // conversion. The unwrapped type is handled separately -- we don't
334 // have to replace an existing converter, we can just register our own.
335 converter::registration *r = const_cast<converter::registration *>
336 (converter::registry::query(type_id<WrapperPtrType>()));
337 if (r) {
338 _PtrToPythonWrapper<WrapperPtrType>::
339 _originalConverter = r->m_to_python;
340 r->m_to_python = _PtrToPythonWrapper<WrapperPtrType>::Convert;
341 } else {
342 // CODE_COVERAGE_OFF Can only happen if there's a bug.
343 TF_CODING_ERROR("No python registration for '%s'!",
344 ArchGetDemangled(typeid(WrapperPtrType)).c_str());
345 // CODE_COVERAGE_ON
346 }
347
348 if (!std::is_same<Wrapper, T>::value)
349 _PtrToPython<PtrType>();
350
351 }
352
353 template <typename PtrType, typename CLS, typename Wrapper, typename T>
354 static void _AddAPI(CLS &c, Wrapper *, T *) {
355 typedef typename
356 _PtrInterface<PtrType>::template Rebind<T>::Type UnwrappedPtrType;
357 // Add 'expired' property and (in)equality testing.
358 c.add_property("expired", _IsPtrExpired<UnwrappedPtrType>,
359 (const char *)
360 "True if this object has expired, False otherwise.");
361 c.def("__bool__", _IsPtrValid<UnwrappedPtrType>,
362 (char const *)
363 "True if this object has not expired. False otherwise.");
364 c.def("__eq__", _ArePtrsEqual<UnwrappedPtrType>,
365 "Equality operator: x == y");
366 c.def("__ne__", _ArePtrsNotEqual<UnwrappedPtrType>,
367 "Non-equality operator: x != y");
368 c.def("__lt__", _ArePtrsLessThan<UnwrappedPtrType>,
369 "Less than operator: x < y");
370 c.def( TfTypePythonClass() );
371 }
372
373 template <typename CLS>
374 void visit(CLS &c) const {
375 typedef typename CLS::wrapped_type Type;
376 typedef typename CLS::metadata::held_type_arg PtrType;
377 static_assert(TF_SUPPORTS_WEAKPTR(Type),
378 "Type must support TfWeakPtr.");
379 // Register conversions
380 _RegisterConversions<PtrType>
381 ((Type *)0, detail::unwrap_wrapper((Type *)0));
382
383 // Register a PyObjectFinder.
384 Tf_RegisterPythonObjectFinder<Type, PtrType>();
385
386 // Add weak ptr api.
387 _AddAPI<PtrType>(c, (Type *)0, detail::unwrap_wrapper((Type *)0));
388 }
389};
390
391struct RefAndWeakPtr : def_visitor<RefAndWeakPtr> {
392 friend class def_visitor_access;
393
394 template <typename CLS, typename Wrapper, typename T>
395 static void _AddAPI(Wrapper *, T *) {
396 _PtrFromPython<TfRefPtr<T> >();
397 typedef typename
398 _PtrInterface<typename CLS::metadata::held_type>::template
399 Rebind<T>::Type PtrType;
400 _ConvertPtrToPython<TfRefPtr<T>, PtrType>();
401 }
402
403 template <typename CLS>
404 void visit(CLS &c) const {
405 typedef typename CLS::wrapped_type Type;
406 static_assert(TF_SUPPORTS_REFPTR(Type),
407 "Type must support TfRefPtr.");
408 // Same as weak ptr plus ref conversions.
409 WeakPtr().visit(c);
410 _AddAPI<CLS>((Type *)0, detail::unwrap_wrapper((Type *)0));
411 }
412};
413
414};
415
416struct TfPyWeakPtr : Tf_PyDefHelpers::WeakPtr {};
417struct TfPyRefAndWeakPtr : Tf_PyDefHelpers::RefAndWeakPtr {};
418
419PXR_NAMESPACE_CLOSE_SCOPE
420
421#endif // PXR_BASE_TF_PY_PTR_HELPERS_H
Type independent WeakPtr holder class.
Low-level utilities for informing users of various internal and external diagnostic conditions.
Provides the ability to hold an arbitrary TfWeakPtr in a non-type-specific manner in order to observe...
Definition: anyWeakPtr.h:37
Reference-counted smart pointer utility class.
Definition: refPtr.h:590
Demangle C++ typenames generated by the typeid() facility.
std::string ArchGetDemangled()
Return demangled RTTI generated-type name.
Definition: demangle.h:86
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition: diagnostic.h:68
Reference counting.
Definitions of basic string utilities in tf.
A boost.python visitor that associates the Python class object created by the wrapping with the TfTyp...
Pointer storage with deletion detection.