1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Francois Beaune, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28
29 // Interface header.
30 #include "pluginstore.h"
31
32 // appleseed.renderer headers.
33 #include "renderer/global/globallogger.h"
34 #include "renderer/utility/plugin.h"
35
36 // appleseed.foundation headers.
37 #include "foundation/platform/path.h"
38 #include "foundation/platform/sharedlibrary.h"
39 #include "foundation/utility/searchpaths.h"
40
41 // Boost headers.
42 #include "boost/filesystem.hpp"
43 #include "boost/thread/locks.hpp"
44 #include "boost/thread/mutex.hpp"
45
46 // Standard headers.
47 #include <cassert>
48 #include <cstddef>
49 #include <map>
50 #include <memory>
51 #include <string>
52
53 using namespace foundation;
54 using namespace std;
55
56 namespace bf = boost::filesystem;
57
58 namespace renderer
59 {
60
61 struct PluginStore::Impl
62 {
63 typedef PluginStore::PluginHandlerType PluginHandlerType;
64 typedef map<string, PluginHandlerType> PluginHandlerMap;
65
66 struct PluginDeleter;
67 typedef unique_ptr<Plugin, PluginDeleter> PluginUniquePtr;
68
69 typedef map<string, PluginUniquePtr> PluginMap;
70 typedef map<Plugin*, PluginMap::const_iterator> PluginInverseMap;
71
72 struct PluginDeleter
73 {
operator ()renderer::PluginStore::Impl::PluginDeleter74 void operator()(Plugin* plugin)
75 {
76 RENDERER_LOG_INFO("unloading plugin %s...", plugin->get_filepath());
77
78 // Try to call the plugin's uninitialization function if defined.
79 const auto uninit_fn = reinterpret_cast<Plugin::UnInitPluginFnType>(plugin->get_symbol("uninitialize_plugin"));
80 if (uninit_fn != nullptr)
81 uninit_fn();
82
83 // Delete the plugin.
84 delete plugin;
85 }
86 };
87
88 boost::mutex m_store_mutex;
89 PluginHandlerMap m_plugin_handlers;
90 PluginMap m_plugin_map;
91 PluginInverseMap m_plugin_inverse_map;
92
load_plugin_no_lockrenderer::PluginStore::Impl93 Plugin* load_plugin_no_lock(const char* filepath)
94 {
95 auto plugin_map_it = m_plugin_map.find(filepath);
96
97 if (plugin_map_it == m_plugin_map.end())
98 {
99 RENDERER_LOG_INFO("loading plugin %s...", filepath);
100
101 // Load the plugin.
102 PluginUniquePtr plugin(new Plugin(filepath));
103
104 // Try to call the plugin's initialization function if defined.
105 const auto init_fn = reinterpret_cast<Plugin::InitPluginFnType>(plugin->get_symbol("initialize_plugin"));
106 if (init_fn != nullptr)
107 {
108 if (!init_fn())
109 {
110 RENDERER_LOG_WARNING("plugin %s failed to initialize itself.", filepath);
111 return nullptr;
112 }
113 }
114
115 // Insert the plugin into the map.
116 const auto insert_result =
117 m_plugin_map.insert(PluginMap::value_type(filepath, std::move(plugin)));
118 assert(insert_result.second);
119 plugin_map_it = insert_result.first;
120
121 // Insert the corresponding entry into the inverse map.
122 #ifndef NDEBUG
123 const auto inverse_insert_result =
124 #endif
125 m_plugin_inverse_map.insert(PluginInverseMap::value_type(plugin_map_it->second.get(), plugin_map_it));
126 assert(inverse_insert_result.second);
127
128 RENDERER_LOG_DEBUG("plugin %s successfully loaded and initialized.", filepath);
129 }
130 else
131 {
132 RENDERER_LOG_DEBUG("plugin %s already loaded.", filepath);
133 }
134
135 return plugin_map_it->second.get();
136 }
137
load_plugin_and_invoke_handlers_no_lockrenderer::PluginStore::Impl138 Plugin* load_plugin_and_invoke_handlers_no_lock(const char* filepath)
139 {
140 // Load the plugin.
141 Plugin* plugin = load_plugin_no_lock(filepath);
142
143 if (plugin != nullptr)
144 {
145 // Invoke plugin handlers.
146 for (const auto& plugin_handler_item : m_plugin_handlers)
147 {
148 const string& entry_point_name = plugin_handler_item.first;
149 const PluginHandlerType& plugin_handler = plugin_handler_item.second;
150
151 // If the plugin exposes the expected entry point then pass it to the plugin handler.
152 void* plugin_entry_point = plugin->get_symbol(entry_point_name.c_str());
153 if (plugin_entry_point != nullptr)
154 plugin_handler(plugin, plugin_entry_point);
155 }
156 }
157
158 return plugin;
159 }
160
load_all_plugins_from_path_no_lockrenderer::PluginStore::Impl161 void load_all_plugins_from_path_no_lock(bf::path path)
162 {
163 path = safe_canonical(path);
164
165 // Only consider directories.
166 if (!bf::exists(path) || !bf::is_directory(path))
167 {
168 RENDERER_LOG_WARNING("not scanning %s for plugins since it doesn't exist or it isn't a directory.",
169 path.string().c_str());
170 return;
171 }
172
173 RENDERER_LOG_INFO("scanning %s for plugins...", path.string().c_str());
174
175 // Iterate over all files in this directory.
176 for (bf::directory_iterator i(path), e; i != e; ++i)
177 {
178 const bf::path& filepath = i->path();
179
180 // Only consider files.
181 if (!bf::is_regular_file(filepath))
182 continue;
183
184 // Only consider shared libraries.
185 if (lower_case(filepath.extension().string()) != SharedLibrary::get_default_file_extension())
186 continue;
187
188 // Load the plugin and invoke plugin handlers.
189 load_plugin_and_invoke_handlers_no_lock(filepath.string().c_str());
190 }
191 }
192 };
193
PluginStore()194 PluginStore::PluginStore()
195 : impl(new Impl())
196 {
197 }
198
~PluginStore()199 PluginStore::~PluginStore()
200 {
201 unload_all_plugins();
202 delete impl;
203 }
204
register_plugin_handler(const char * entry_point_name,const PluginHandlerType & plugin_handler)205 void PluginStore::register_plugin_handler(
206 const char* entry_point_name,
207 const PluginHandlerType& plugin_handler)
208 {
209 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
210 impl->m_plugin_handlers.insert(
211 Impl::PluginHandlerMap::value_type(entry_point_name, plugin_handler));
212 }
213
unload_all_plugins()214 void PluginStore::unload_all_plugins()
215 {
216 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
217
218 RENDERER_LOG_INFO("unloading all plugins...");
219
220 impl->m_plugin_inverse_map.clear();
221 impl->m_plugin_map.clear();
222 }
223
load_plugin(const char * filepath)224 Plugin* PluginStore::load_plugin(const char* filepath)
225 {
226 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
227 return impl->load_plugin_and_invoke_handlers_no_lock(filepath);
228 }
229
unload_plugin(Plugin * plugin)230 void PluginStore::unload_plugin(Plugin* plugin)
231 {
232 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
233
234 const auto plugin_map_inverse_it = impl->m_plugin_inverse_map.find(plugin);
235 assert(plugin_map_inverse_it != impl->m_plugin_inverse_map.end());
236
237 impl->m_plugin_map.erase(plugin_map_inverse_it->second);
238 impl->m_plugin_inverse_map.erase(plugin_map_inverse_it);
239 }
240
load_all_plugins_from_paths(const SearchPaths & search_paths)241 void PluginStore::load_all_plugins_from_paths(const SearchPaths& search_paths)
242 {
243 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
244
245 for (size_t i = 0, e = search_paths.get_path_count(); i < e; ++i)
246 {
247 bf::path search_path(search_paths.get_path(i));
248
249 // Make the search path absolute if it isn't already.
250 if (!search_path.is_absolute() && search_paths.has_root_path())
251 search_path = search_paths.get_root_path().c_str() / search_path;
252
253 // Load all plugins from this search path.
254 impl->load_all_plugins_from_path_no_lock(search_path);
255 }
256 }
257
load_all_plugins_from_path(const char * path)258 void PluginStore::load_all_plugins_from_path(const char* path)
259 {
260 boost::lock_guard<boost::mutex> lock(impl->m_store_mutex);
261 impl->load_all_plugins_from_path_no_lock(path);
262 }
263
264 } // namespace renderer
265