Nektar++
Python/BasicUtils/NekFactory.hpp
Go to the documentation of this file.
1///////////////////////////////////////////////////////////////////////////////
2//
3// File: NekFactory.hpp
4//
5// For more information, please see: http://www.nektar.info
6//
7// The MIT License
8//
9// Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10// Department of Aeronautics, Imperial College London (UK), and Scientific
11// Computing and Imaging Institute, University of Utah (USA).
12//
13// Permission is hereby granted, free of charge, to any person obtaining a
14// copy of this software and associated documentation files (the "Software"),
15// to deal in the Software without restriction, including without limitation
16// the rights to use, copy, modify, merge, publish, distribute, sublicense,
17// and/or sell copies of the Software, and to permit persons to whom the
18// Software is furnished to do so, subject to the following conditions:
19//
20// The above copyright notice and this permission notice shall be included
21// in all copies or substantial portions of the Software.
22//
23// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29// DEALINGS IN THE SOFTWARE.
30//
31// Description: Python convenience wrappers for NekFactory.
32//
33///////////////////////////////////////////////////////////////////////////////
34
35#ifndef NEKTAR_LIBUTILITIES_PYTHON_BASICUTILS_NEKFACTORY_HPP
36#define NEKTAR_LIBUTILITIES_PYTHON_BASICUTILS_NEKFACTORY_HPP
37
40
41#include <functional>
42#include <type_traits>
43#include <utility>
44
45//
46// The following code is adapted from:
47// https://stackoverflow.com/questions/21192659/variadic-templates-and-stdbind
48//
49// The objective is to define an alternative to std::placeholders that can be
50// used in combination with variadic templates to wrap arbitrary functions with
51// arguments defined through a parameter pack.
52//
53
54template <std::size_t> struct placeholder_template
55{
56};
57
58namespace std
59{
60template <std::size_t N>
61struct is_placeholder<placeholder_template<N>>
62 : integral_constant<std::size_t, N + 1>
63{
64};
65} // namespace std
66
67using namespace Nektar;
68
69/**
70 * @brief Helper class to be used in NekFactory_Register. Stores the Python
71 * function used to call the creator function.
72 */
73template <class T> class NekFactoryRegisterHelper
74{
75public:
76 NekFactoryRegisterHelper(py::object obj) : m_obj(obj)
77 {
78 py::incref(obj.ptr());
79 }
80
82 {
83 py::decref(m_obj.ptr());
84 }
85
86 /**
87 * @brief Callback to Python instantiator function (typically a class
88 * constructor). Assumes that all arguments can be converted to boost.python
89 * through converter infrastructure.
90 */
91 template <class... Args> std::shared_ptr<T> create(Args... args)
92 {
93 // Create an object using the Python callable.
94 py::object inst = m_obj(args...);
95
96 // Assume that it returns an object of the appropriate type.
97 return py::extract<std::shared_ptr<T>>(inst);
98 }
99
100protected:
101 /// Python function that is used to construct objects.
102 py::object m_obj;
103};
104
105/**
106 * @brief C-style callback to destroy the #NekFactoryRegisterHelper class
107 * to avoid memory leaks.
108 */
109#if PY_MAJOR_VERSION == 2
110template <class T> void NekFactoryCapsuleDestructor(void *ptr)
111{
113 delete tmp;
114}
115#else
116template <class T> void NekFactoryCapsuleDestructor(PyObject *ptr)
117{
119 (NekFactoryRegisterHelper<T> *)PyCapsule_GetPointer(ptr, nullptr);
120 delete tmp;
121}
122#endif
123
124template <typename tFac> class NekFactory_Register;
125
126/**
127 * @brief Lightweight wrapper for the factory RegisterCreatorFunction, to
128 * support the ability for Python subclasses of @tparam tBase to register
129 * themselves with the NekFactory.
130 *
131 * This function wraps NekFactory::RegisterCreatorFunction. This function
132 * expects a function pointer to a C++ object that will construct an instance of
133 * @tparam tBase. In this case we therefore need to construct a function call
134 * that will construct our Python object (which is a subclass of @tparam tBase),
135 * and then pass this back to Boost.Python to give the Python object back.
136 *
137 * We have to do some indirection here to get this to work, but we can
138 * achieve this with the following strategy:
139 *
140 * - Create a @c NekFactoryCapsuleDestructor object, which as an argument will
141 * store the Python class instance that will be instantiated from the Python
142 * side.
143 * - Using std::bind, construct a function pointer to the helper's creation
144 * function, NekFactoryCapsuleDestructor<tBase>::create.
145 * - Create a Python capsule that will contain the @c
146 * NekFactoryCapsuleDestructor<tBase> instance, and register this in the
147 * global namespace of the current module. This then ties the capsule to the
148 * lifetime of the module.
149 */
150template <template <typename, typename, typename...> typename tFac,
151 typename tBase, typename tKey, typename... tParam>
152class NekFactory_Register<tFac<tKey, tBase, tParam...>>
153{
154public:
155 NekFactory_Register(tFac<tKey, tBase, tParam...> &fac) : m_fac(fac)
156 {
157 }
158
159 void operator()(std::string const &name, py::object &obj)
160 {
161 // Create a factory register helper, which will call the C++ function to
162 // create the factory.
165
166 // Register this with the factory using std::bind to grab a function
167 // pointer to that particular object's function. We use
168 // placeholder_template to bind to some number of arguments.
169 DoRegister(name, helper, std::index_sequence_for<tParam...>{});
170
171 // Create a capsule that will be embedded in the __main__ namespace. So
172 // deallocation will occur, but only once Python ends or the Python
173 // module is deallocated.
174 std::string key = "_" + name;
175
176 // Allocate a capsule to ensure memory is cleared up upon deallocation
177 // (depends on Python 2 or 3).
178#if PY_MAJOR_VERSION == 2
179 py::object capsule(py::handle<>(
180 PyCObject_FromVoidPtr(helper, NekFactoryCapsuleDestructor<tBase>)));
181#else
182 py::object capsule(py::handle<>(PyCapsule_New(
183 helper, nullptr, NekFactoryCapsuleDestructor<tBase>)));
184#endif
185
186 // Embed the capsule in __main__.
187 py::import("__main__").attr(key.c_str()) = capsule;
188 }
189
190private:
191 /**
192 * @brief Helper function to unpack parameter arguments into the factory's
193 * register creation function using the #placeholder_template helper struct.
194 */
195 template <std::size_t... Is>
196 void DoRegister(std::string const &name,
198 std::integer_sequence<std::size_t, Is...>)
199 {
200 m_fac.RegisterCreatorFunction(
201 name,
202 std::bind(
204 helper, placeholder_template<Is>{}...));
205 }
206
207 /// Reference to the NekFactory.
208 tFac<tKey, tBase, tParam...> &m_fac;
209};
210
211#endif
void NekFactoryCapsuleDestructor(PyObject *ptr)
C-style callback to destroy the NekFactoryRegisterHelper class to avoid memory leaks.
void DoRegister(std::string const &name, NekFactoryRegisterHelper< tBase > *helper, std::integer_sequence< std::size_t, Is... >)
Helper function to unpack parameter arguments into the factory's register creation function using the...
void operator()(std::string const &name, py::object &obj)
tFac< tKey, tBase, tParam... > & m_fac
Reference to the NekFactory.
Helper class to be used in NekFactory_Register. Stores the Python function used to call the creator f...
std::shared_ptr< T > create(Args... args)
Callback to Python instantiator function (typically a class constructor). Assumes that all arguments ...
py::object m_obj
Python function that is used to construct objects.
STL namespace.