ALib uses the term "resources" for string data that technically may be defined in a constant and static fashion, but that software volunteers to make configurable. Typical samples of data that software exposes for external management are "themes" ( color and font schemes), or so-called "externalized strings", which mostly may be used to translate software into a different "locale" and human language.
While the conceptual definition of resources is very similar to the concept of configuration data, it is good practice to thoroughly decide for each specific piece of data that is deemed to be made configurable, whether it is resource or configuration data. While the mentioned samples of language translation strings and color schemes are typically resources, a server name that software attaches to is typically configuration data. End users commonly know and understand the difference between the two intuitively and appreciate if the corresponding data sources are separated, especially as this way, resource data does not "clutter" the definitions of configuration sources.
The best rule of thumb to differentiate resource from configuration data is to check if it is possible to distribute software without turning the data in question into either of the two externalization concepts, thus "hard coding" the data. If so, the data more likely is resource data. In the sample of the "server address" above, this is not possible if such a server address targets a machine that is under the control of an end user. However, if it is a machine provided by the distributor of the software which is the same with any installation, then the concept of resourced data rather fits.
As documented in chapter 2. ALib Modules of the ALib Programmer's Manual, the various ALib Modules can be separated into those who are ALib Camps and those which are not. In this module dependency graph it could be noted that all ALib Camps are dependent on module ALib Camp, which in turn is dependent on this module ALib Resources.
This relation is explained as follows:
Therefore, all ALib Camps not only depend on module ALib Camp, but also dispose of a singleton type derived from class Camp.
ALib resources implemented with this namespace, conceptually impose a set of rules which may be named a "contract", "invariants" or just "determinations". These rules are given in the following subchapters.
Key values to resources are defined as a two-level hierarchy of a category and a name. This determination complies with how configuration data is addressed with module ALib Variables. This is also in alignment with common 3rd-party resource management systems established in the industry.
Independent of the compilation options of ALib in respect to choosing the default character width, the string-type of the category and name is fixed to NString, hence using the narrow nchar type. This is not in alignment with configuration data as defined in ALib Variables. The rationale for this is that while configuration data categories and names may be translated themselves to localized versions (preferably by defining those strings as resources!), the category and name strings of the resources are deemed to be hard-coded in the source code that accesses resources. As such, they are not shared with end-users, are never localized, and should be using the plain 7-bit printable ASCII character set.
All resource data is of String type (of compilation-dependent character width).
This restriction imposes that any other data type (for example color codes) has to be de-serialized (decoded) when resourced.
Although resource data technically is non static data, conceptually with this implementation it is.
This determination has the following impacts:
Accessing resources using abstract interface method ResourcePool::Get is a thread-safe operation.
In contrast to this, an invocation to any of the methods that define resources, namely ResourcePool::Bootstrap and ResourcePool::BootstrapBulk is not a thread-safe operation. This is true in respect to each other as well - and most important - also in respect to parallel resource access with method Get.
This determination has the following impacts:
The central type of the module, class ResourcePool was already mentioned several times. It constitutes a pure abstract interface. Due to the determinations of the concept given in previous chapter 3. Data Contract / Conceptual Invariants, its interface is very simple especially in respect to accessing resources, which is exclusively done with method ResourcePool::Get.
A user of ALib should have no bigger effort to implement this interface and adopt her own or any 3rd-party "backend" that performs the resource management and the externalization of strings.
Apart from that, two implementations of the interface are provided with ALib. Those are quickly introduced in the following sections.
As explained above, an implementation of interface ResourcePool has to be constructed during bootstrap of ALib and distributed among the modules.
In case the bootstrap process is not customized, an instance of class LocalResourcePool is created and shared.
This class does not allow any externalization of resources and simply stores the given pointers to the static data in a HashTable, using monotonic allocation.
A second built-in implementation of class ResourcePool which can be created and shared among the modules of ALib by customizing the bootstrap process of the library, is given with class ConfigResourcePool.
The type externalizes resources by inheriting from class Configuration. With that, flexible ways of externalizing the data are given. The class is enabled to fetch all "hard-coded" resources fed by camps with methods Bootstrap and BootstrapBulk.
If after creation of an instance of the type, this instance is not changed and just used, then it behaves in an identical way as type LocalResourcePool (with only weaker performance and increased memory overhead).
The huge advantage of this type is, that any custom externalization is achieved along the very same lines as done with configuration data. Thus, a user of the library that attached such data, will have no problem in also load resources from external custom sources.
The type is suitable in any situation where no other ("more professional") 3rd-party "backend" for resource management is available.
Here are some tips for the usage:
Sometimes it is required to define resource information, namely
for use by other components. Type trait ResourcedTraits may be specialized to do such definition for C++ types. A specialization of the struct can be easily implemented using macro ALIB_RESOURCED.
A sample for the use of this struct is given with module ALib Variables: To load and store configuration data, this module exposes a type of ALib Enum Records and accepts custom enumeration types in various interface methods, if they just have this specific record type associated.
Now, if an element of a custom enumeration type that disposes of a specialization of ResourcedTraits is passed to such an interface method, internally this information is used to load further details of the variable from the resource pool.
As soon as struct ResourcedTraits is specialized for a type, helper-struct static struct ResourcedType becomes available.
The type has two overloaded methods Get: The first is parameterless and simply receives the resource string associated to a type with the specialization of ResourcedTraits. The second takes a replacement for the resource name. This may be used to retrieve resource strings which are likewise associated to the type.
Furthermore, the struct provides methods TypeNamePrefix and TypeNamePostfix which are meant to provide a standardized way to define a type's name using resources. The methods are for example used with specializations AppendableTraits<TEnum,TChar,TAllocator> and AppendableTraits<TBitwiseEnum,TChar,TAllocator> which write enum element names into instances of type AString.
A next helper-struct is given with ResourceInfo which first of all is a simple struct that stores resourcing information (the resource pool and category and name strings) for later use.
While this struct is usable without a specialization of ResourcedTraits, in most use cases it is, because it allows converting the compile-time information given by ResourcedTraits into run-time information.
Besides just externalizing strings, many use cases require to access externalized data sets or even whole tables of this.
ALib module ALib EnumRecords provides a solution for this with its concept ALib Enum Records. There a table of data is addressed using the C++ type information of enum types. Single records of a table may (or may not) be addressed by elements of the corresponding enumeration. The module provides convenient facilities the fields of the records and whole tables from resourced strings.
Before you go ahead and implement your "own" solution for externalized static data, it might be worthwhile to check out if ALib Enum Records meet your requirements.
When resources are externalized, for example for translation to different human languages, the list of resources have to be imported to the external "backend". To do so all resources have to be queried from the library.
Here is a sample program that performs this task:
The output of this sample can directly be loaded by class IniFile, hence with a plug-in attached to an instance of built-in resource pool implementation ConfigResourcePool. The sample might be adopted accordingly to write a format suitable to be imported to the backend of choice.
With every update of the library, changes of the resource strings have to be determined. This might be done for example with a custom unit test that compares the default entries with the ones currently stored in an external backend. New resources might be added, others might disappear, and worst: existing ones might change their content format. In the latter case, an externalized resource might be errorneous and lead to undefined behavior.
Starting with library version 1903, to support the detection of changes, the version history is found in
ALIB_BASE_DIR/docs/pages/resource-exports/
The following exports are available:
With the provision of the compiler-symbol ALIB_DEBUG_RESOURCES, static field LocalResourcePool::DbgResourceLoadObserver becomes available. If set to &std::cout
before bootstrapping ALib, the resource load process can be observed on the console, because methods LocalResourcePool::BootstrapBulk and LocalResourcePool::BootstrapAddOrReplace will write information on bulk and singular resource data definitions. This tremendously helps to find errors in resource strings, which often are simply missing commas and similar.
Next, virtual method ResourcePool::DbgGetCategories and ResourcePool::DbgGetList become available. The latter returns a list of all resources defined, including a usage counter. The method is only implemented by class LocalResourcePool. Alternative type ConfigResourcePool does not implement it.
Furthermore, convenience function DbgDump becomes available which simply writes the list of symbols into an AString, sorted by category and in alphabetical order.
The usage counter included in the list may be used to identify two groups of resource strings:
While entries of the first type may have become obsolete and are candidates for removal, those of the second type, software might consider to "cache" the symbol in a variable instead of repeatedly retrieving it from the resource pool.
Remember: While trivial implementation class LocalResourcePool is very fast and resource access is not really noticeable, other implementations might not be.
The following code snippets taken from the ALib unit tests, demonstrate how to quickly leverage these debug features, depending on the compiler-symbol ALIB_DEBUG_RESOURCES. The snippets might be copied to own projects and remain there forever.
Bootstrapping may look as follows:
Before termination of software (hence, before invoking Shutdown), the following code may be placed:
A comprehensive sample of using ALib resources placed in a custom module is provided with the tutorial of ALib Module 'CLI'. The sample code provided there can be easily used as a jump start into an own project that creates a custom ALib module and leverages the resource features provided.