From d6189cb74f1b9089fd62c8db559c8e93135d393c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 24 Jul 2018 20:46:56 +0200 Subject: [PATCH] Trade: support for file loading callbacks in AbstractImporter. --- doc/changelog.dox | 1 + doc/snippets/MagnumTrade.cpp | 31 +++ src/Magnum/Trade/AbstractImporter.cpp | 44 +++- src/Magnum/Trade/AbstractImporter.h | 193 +++++++++++++++- .../Trade/Test/AbstractImporterTest.cpp | 217 +++++++++++++++++- src/Magnum/Trade/Trade.h | 3 + 6 files changed, 482 insertions(+), 7 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 27fc89c94..f6ae4e53b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -74,6 +74,7 @@ See also: separate translation / rotation / scaling specification instead of a combined transformation matrix. See @ref Trade::ObjectData2D::transformation() and @ref Trade::ObjectData3D::transformation() for more information. +- Support for file loading callbacks in @ref Trade::AbstractImporter - Debug output for @ref Trade::AbstractImporter::Feature, @ref Trade::AbstractImageConverter::Feature enums and @ref Trade::AbstractImporter::Features, diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 7a10ffdb9..8c14e11d9 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include + #include "Magnum/PixelFormat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" @@ -37,6 +40,34 @@ using namespace Magnum::Math::Literals; int main() { +{ +std::unique_ptr importer; +/* [AbstractImporter-setFileCallback] */ +importer->setFileCallback([](const std::string& filename, + Trade::ImporterFileCallbackPolicy, void*) { + Utility::Resource rs("data"); + return rs.getRaw(filename); + }); +/* [AbstractImporter-setFileCallback] */ +} + +{ +std::unique_ptr importer; +/* [AbstractImporter-setFileCallback-template] */ +struct Data { + std::unordered_map> files; +} data; + +importer->setFileCallback([](const std::string& filename, + Trade::ImporterFileCallbackPolicy, Data& data) { + auto found = data.files.find(filename); + if(found == data.files.end()) found = data.files.emplace( + filename, Utility::Directory::read(filename)).first; + return Containers::ArrayView{found->second}; + }, data); +/* [AbstractImporter-setFileCallback-template] */ +} + { /* [ImageData-construction] */ Containers::Array data; diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index e2405d5ca..1123e7e71 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -72,6 +72,20 @@ AbstractImporter::AbstractImporter(PluginManager::Manager& man AbstractImporter::AbstractImporter(PluginManager::AbstractManager& manager, const std::string& plugin): PluginManager::AbstractManagingPlugin{manager, plugin} {} +void AbstractImporter::setFileCallback(Containers::ArrayView(*callback)(const std::string&, ImporterFileCallbackPolicy, void*), void* const userData) { + CORRADE_ASSERT(!isOpened(), "Trade::AbstractImporter::setFileCallback(): can't be set while a file is opened", ); + if(!(features() & (Feature::FileCallback|Feature::OpenData))) { + Warning{} << "Trade::AbstractImporter::setFileCallback(): importer supports neither loading from data nor via callbacks, ignoring"; + return; + } + + _fileCallback = callback; + _fileCallbackUserData = userData; + doSetFileCallback(callback, userData); +} + +void AbstractImporter::doSetFileCallback(Containers::ArrayView(*)(const std::string&, ImporterFileCallbackPolicy, void*), void*) {} + bool AbstractImporter::openData(Containers::ArrayView data) { CORRADE_ASSERT(features() & Feature::OpenData, "Trade::AbstractImporter::openData(): feature not supported", {}); @@ -100,7 +114,17 @@ void AbstractImporter::doOpenState(const void*, const std::string&) { bool AbstractImporter::openFile(const std::string& filename) { close(); - doOpenFile(filename); + + /* If file loading callbacks are set and the importer can open data, do + that. Mark the file as ready to be closed once opening is finished. */ + if((doFeatures() & Feature::OpenData) && _fileCallback) { + doOpenData(_fileCallback(filename, ImporterFileCallbackPolicy::LoadTemporary, _fileCallbackUserData)); + _fileCallback(filename, ImporterFileCallbackPolicy::Close, _fileCallbackUserData); + + /* Otherwise (either no callbacks set or opening data is not supported) + just call the default implementation */ + } else doOpenFile(filename); + return isOpened(); } @@ -527,6 +551,7 @@ Debug& operator<<(Debug& debug, const AbstractImporter::Feature value) { #define _c(v) case AbstractImporter::Feature::v: return debug << "Trade::AbstractImporter::Feature::" #v; _c(OpenData) _c(OpenState) + _c(FileCallback) #undef _c /* LCOV_EXCL_STOP */ } @@ -537,7 +562,22 @@ Debug& operator<<(Debug& debug, const AbstractImporter::Feature value) { Debug& operator<<(Debug& debug, const AbstractImporter::Features value) { return Containers::enumSetDebugOutput(debug, value, "Trade::AbstractImporter::Features{}", { AbstractImporter::Feature::OpenData, - AbstractImporter::Feature::OpenState}); + AbstractImporter::Feature::OpenState, + AbstractImporter::Feature::FileCallback}); +} + +Debug& operator<<(Debug& debug, const ImporterFileCallbackPolicy value) { + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case ImporterFileCallbackPolicy::v: return debug << "Trade::ImporterFileCallbackPolicy::" #v; + _c(LoadTemporary) + _c(LoadPernament) + _c(Close) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "Trade::ImporterFileCallbackPolicy(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; } }} diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index e33fdacf0..044b16b1e 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -44,6 +44,56 @@ namespace Magnum { namespace Trade { +/** +@brief Importer file loading callback policy + +@see @ref AbstractImporter::setFileCallback(), + @ref Trade-AbstractImporter-usage-callbacks +*/ +enum class ImporterFileCallbackPolicy: UnsignedByte { + /** + * The requested file is used only during a call of given function and the + * memory view is not referenced anymore once the function exits. + * + * This can be the case for example when importing image data using + * @ref AbstractImporter::image2D() --- imported data are copied into the + * returned @ref ImageData2D object and the original file is not needed + * anymore. Note, however, that this might not be the case for all + * importers --- see documentation of a particular plugin for concrete + * info. + */ + LoadTemporary, + + /** + * The requested file may be used for loading most or all data in the next + * steps, so the importer expects the memory view to be valid for as long + * as data import functions are called on it, but at most until the + * importer is destroyed, @ref AbstractImporter::close() is called or + * another file is opened. + * + * This can be the case for example when importing mesh data using + * @ref AbstractImporter::mesh3D() --- all vertex data might be combined in + * a single binary file and each mesh occupies only a portion of it. Note, + * however, that this might not be the case for all importers --- see + * documentation of a particular plugin for concrete info. + */ + LoadPernament, + + /** + * A file that has been previously loaded by this callback can be closed + * now (and its memory freed). This is just a hint, it's not *required* for + * the callback to close it. This policy is also only ever called with a + * file that was previously opened with the same callback, so it's possible + * to completely ignore it and just return the cached value. + * + * This can be the case for example when an importer is done parsing a text + * file into an internal representation and the original data are no longer + * needed (and, for example, other files need to be loaded and they could + * repurpose the unused memory). + */ + Close +}; + /** @brief Base for importer plugins @@ -54,9 +104,11 @@ data. See @ref plugins for more information and `*Importer` classes in @section Trade-AbstractImporter-subclassing Subclassing The plugin needs to implement the @ref doFeatures(), @ref doIsOpened() -functions, at least one of @ref doOpenData() / @ref doOpenFile() / @ref doOpenState() -functions, function @ref doClose() and one or more tuples of data access -functions, based on what features are supported in given format. +functions, at least one of @ref doOpenData() / @ref doOpenFile() / +@ref doOpenState() functions, function @ref doClose(), function +@ref doSetFileCallback() in case it's desired to respond on file loading +callback setup and one or more tuples of data access functions, based on what +features are supported in given format. For multi-data formats the file opening shouldn't take long and all parsing should be done in the data parsing functions instead, because the user might @@ -74,6 +126,8 @@ checked by the implementation: supported. - The @ref doOpenState() function is called only if @ref Feature::OpenState is supported. +- The @ref doSetFileCallback() function is called only if + @ref Feature::FileCallback is supported and there is no file opened. - All `do*()` implementations working on an opened file are called only if there is any file opened. - All `do*()` implementations taking data ID as parameter are called only if @@ -98,7 +152,18 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi OpenData = 1 << 0, /** Opening already loaded state using @ref openState() */ - OpenState = 1 << 1 + OpenState = 1 << 1, + + /** + * Specifying callbacks for loading additional files referenced + * from the main file using @ref setFileCallback(). If the importer + * doesn't expose this feature, the format is either single-file or + * loading via callbacks is not supported. + * + * See @ref Trade-AbstractImporter-usage-callbacks and particular + * importer documentation for more information. + */ + FileCallback = 1 << 2 }; /** @brief Set of features supported by this importer */ @@ -142,6 +207,85 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Features supported by this importer */ Features features() const { return doFeatures(); } + /** + * @brief File opening callback function + * + * @see @ref Trade-AbstractImporter-usage-callbacks + */ + auto fileCallback() const -> Containers::ArrayView(*)(const std::string&, ImporterFileCallbackPolicy, void*) { return _fileCallback; } + + /** + * @brief File opening callback user data + * + * @see @ref Trade-AbstractImporter-usage-callbacks + */ + void* fileCallbackUserData() const { return _fileCallbackUserData; } + + /** + * @brief Set file opening callback + * + * In case a scene file opened using @ref openData() references other + * files such as images and @ref Feature::FileCallback is supported by + * the importer, @p callback will be used to load file data. The + * callback function gets a filename, @ref ImporterFileCallbackPolicy + * and the @p userData pointer as input and returns a non-owning view + * on the loaded data as output. + * + * In case @ref openFile() is used and at least @ref Feature::OpenData + * is supported, the callback is used for loading the top-level file. + * First the file is loaded with @ref ImporterFileCallbackPolicy::LoadTemporary + * passed to the callback, then the returned memory view is passed to + * @ref openData() (sidestepping the potential @ref openFile() + * implementation of that particular plugin) and after that the + * callback is called again with @ref ImporterFileCallbackPolicy::Close + * because the semantics of @ref openData() don't require the data to + * be alive after. In case you need a different behavior, use + * @ref openData() directly. + * + * In case the importer supports neither @ref Feature::FileCallback nor + * @ref Feature::OpenData, the callback won't have a chance to be used + * in any of the above scenarios and thus the function prints a warning + * and returns without setting anything. In case @p callback is + * @cpp nullptr @ce, the current callback (if any) is reset. + * + * It's expected that this function is called *before* a file is + * opened. It's also expected that the loaded data are kept in scope + * for as long as the importer needs them, based on the value of + * @ref ImporterFileCallbackPolicy. Particular importer documentation + * provides * more information about the behavior. + * + * Following is an example of setting up a file loading callback for + * fetching compiled-in resources from @ref Corrade::Utility::Resource. + * See the overload below for a more convenient type-safe way to pass + * the user data pointer. + * + * @snippet MagnumTrade.cpp AbstractImporter-setFileCallback + * + * @see @ref Trade-AbstractImporter-usage-callbacks + */ + void setFileCallback(Containers::ArrayView(*callback)(const std::string&, ImporterFileCallbackPolicy, void*), void* userData = nullptr); + + /** + * @brief Set file opening callback + * + * Equivalent to calling the above with a lambda wrapper that casts + * @cpp void* @ce back to @cpp T* @ce and dereferences it in order to + * pass it to @p callback. Example usage: + * + * @snippet MagnumTrade.cpp AbstractImporter-setFileCallback-template + * + * @see @ref Trade-AbstractImporter-usage-callbacks + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + template void setFileCallback(Containers::ArrayView(*callback)(const std::string&, ImporterFileCallbackPolicy, T&), T& userData); + #else + /* Otherwise the user would be forced to use the + operator to convert + a lambda to a function pointer and (besides being weird and + annoying) it's also not portable because it doesn't work on MSVC + 2015 and older versions of MSVC 2017. */ + template void setFileCallback(Callback callback, T& userData); + #endif + /** @brief Whether any file is opened */ bool isOpened() const { return doIsOpened(); } @@ -176,6 +320,10 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Closes previous file, if it was opened, and tries to open given * file. Returns @cpp true @ce on success, @cpp false @ce otherwise. + * If file loading callbacks are set via @ref setFileCallback() and + * @ref Feature::OpenData is supported, this function uses the callback + * to load the file and passes the memory view to @ref openData() + * instead. See @ref setFileCallback() for more information. * @see @ref features(), @ref openData() */ bool openFile(const std::string& filename); @@ -559,6 +707,17 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Implementation for @ref features() */ virtual Features doFeatures() const = 0; + /** + * @brief Implementation for @ref setFileCallback() + * + * Useful when the importer needs to modify some internal state on + * callback setup. Default implementation does nothing and this + * function doesn't need to be implemented --- the callback function + * and user data pointer are available through @ref fileCallback() and + * @ref fileCallbackUserData(). + */ + virtual void doSetFileCallback(Containers::ArrayView(*callback)(const std::string&, ImporterFileCallbackPolicy, void*), void* userData); + /** @brief Implementation for @ref isOpened() */ virtual bool doIsOpened() const = 0; @@ -868,8 +1027,31 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Implementation for @ref importerState() */ virtual const void* doImporterState() const; + + private: + Containers::ArrayView(*_fileCallback)(const std::string&, ImporterFileCallbackPolicy, void*){}; + void* _fileCallbackUserData{}; + + /* Used by the templated version only */ + struct FileCallbackTemplate { + void(*callback)(); + void* userData; + } _fileCallbackTemplate{}; }; +#ifndef DOXYGEN_GENERATING_OUTPUT +template void AbstractImporter::setFileCallback(Callback callback, T& userData) { + /* Don't try to wrap a null function pointer */ + if(!callback) return setFileCallback(nullptr); + + _fileCallbackTemplate = { reinterpret_cast(static_cast(*)(const std::string&, ImporterFileCallbackPolicy, T&)>(callback)), &userData }; + setFileCallback([](const std::string& filename, const ImporterFileCallbackPolicy flags, void* const userData) { + auto& s = *reinterpret_cast(userData); + return reinterpret_cast(*)(const std::string&, ImporterFileCallbackPolicy, T&)>(s.callback)(filename, flags, *static_cast(s.userData)); + }, &_fileCallbackTemplate); +} +#endif + CORRADE_ENUMSET_OPERATORS(AbstractImporter::Features) /** @debugoperatorclassenum{AbstractImporter,AbstractImporter::Feature} */ @@ -878,6 +1060,9 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AbstractImporter::Feature va /** @debugoperatorclassenum{AbstractImporter,AbstractImporter::Features} */ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AbstractImporter::Features value); +/** @debugoperatorenum{ImporterFileCallbackPolicy} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ImporterFileCallbackPolicy value); + }} #endif diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 5b508085d..77a7b5204 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -60,6 +60,15 @@ class AbstractImporterTest: public TestSuite::Tester { void openStateNotSupported(); void openStateNotImplemented(); + void setFileCallback(); + void setFileCallbackTemplate(); + void setFileCallbackTemplateNull(); + void setFileCallbackFileOpened(); + void setFileCallbackNotImplemented(); + void setFileCallbackNotSupported(); + void setFileCallbackOpenFileAsData(); + void setFileCallbackOpenFileAsDataNotSupported(); + void defaultScene(); void defaultSceneNotImplemented(); void defaultSceneNoFile(); @@ -214,6 +223,7 @@ class AbstractImporterTest: public TestSuite::Tester { void debugFeature(); void debugFeatures(); + void debugFileCallbackPolicy(); }; AbstractImporterTest::AbstractImporterTest() { @@ -229,6 +239,15 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::openStateNotSupported, &AbstractImporterTest::openStateNotImplemented, + &AbstractImporterTest::setFileCallback, + &AbstractImporterTest::setFileCallbackTemplate, + &AbstractImporterTest::setFileCallbackTemplateNull, + &AbstractImporterTest::setFileCallbackFileOpened, + &AbstractImporterTest::setFileCallbackNotImplemented, + &AbstractImporterTest::setFileCallbackNotSupported, + &AbstractImporterTest::setFileCallbackOpenFileAsData, + &AbstractImporterTest::setFileCallbackOpenFileAsDataNotSupported, + &AbstractImporterTest::defaultScene, &AbstractImporterTest::defaultSceneNotImplemented, &AbstractImporterTest::defaultSceneNoFile, @@ -382,7 +401,8 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::importerStateNoFile, &AbstractImporterTest::debugFeature, - &AbstractImporterTest::debugFeatures}); + &AbstractImporterTest::debugFeatures, + &AbstractImporterTest::debugFileCallbackPolicy}); } void AbstractImporterTest::construct() { @@ -536,6 +556,194 @@ void AbstractImporterTest::openStateNotImplemented() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::openState(): feature advertised but not implemented\n"); } +void AbstractImporterTest::setFileCallback() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; } + bool doIsOpened() const override { return false; } + void doClose() override {} + void doSetFileCallback(Containers::ArrayView(*)(const std::string&, ImporterFileCallbackPolicy, void*), void* userData) override { + *static_cast(userData) = 1337; + } + }; + + int a = 0; + Importer importer; + auto lambda = [](const std::string&, ImporterFileCallbackPolicy, void*) { + return Containers::ArrayView{}; + }; + importer.setFileCallback(lambda, &a); + CORRADE_COMPARE(importer.fileCallback(), lambda); + CORRADE_COMPARE(importer.fileCallbackUserData(), &a); + CORRADE_COMPARE(a, 1337); +} + +void AbstractImporterTest::setFileCallbackTemplate() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; } + bool doIsOpened() const override { return false; } + void doClose() override {} + void doSetFileCallback(Containers::ArrayView(*)(const std::string&, ImporterFileCallbackPolicy, void*), void*) override { + called = true; + } + + public: bool called = false; + }; + + int a = 0; + auto lambda = [](const std::string&, ImporterFileCallbackPolicy, int&) { + return Containers::ArrayView{}; + }; + Importer importer; + importer.setFileCallback(lambda, a); + CORRADE_VERIFY(importer.fileCallback()); + CORRADE_VERIFY(importer.fileCallbackUserData()); + CORRADE_VERIFY(importer.called); + + /* The data pointers should be wrapped, thus not the same */ + CORRADE_VERIFY(reinterpret_cast(importer.fileCallback()) != reinterpret_cast(static_cast(*)(const std::string&, ImporterFileCallbackPolicy, int&)>(lambda))); + CORRADE_VERIFY(importer.fileCallbackUserData() != &a); +} + +void AbstractImporterTest::setFileCallbackTemplateNull() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::OpenData|Feature::FileCallback; } + bool doIsOpened() const override { return false; } + void doClose() override {} + void doSetFileCallback(Containers::ArrayView(*callback)(const std::string&, ImporterFileCallbackPolicy, void*), void* userData) override { + called = !callback && !userData; + } + + public: bool called = false; + }; + + int a = 0; + Importer importer; + importer.setFileCallback(static_cast(*)(const std::string&, ImporterFileCallbackPolicy, int&)>(nullptr), a); + CORRADE_VERIFY(!importer.fileCallback()); + CORRADE_VERIFY(!importer.fileCallbackUserData()); + CORRADE_VERIFY(importer.called); +} + +void AbstractImporterTest::setFileCallbackFileOpened() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + }; + + std::ostringstream out; + Error redirectError{&out}; + + Importer importer; + importer.setFileCallback([](const std::string&, ImporterFileCallbackPolicy, void*) { + return Containers::ArrayView{}; + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::setFileCallback(): can't be set while a file is opened\n"); +} + +void AbstractImporterTest::setFileCallbackNotImplemented() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::FileCallback; } + bool doIsOpened() const override { return false; } + void doClose() override {} + }; + + int a; + auto lambda = [](const std::string&, ImporterFileCallbackPolicy, void*) { + return Containers::ArrayView{}; + }; + Importer importer; + importer.setFileCallback(lambda, &a); + CORRADE_COMPARE(importer.fileCallback(), lambda); + CORRADE_COMPARE(importer.fileCallbackUserData(), &a); + /* Should just work, no need to implement the function */ +} + +void AbstractImporterTest::setFileCallbackNotSupported() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + }; + + std::ostringstream out; + Warning redirectWarning{&out}; + + int a; + Importer importer; + importer.setFileCallback([](const std::string&, ImporterFileCallbackPolicy, void*) { + return Containers::ArrayView{}; + }, &a); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::setFileCallback(): importer supports neither loading from data nor via callbacks, ignoring\n"); + CORRADE_VERIFY(!importer.fileCallback()); + CORRADE_VERIFY(!importer.fileCallbackUserData()); +} + +void AbstractImporterTest::setFileCallbackOpenFileAsData() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::OpenData; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + void doOpenData(Containers::ArrayView data) override { + _opened = (data.size() == 1 && data[0] == '\xb0'); + } + + bool _opened = false; + }; + + struct State { + const char data = '\xb0'; + bool loaded = false; + bool closed = false; + bool calledNotSureWhy = false; + } state; + Importer importer; + importer.setFileCallback([](const std::string& filename, ImporterFileCallbackPolicy policy, State& state) { + if(filename == "file.dat" && policy == ImporterFileCallbackPolicy::LoadTemporary) { + state.loaded = true; + return Containers::ArrayView{&state.data, 1}; + } + + if(filename == "file.dat" && policy == ImporterFileCallbackPolicy::Close) { + state.closed = true; + return Containers::ArrayView{}; + } + + state.calledNotSureWhy = true; + return Containers::ArrayView{}; + }, state); + + CORRADE_VERIFY(importer.openFile("file.dat")); + CORRADE_VERIFY(state.loaded); + CORRADE_VERIFY(state.closed); + CORRADE_VERIFY(!state.calledNotSureWhy); +} + +void AbstractImporterTest::setFileCallbackOpenFileAsDataNotSupported() { + class Importer: public Trade::AbstractImporter { + Features doFeatures() const override { return Feature::FileCallback; } + bool doIsOpened() const override { return _opened; } + void doClose() override { _opened = false; } + + void doOpenFile(const std::string& filename) override { + _opened = filename == "file.dat"; + } + + bool _opened = false; + }; + + bool calledNotSureWhy = false; + Importer importer; + importer.setFileCallback([](const std::string&, ImporterFileCallbackPolicy, bool& calledNotSureWhy) { + calledNotSureWhy = true; + return Containers::ArrayView{}; + }, calledNotSureWhy); + + CORRADE_VERIFY(importer.openFile("file.dat")); + CORRADE_VERIFY(!calledNotSureWhy); +} + void AbstractImporterTest::defaultScene() { class Importer: public Trade::AbstractImporter { Features doFeatures() const override { return {}; } @@ -2744,6 +2952,13 @@ void AbstractImporterTest::debugFeatures() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::Feature::OpenData|Trade::AbstractImporter::Feature::OpenState Trade::AbstractImporter::Features{}\n"); } +void AbstractImporterTest::debugFileCallbackPolicy() { + std::ostringstream out; + + Debug{&out} << ImporterFileCallbackPolicy::Close << ImporterFileCallbackPolicy(0xf0); + CORRADE_COMPARE(out.str(), "Trade::ImporterFileCallbackPolicy::Close Trade::ImporterFileCallbackPolicy(0xf0)\n"); +} + }}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AbstractImporterTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 3b9d65573..a808cd5a2 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -35,7 +35,10 @@ namespace Magnum { namespace Trade { #ifndef DOXYGEN_GENERATING_OUTPUT class AbstractImageConverter; + +enum class ImporterFileCallbackPolicy: UnsignedByte; class AbstractImporter; + class AbstractMaterialData; class CameraData;