1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Importer/BsImporter.h"
4#include "Resources/BsResource.h"
5#include "FileSystem/BsFileSystem.h"
6#include "Importer/BsSpecificImporter.h"
7#include "Importer/BsShaderIncludeImporter.h"
8#include "Importer/BsImportOptions.h"
9#include "Debug/BsDebug.h"
10#include "FileSystem/BsDataStream.h"
11#include "Error/BsException.h"
12#include "Utility/BsUUID.h"
13#include "Resources/BsResources.h"
14#include "Threading/BsThreadPool.h"
15#include "Threading/BsTaskScheduler.h"
16
17namespace bs
18{
19 Importer::Importer()
20 {
21 mAsyncOpSyncData = bs_shared_ptr_new<AsyncOpSyncData>();
22
23 _registerAssetImporter(bs_new<ShaderIncludeImporter>());
24 }
25
26 Importer::~Importer()
27 {
28 for(auto i = mAssetImporters.begin(); i != mAssetImporters.end(); ++i)
29 {
30 if((*i) != nullptr)
31 bs_delete(*i);
32 }
33
34 mAssetImporters.clear();
35 }
36
37 bool Importer::supportsFileType(const String& extension) const
38 {
39 for(auto iter = mAssetImporters.begin(); iter != mAssetImporters.end(); ++iter)
40 {
41 if(*iter != nullptr && (*iter)->isExtensionSupported(extension))
42 return true;
43 }
44
45 return false;
46 }
47
48 bool Importer::supportsFileType(const UINT8* magicNumber, UINT32 magicNumSize) const
49 {
50 for(auto iter = mAssetImporters.begin(); iter != mAssetImporters.end(); ++iter)
51 {
52 if(*iter != nullptr && (*iter)->isMagicNumberSupported(magicNumber, magicNumSize))
53 return true;
54 }
55
56 return false;
57 }
58
59 HResource Importer::import(const Path& inputFilePath, SPtr<const ImportOptions> importOptions, const UUID& UUID)
60 {
61 SPtr<Resource> importedResource = _import(inputFilePath, importOptions);
62
63 if(UUID.empty())
64 return gResources()._createResourceHandle(importedResource);
65
66 return gResources()._createResourceHandle(importedResource, UUID);
67 }
68
69 TAsyncOp<HResource> Importer::importAsync(const Path& inputFilePath, SPtr<const ImportOptions> importOptions,
70 const UUID& UUID)
71 {
72 TAsyncOp<HResource> output(mAsyncOpSyncData);
73
74 SpecificImporter* importer = prepareForImport(inputFilePath, importOptions);
75 if(!importer)
76 {
77 output._completeOperation(HResource());
78 return output;
79 }
80
81 queueForImport(importer, inputFilePath, importOptions, UUID, output);
82 return output;
83 }
84
85 SPtr<MultiResource> Importer::importAll(const Path& inputFilePath, SPtr<const ImportOptions> importOptions)
86 {
87 Vector<SubResource> output;
88
89 Vector<SubResourceRaw> importedResource = _importAll(inputFilePath, importOptions);
90 for(auto& entry : importedResource)
91 {
92 HResource handle = gResources()._createResourceHandle(entry.value);
93 output.push_back({ entry.name, handle });
94 }
95
96 return bs_shared_ptr_new<MultiResource>(output);
97 }
98
99 TAsyncOp<SPtr<MultiResource>> Importer::importAllAsync(const Path& inputFilePath,
100 SPtr<const ImportOptions> importOptions)
101 {
102 TAsyncOp<SPtr<MultiResource>> output(mAsyncOpSyncData);
103
104 SpecificImporter* importer = prepareForImport(inputFilePath, importOptions);
105 if(!importer)
106 {
107 output._completeOperation(bs_shared_ptr_new<MultiResource>());
108 return output;
109 }
110
111 queueForImport(importer, inputFilePath, importOptions, UUID::EMPTY, output);
112 return output;
113 }
114
115 SPtr<Resource> Importer::_import(const Path& inputFilePath, SPtr<const ImportOptions> importOptions)
116 {
117 SpecificImporter* importer = prepareForImport(inputFilePath, importOptions);
118 if(importer == nullptr)
119 return nullptr;
120
121 const UINT64 taskId = waitForAsync(importer);
122 SPtr<Resource> output = importer->import(inputFilePath, importOptions);
123
124 if(importer->getAsyncMode() == ImporterAsyncMode::Single)
125 {
126 Lock lock(mLastTaskMutex);
127 auto iterFind = mLastQueuedTask.find(importer);
128 if (iterFind != mLastQueuedTask.end())
129 {
130 if (iterFind->second.id == taskId)
131 mLastQueuedTask.erase(iterFind);
132
133 mTaskCompleted.notify_one();
134 }
135 }
136
137 return output;
138 }
139
140 Vector<SubResourceRaw> Importer::_importAll(const Path& inputFilePath, SPtr<const ImportOptions> importOptions)
141 {
142 SpecificImporter* importer = prepareForImport(inputFilePath, importOptions);
143 if(!importer)
144 return Vector<SubResourceRaw>();
145
146 const UINT64 taskId = waitForAsync(importer);
147 Vector<SubResourceRaw> output = importer->importAll(inputFilePath, importOptions);
148
149 if(importer->getAsyncMode() == ImporterAsyncMode::Single)
150 {
151 Lock lock(mLastTaskMutex);
152 auto iterFind = mLastQueuedTask.find(importer);
153 if (iterFind != mLastQueuedTask.end())
154 {
155 if (iterFind->second.id == taskId)
156 mLastQueuedTask.erase(iterFind);
157
158 mTaskCompleted.notify_one();
159 }
160 }
161 return output;
162 }
163
164 SpecificImporter* Importer::prepareForImport(const Path& filePath, SPtr<const ImportOptions>& importOptions) const
165 {
166 if (!FileSystem::isFile(filePath))
167 {
168 LOGWRN("Trying to import asset that doesn't exists. Asset path: " + filePath.toString());
169 return nullptr;
170 }
171
172 SpecificImporter* importer = getImporterForFile(filePath);
173 if (importer == nullptr)
174 return nullptr;
175
176 if (importOptions == nullptr)
177 importOptions = importer->getDefaultImportOptions();
178 else
179 {
180 SPtr<const ImportOptions> defaultImportOptions = importer->getDefaultImportOptions();
181 if (importOptions->getTypeId() != defaultImportOptions->getTypeId())
182 {
183 BS_EXCEPT(InvalidParametersException, "Provided import options is not of valid type. " \
184 "Expected: " + defaultImportOptions->getTypeName() + ". Got: " + importOptions->getTypeName() + ".");
185 }
186 }
187
188 return importer;
189 }
190
191 UINT64 Importer::waitForAsync(SpecificImporter* importer)
192 {
193 UINT64 taskId = 0;
194
195 const ImporterAsyncMode asyncMode = importer->getAsyncMode();
196 if(asyncMode == ImporterAsyncMode::Single)
197 {
198 Lock lock(mLastTaskMutex);
199
200 // Wait for any existing async tasks to complete
201 while(true)
202 {
203 const auto iterFind = mLastQueuedTask.find(importer);
204 if (iterFind != mLastQueuedTask.end())
205 mTaskCompleted.wait(lock);
206 else
207 break;
208 }
209
210 // Register a new task so other calls to this method know to wait
211 taskId = mTaskId++;
212 mLastQueuedTask[importer] = QueuedTask(nullptr, taskId);
213 }
214
215 return taskId;
216 }
217
218 template<class ReturnType>
219 void doImport(TAsyncOp<ReturnType> op, SpecificImporter* importer, const Path& filePath, const UUID& uuid,
220 const SPtr<const ImportOptions>& importOptions)
221 {
222 assert(false && "Invalid template instantiation called.");
223 }
224
225 template<>
226 void doImport(TAsyncOp<HResource> op, SpecificImporter* importer, const Path& filePath, const UUID& uuid,
227 const SPtr<const ImportOptions>& importOptions)
228 {
229 SPtr<Resource> resourcePtr = importer->import(filePath, importOptions);
230
231 HResource resource;
232 if (uuid.empty())
233 resource = gResources()._createResourceHandle(resourcePtr);
234 else
235 resource = gResources()._createResourceHandle(resourcePtr, uuid);
236
237 op._completeOperation(resource);
238 }
239
240 template<>
241 void doImport(TAsyncOp<SPtr<MultiResource>> op, SpecificImporter* importer, const Path& filePath, const UUID& uuid,
242 const SPtr<const ImportOptions>& importOptions)
243 {
244 Vector<SubResourceRaw> rawSubresources = importer->importAll(filePath, importOptions);
245
246 Vector<SubResource> subresources;
247 for (auto& entry : rawSubresources)
248 {
249 HResource handle = gResources()._createResourceHandle(entry.value);
250 subresources.push_back({ entry.name, handle });
251 }
252
253 op._completeOperation(bs_shared_ptr_new<MultiResource>(subresources));
254 }
255
256 template<class ReturnType>
257 void Importer::queueForImport(SpecificImporter* importer, const Path& inputFilePath,
258 const SPtr<const ImportOptions>& importOptions, const UUID& uuid, TAsyncOp<ReturnType>& op)
259 {
260 ImporterAsyncMode asyncMode = importer->getAsyncMode();
261
262 // If the importer only supports single thread import, the tasks need to be chained using dependencies so they get
263 // executed in sequence
264 UINT64 taskId = 0;
265 SPtr<Task> dependency;
266 if(asyncMode == ImporterAsyncMode::Single)
267 {
268 mLastTaskMutex.lock();
269 taskId = mTaskId++;
270
271 auto iterFind = mLastQueuedTask.find(importer);
272 if(iterFind != mLastQueuedTask.end())
273 dependency = iterFind->second.task;
274 }
275
276 SPtr<Task> task = Task::create("ImportWorker",
277 [this, importer, inputFilePath, importOptions, uuid, taskId, op]
278 {
279 doImport(op, importer, inputFilePath, uuid, importOptions);
280
281 // Clear itself from the task list so we don't unnecessarily keep a reference. But first make sure we are the
282 // last task by comparing the ids.
283 Lock lock(mLastTaskMutex);
284 auto iterFind = mLastQueuedTask.find(importer);
285 if(iterFind != mLastQueuedTask.end())
286 {
287 if(iterFind->second.id == taskId)
288 mLastQueuedTask.erase(iterFind);
289
290 mTaskCompleted.notify_one();
291 }
292
293 }, TaskPriority::Normal, dependency);
294
295 if(asyncMode == ImporterAsyncMode::Single)
296 {
297 mLastQueuedTask[importer] = QueuedTask(task, taskId);
298 mLastTaskMutex.unlock();
299 }
300
301 TaskScheduler::instance().addTask(task);
302 }
303
304 template void Importer::queueForImport(SpecificImporter*, const Path&, const SPtr<const ImportOptions>&, const UUID&,
305 TAsyncOp<HResource>&);
306
307 template void Importer::queueForImport(SpecificImporter*, const Path&, const SPtr<const ImportOptions>&, const UUID&,
308 TAsyncOp<SPtr<MultiResource>>&);
309
310 SPtr<ImportOptions> Importer::createImportOptions(const Path& inputFilePath)
311 {
312 if(!FileSystem::isFile(inputFilePath))
313 {
314 LOGWRN("Trying to import asset that doesn't exists. Asset path: " + inputFilePath.toString());
315 return nullptr;
316 }
317
318 SpecificImporter* importer = getImporterForFile(inputFilePath);
319 if(importer == nullptr)
320 return nullptr;
321
322 return importer->createImportOptions();
323 }
324
325 void Importer::_registerAssetImporter(SpecificImporter* importer)
326 {
327 if(!importer)
328 {
329 LOGWRN("Trying to register a null asset importer!");
330 return;
331 }
332
333 mAssetImporters.push_back(importer);
334 }
335
336 SpecificImporter* Importer::getImporterForFile(const Path& inputFilePath) const
337 {
338 String ext = inputFilePath.getExtension();
339 if (ext.empty())
340 return nullptr;
341
342 ext = ext.substr(1, ext.size() - 1); // Remove the .
343 if(!supportsFileType(ext))
344 {
345 LOGWRN("There is no importer for the provided file type. (" + inputFilePath.toString() + ")");
346 return nullptr;
347 }
348
349 for(auto iter = mAssetImporters.begin(); iter != mAssetImporters.end(); ++iter)
350 {
351 if(*iter != nullptr && (*iter)->isExtensionSupported(ext))
352 {
353 return *iter;
354 }
355 }
356
357 return nullptr;
358 }
359
360 BS_CORE_EXPORT Importer& gImporter()
361 {
362 return Importer::instance();
363 }
364}
365