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