From 60f648972728f96511089eb853d3e370d2db1d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 10 Mar 2022 00:18:53 +0100 Subject: [PATCH] Utility: write proper docs for the Resource class. This was just embarrassingly bad and incomplete, oh god. Though I remember even back in 2010 this was still the *better* documentation out there, compared to the usual OSS C and C++ libraries. Times surely changed for the better. --- doc/corrade-changelog.dox | 1 + doc/snippets/Utility.cpp | 38 ++++++ src/Corrade/Utility/Resource.h | 226 ++++++++++++++++++++++++++------- src/Corrade/Utility/rc.cpp | 29 +++-- 4 files changed, 235 insertions(+), 59 deletions(-) diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index b2eb33b53..6d6f9c3be 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -525,6 +525,7 @@ namespace Corrade { @subsection corrade-changelog-latest-documentation Documentation - Added a @ref Containers-mapping "table mapping between STL and Corrade containers" +- New and thorough documentation for the @ref Utility::Resource class - Various fixes (see [mosra/corrade#108](https://github.com/mosra/corrade/pull/108), [mosra/corrade#119](https://github.com/mosra/corrade/pull/119), [mosra/corrade#120](https://github.com/mosra/corrade/pull/120)) diff --git a/doc/snippets/Utility.cpp b/doc/snippets/Utility.cpp index 3c2007b5c..017be4c1e 100644 --- a/doc/snippets/Utility.cpp +++ b/doc/snippets/Utility.cpp @@ -48,6 +48,7 @@ #include "Corrade/Utility/Macros.h" #include "Corrade/Utility/Memory.h" #include "Corrade/Utility/Path.h" +#include "Corrade/Utility/Resource.h" #include "Corrade/Utility/Sha1.h" #include "Corrade/Utility/StlMath.h" @@ -801,6 +802,43 @@ const char* shader = "#line " CORRADE_LINE_STRING "\n" R"GLSL( static_cast(shader); } +{ +/* [Resource-usage] */ +Utility::Resource rs{"game-data"}; + +std::string licenseText = rs.get("license.txt"); +Containers::ArrayView soundData = rs.getRaw("intro.ogg"); +DOXYGEN_ELLIPSIS() + +std::istringstream in{rs.get("levels/easy.conf")}; +Utility::Configuration easyLevel{in}; +DOXYGEN_ELLIPSIS() +/* [Resource-usage] */ +static_cast(soundData); +} + +{ +struct Foo { +/* [Resource-usage-static] */ +int main(int argc, char** argv) { + CORRADE_RESOURCE_INITIALIZE(MyGame_RESOURCES) + + DOXYGEN_ELLIPSIS(static_cast(argc); static_cast(argv);) +} +/* [Resource-usage-static] */ +}; +} + +{ +/* [Resource-usage-override] */ +Utility::Resource::overrideGroup("game-data", Utility::Path::join( + /* Assuming resources.conf is next to this C++ source file */ + Utility::Path::split(Utility::Path::fromNativeSeparators(__FILE__)).first(), + "resources.conf" +)); +/* [Resource-usage-override] */ +} + { /* [Tweakable-define] */ #define _ CORRADE_TWEAKABLE diff --git a/src/Corrade/Utility/Resource.h b/src/Corrade/Utility/Resource.h index 62a3795bf..1fb74be3e 100644 --- a/src/Corrade/Utility/Resource.h +++ b/src/Corrade/Utility/Resource.h @@ -44,54 +44,166 @@ namespace Implementation { } /** -@brief Data resource management +@brief Access to compiled-in resources -This class provides support for compiled-in data resources - both compiling -and reading. Resources can be differentiated into more groups, every resource -in given group has unique filename. +This class provides access to data files compiled into the executable using the +@ref corrade-cmake-add-resource "corrade_add_resource()" CMake macro or the +@ref corrade-rc "corrade-rc" utility. -See @ref resource-management for brief introduction and example usage. -Standalone resource compiler executable is implemented in -@ref corrade-rc "corrade-rc". +@m_class{m-note m-success} -@m_class{m-note m-default} - -@par Memory access and operation complexity - Resource registration (either automatic or using - @ref CORRADE_RESOURCE_INITIALIZE()) is a simple operation without any - heap access or other operations that could potentially fail. When using - only the @ref hasGroup() and @ref getRaw() APIs with plain C string - literals, no memory allocation or heap access is involved either. @par - The group lookup during construction and @ref hasGroup() is done with a - @f$ \mathcal{O}(n) @f$ complexity as the resources register themselves - into a linked list. Actual file lookup after is done in-place on the - compiled-in data in a @f$ \mathcal{O}(\log{}n) @f$ time. + The @ref resource-management tutorial shows a complete setup for + compiled-in resources including a CMake project. + +@section Utility-Resource-compilation Resource compilation -@section Utility-Resource-conf Resource configuration file +Resources are organized in groups, where a group is a set of files that are +encoded in a hexadecimal form into a single `*.cpp` file which is then compiled +alongside your other sources. The @ref corrade-rc "corrade-rc" executable and the @ref corrade-cmake-add-resource "corrade_add_resource()" CMake macro take a -configuration file as an input, listing files to be compiled as resources. The -file can be also used when overriding compiled-in resources with live data -using @ref overrideGroup(). All filenames are expected to be in UTF-8. Example -file: +configuration file as an input, which lists files to be compiled as resources. +All filenames are expected to be in UTF-8. A configuration file can look for +example like this, with syntax matching what @ref Configuration understands: @code{.ini} -group=myGroup +group=game-data + +[file] +filename=license.txt [file] filename=../resources/intro-new-final.ogg alias=intro.ogg [file] -filename=license.txt +filename=levels/insane.conf +alias=levels/easy.conf +@endcode -[file] -filename=levels-insane.conf -alias=levels-easy.conf +The @cb{.ini} group @ce is an identifier that you'll subsequently pass to the +@ref Resource() constructor, each @cb{.ini} [file] @ce section then describes +one file to be compiled in, with paths relative to location of the +configuration file. By default, the @cb{.ini} filename @ce is the name under +which the files will be available when calling @ref getRaw() or @ref get() +later, including any directory separators. Use the @cpp alias @ce option to +override the name. + +There can be just one resource group or several, organization of the files is +completely up to you. For example, if there's a set of files used only by a +particular library but not other parts of the application, it might be useful +to have them in a dedicated group. Or if there's a lot of files, you might wish +to split them up into multiple groups to speed up the compilation and reduce +compiler memory use. + +@subsection Utility-Resource-compilation-cmake Using CMake + +Assuming the above file was named `resources.conf`, the following CMake snippet +will compile the referenced files into a C++ source stored inside the build +directory. Its filename gets saved into a @cb{.sh} ${MyGame_RESOURCES} @ce +variable, which subsequently gets passed to the @cb{.cmake} add_executable() @ce +call: + +@code{.cmake} +corrade_add_resource(MyGame_RESOURCES resources.conf) + +add_executable(MyGame … ${MyGame_RESOURCES}) +@endcode + +The @ref corrade-cmake-add-resource "corrade_add_resource()" macro also takes +care of dependency management --- if either the configuration file or any files +referenced by it are changed, it triggers a recompilation of the resources, +same as with usual C++ sources. + +The variable name also acts as a name used for symbols in the generated file +--- it has to be a valid C identifier and has to be unique among all resources +compiled into the same executable. But apart from that, you'd need the name +only if you deal with @ref Utility-Resource-usage-static "resources in static libraries" +as explained below. + +@subsection Utility-Resource-compilation-manual Compiling the resources manually + +If you're not using CMake, you can execute the @ref corrade-rc "corrade-rc" +utility manually to produce a C++ file that you then compile together with your +project. The following invocation would be equivalent to the above CMake macro +call: + +@code{.shell-session} +corrade-rc MyGame_RESOURCES path/to/resources.conf output.cpp @endcode +This will generate `output.cpp` in current directory, which you then compile +together with your sources. The first parameter is again a name used for the +symbols in the generated file. + +@section Utility-Resource-usage Accessing the resources + +If you compiled the resources directly into an executable or into a shared +library, you can access them from the C++ code without having to do anything +else. First instantiate the class with a group name matching the +@cb{.ini} group @ce value in the configuration file, and then access the files +by their filenames: + +@snippet Utility.cpp Resource-usage + +Because the data are coming from a readonly memory inside the executable +itself, the class returns non-owning views. In most conditions you can assume +unlimited lifetime of the data, see @ref getRaw() and @ref get() for details. + +@subsection Utility-Resource-usage-static Resources in static libraries + +If you compile the resources into a static library, the linker will implicitly +treat the data as unreferenced and won't include them in the final executable, +leading to a not-found assertion during @ref Resource construction. To prevent +this, you need to reference them. This can be done using the +@ref CORRADE_RESOURCE_INITIALIZE() macro, to which you pass the symbol name +used in the CMake macro or command-line invocation earlier: + +@snippet Utility.cpp Resource-usage-static + +It's important to call it outside of any namespace, otherwise you'll get a +linker error. The @cpp main() @ce function is ideal for this, or you can create +a dedicated function outside a namespace and then call it from within a +namespace. + +@subsection Utility-Resource-usage-override Overriding compiled-in resources + +For shorter turnaround times when iterating on compiled-in resources it's +possible to override them at runtime using @ref overrideGroup(). That way you +won't need to wait for a recompilation, relink and restart of the application +when making changes --- instead you tell the application itself to fetch the +data from the same location the resource compiler would, by pointing it to +the original `resource.conf` file on disk: + +@snippet Utility.cpp Resource-usage-override + +@ref Resource instance created after this point will parse the configuration +file and fetch the data from there, or fall back to the compiled-in resource on +error. The files get cached for the lifetime of a particular @ref Resource +instance, any subsequent changes in files thus get picked up only next time an +instance is created. + +@m_class{m-note m-success} + +@par + See also @ref Tweakable and @ref FileWatcher for other means of runtime + editing without recompilation and file-change-triggered operations. + +@section Utility-Resource-access Memory access and operation complexity + +Resource registration (either automatic or using +@ref CORRADE_RESOURCE_INITIALIZE()) is a simple operation without any heap +access or other operations that could potentially fail. When using only the +@ref hasGroup() and @ref getRaw() APIs with compile-time string literals, no +memory allocation or heap access is involved either. + +The group lookup during construction and @ref hasGroup() is done with a +@f$ \mathcal{O}(n) @f$ complexity as the resource groups register themselves +into a linked list. Actual file lookup after is done in-place on the +compiled-in data in a @f$ \mathcal{O}(\log{}n) @f$ time. + @section Utility-Resource-multithreading Thread safety The resources register themselves into a global storage. If done @@ -109,16 +221,17 @@ and thus is thread-safe. class CORRADE_UTILITY_EXPORT Resource { public: /** - * @brief Override group + * @brief Override a group * @param group Group name - * @param configurationFile Filename of configuration file. Empty - * string will discard the override. + * @param configurationFile Filename of the configuration file. Use an + * empty string to discard a previously set override. * * Overrides compiled-in resources of given group with live data - * specified in given configuration file, useful during development and - * debugging. Subsequently created @ref Resource instances with the - * same group will take data from live filesystem instead and fallback - * to compiled-in resources only for files that are not found. + * specified in given configuration file, which is useful during + * development and debugging. Subsequently created @ref Resource + * instances with the same group will take data from a live filesystem + * instead and fallback to compiled-in resources only for files that + * are not found in the overriden file. * * @attention Unlike all other methods of this class, this one is *not* * thread-safe. See @ref Utility-Resource-multithreading for more @@ -149,19 +262,34 @@ class CORRADE_UTILITY_EXPORT Resource { ~Resource(); /** - * @brief List of all resources in the group + * @brief List of all files in the group + * + * The resource group has no concept of a directory hierarchy --- if + * filenames in the input configuration file contain path separators, + * the returned list will contain them verbatim. * - * Note that the list contains only list of compiled-in files, no - * additional filenames from overridden group are included. + * Note that the list contains only the compiled-in files, no + * additional filenames supplied by an + * @ref Utility-Resource-usage-override "overriden group" are included. + * This is done to avoid overrides causing unexpected behavior in code + * that assumes a fixed set of files. */ std::vector list() const; /** * @brief Get resource data - * @param filename Filename in UTF-8 * - * Returns a view on data of given file in the group. Expects that - * the file exists. If the file is empty, returns @cpp nullptr @ce. + * Expects that the group contains given @p filename. If the file is + * empty, returns a zero-sized @cpp nullptr @ce view. If the file is + * not coming from an + * @ref Utility-Resource-usage-override "overriden group", the returned + * view can be assumed to have unlimited lifetime, otherwise it's alive + * only until the next @ref overrideGroup() call on the same group. + * + * The @p filename is expected to be in in UTF-8. Unlike with + * @ref Path::read(), no OS-specific treatment of non-null terminated + * strings nor any encoding conversion is done --- this function never + * allocates. */ Containers::ArrayView getRaw(const std::string& filename) const; @@ -172,10 +300,14 @@ class CORRADE_UTILITY_EXPORT Resource { /** * @brief Get resource data as a @ref std::string - * @param filename Filename in UTF-8 * - * Returns data of given file in the group. Expects that the file - * exists. + * Expects that the group contains given @p filename. If the file is + * empty, returns a zero-sized string. + * + * The @p filename is expected to be in in UTF-8. Unlike with + * @ref Path::read(), no OS-specific treatment of non-null terminated + * strings nor any encoding conversion is done --- this function never + * allocates. */ std::string get(const std::string& filename) const; @@ -251,7 +383,9 @@ of application execution. It's also safe to call this macro more than once. De-registers resource previously (even automatically) initialized via @ref CORRADE_RESOURCE_INITIALIZE(). After this call, @ref Corrade::Utility::Resource "Utility::Resource" will not know about given -resource anymore. +resource anymore. Useful for example when a resource was a part of a +dynamically loaded library (or a @ref Corrade::PluginManager "plugin") and it +needs to be cleaned up after the library got unloaded again. @attention This macro should be called outside of any namespace. See the @ref CORRADE_RESOURCE_INITIALIZE() macro for more information. diff --git a/src/Corrade/Utility/rc.cpp b/src/Corrade/Utility/rc.cpp index ad19dda06..e616cef50 100644 --- a/src/Corrade/Utility/rc.cpp +++ b/src/Corrade/Utility/rc.cpp @@ -32,11 +32,13 @@ namespace Corrade { /** @page corrade-rc Resource compiler -@brief Utility for compiling data resources via command-line. +@brief Resource compiling utility for @ref Utility::Resource. -Produces compiled C++ file with data in hexadecimal representation to be used -with @ref Utility::Resource. See @ref resource-management for brief -introduction. +@m_keywords{corrade-rc} + +Produces a C++ file with data in a hexadecimal representation to be compiled +into an executable and used with @ref Utility::Resource. See also +@ref resource-management for a tutorial. This utility is built if `WITH_RC` is enabled when building Corrade. To use this utility with CMake, see the @@ -56,14 +58,15 @@ for more information. @section corrade-rc-usage Usage @code{.sh} -corrade-rc [-h|--help] [--] name resources.conf outfile.cpp +corrade-rc [-h|--help] [--] name resources.conf output.cpp @endcode Arguments: +- `name` --- exported symbol name - `resources.conf` --- resource configuration file (see @ref Utility::Resource for format description) -- `outfile.cpp` --- output file +- `output.cpp` --- output file - `-h`, `--help` --- display this help message and exit */ @@ -74,18 +77,18 @@ using namespace Corrade; #ifndef DOXYGEN_GENERATING_OUTPUT /* LCOV_EXCL_START */ int main(int argc, char** argv) { Utility::Arguments args; - args.addArgument("name") + args.addArgument("name").setHelp("name", "exported symbol name") .addArgument("conf").setHelp("conf", "resource configuration file", "resources.conf") - .addArgument("out").setHelp("out", "output file", "outfile.cpp") + .addArgument("output").setHelp("output", "output file", "output.cpp") .setCommand("corrade-rc") - .setGlobalHelp("Resource compiler for Corrade.") + .setGlobalHelp("Corrade resource compiler.") .parse(argc, argv); /* Remove previous output file. Only if it exists, to not print an error message when compiling for the first time. If it fails, die as well -- we'd not succeed after either. */ - if(Utility::Path::exists(args.value("out")) && - !Utility::Path::remove(args.value("out"))) + if(Utility::Path::exists(args.value("output")) && + !Utility::Path::remove(args.value("output"))) return 1; /* Compile file */ @@ -96,8 +99,8 @@ int main(int argc, char** argv) { /* Save output */ /** @todo drop the StringView cast once resourceCompileFrom() is -free */ - if(!Utility::Path::write(args.value("out"), Containers::StringView{compiled})) { - Utility::Error{} << "Cannot write output file" << '\'' + args.value("out") + '\''; + if(!Utility::Path::write(args.value("output"), Containers::StringView{compiled})) { + Utility::Error{} << "Cannot write output file" << '\'' + args.value("output") + '\''; return 3; }