Skip to content

Commit

Permalink
Utility: write proper docs for the Resource class.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mosra committed Mar 10, 2022
1 parent 7cf082a commit 60f6489
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 59 deletions.
1 change: 1 addition & 0 deletions doc/corrade-changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
38 changes: 38 additions & 0 deletions doc/snippets/Utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -801,6 +802,43 @@ const char* shader = "#line " CORRADE_LINE_STRING "\n" R"GLSL(
static_cast<void>(shader);
}

{
/* [Resource-usage] */
Utility::Resource rs{"game-data"};

std::string licenseText = rs.get("license.txt");
Containers::ArrayView<const char> 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<void>(soundData);
}

{
struct Foo {
/* [Resource-usage-static] */
int main(int argc, char** argv) {
CORRADE_RESOURCE_INITIALIZE(MyGame_RESOURCES)

DOXYGEN_ELLIPSIS(static_cast<void>(argc); static_cast<void>(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
Expand Down
226 changes: 180 additions & 46 deletions src/Corrade/Utility/Resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<std::string> 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<const char> getRaw(const std::string& filename) const;

Expand All @@ -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;

Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 60f6489

Please sign in to comment.