ALib uses the term "resources" for string data that technically may be defined in a constant and static fashion, but that a software volunteers to make configurable. Typical samples of data that a software exposes for external management are "themes" ( color and font schemes), or so called "externalized strings", which mostly may be used to translate a 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 a 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 a 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 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 "full" modules and those which are not. In this module dependency graph it could be noted that all "full modules" (dark blue) are dependent on module ALib Resources, discussed in this manual.
This relation is explained as follows:
Therefore, all "full" ALib modules not only depend on module ALib Resources, but also dispose about a singleton type derived from core library class Module.
ALib resources implemented with this module, 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 Configuration. This is also in alignment with common 3rd-party resource management systems established in the industry.
Independent from 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 Configuration. The rational for this is that while configuration data categories and names may be translated themselves to localized versions (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 using entities found with module ALib Configuration. For this, it disposes about a public member of type Configuration and installs one InMemoryPlugin with default priority. This plug-in fetches all resources fed 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).
Externalization is achieved when a custom plug-in is attached to the configuration object found with field ConfigResourcePool::Config. For example, this could be of type IniFile and be attached with priority Priorities::Standard.
With that, any entries made in that INI-file, would simply overwrite the corresponding resource values which use the lower default priority.
With this type a very simple but yet powerful way of externalizing resource data is given. 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. TMP struct T_Resourced 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 Configuration: 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 about a specialization of T_Resourced is passed to such interface method, internally this information is used to load further details of the variable from the resource pool.
As soon as struct T_Resourced 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 T_Resourced. 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 T_Append<TEnum,TChar> and T_Append<TEnumBitwise,TChar> 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 T_Resourced, in most use cases it is, because it allows to convert the compile-time information given by T_Resourced 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 Enums 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 compiler symbol ALIB_DEBUG_RESOURCES, static field LocalResourcePool::DbgResourceLoadObserver becomes available. If set to &std::cout
prior to 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 simple 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 method ResourcePool::DbgDump becomes available which simply writes the list of symbols into an AString, sorted by category and in an 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, a 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 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, prior to invoking ALIB.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.