Rules Data Store
Background
This document is primarily intended to communicate the design of PCGen Rules Data Store.
This document provides a detailed overview of the architecture of a specific portion of PCGen. The overall architecture and further details of other subsystems and processes are provided in separate documents available on the Architecture page.
Overview
The Rules Data Store is one of the major components of PCGen. It is responsible for storing information about game systems. As a "Data Store", it is very much akin to a complicated database (it could be converted into an actual database with some effort). The Rules Data Store is not designed to have methods that interpret the contents of the Rules Data Store, nor is the Rules Data Store capable of storing the information it contains into a persistent state. The conversion to and from a persistent state is owned by the Rules Persistence System.
Requirements
Flexible Data Structure
Information should be stored in a fashion that minimizes fixed data structures, including, but not limited to, fixed field names within CDOM Object implementations.
Fixed data structures drive code changes when additional features are added to PCGen. The end design goal should allow for new features to be added to PCGen entirely through plugins/data, without modification of the core. This means the Rules Data Store must store data in a fashion that does not fix or limit what information can be stored within the Rules Data Store.
Separate Static and Dynamic data
Information about a CDOM Object should be separate from information about how the object affects a Player Character.
Storing both stable and dynamic information within the same object means the entire object must be cloned in order to be re-used. This results in a waste of resources (duplicate information) which can be eliminated if a CDOM Object does not store dynamic information. Implementing this structural requirement will mean CDOM Objects will not need to be cloned in order to be placed within a PlayerCharacter.
Sub-components
CDOM Objects
CDOM Objects are the primary component of the Rules Data Store. They store information about the impact to a Player Character and other relationships that the CDOM Object has within PCGen.
Reference Context
The Reference Context is the indexing system used to determine what objects exist within the Rules Data Store. For purposes of the Rules Data Store, the Reference Context is capable of returning all of the objects of a given type (e.g. Language), or an object of a given type that is referred to by a specific key.
Master List Interface
The Master List Interface is used to index the initial contents of "global" CDOMLists. These global lists include things like ClassSpellLists, which are initialized by the contents of the CLASSES token within Spell LST files.
Helpers
Helpers are objects that are designed to be stored in CDOM Objects. These objects store information in a type-safe fashion, and often provide useful methods around the information contained within the Helper. Helpers are generally very simple objects to store a very small group of closely related information.
Content
Content are objects that are more complex information that can be provided to a PlayerCharacter. These objects store information in a type-safe fashion, and often provide useful methods around the information contained within the Content. Content objects may also have Prerequisites that a Player Character must pass before the Content can be used by the Player Character.
It is noted that the distinction between Helpers and Content in PCGen 5.16 is extremely weak. The reason for the distinction is future versions of PCGen may use take up the concept of "granting" objects, in which case Content could be granted and Helpers could not.
Key Design Decisions
Value Semantic Data Structure
The Rules Data Store will be a Value-Semantic data structure, in order to protect the contents of the Rules Data Store from unintended modification.
First, this avoids confusion. Reference-Semantic structures allow internal contents to be extracted via a "get" method and then the internal structure of the object can be modified. This is considered by some a violation of object-oriented design, and can be highly confusing if a developer expects a "get" to really mean "get" and not "get in order to set".
Second, in restricting the design, this minimizes the change for unintended consequences. Accidental modification of a data structure that should not be changed is possible in reference semantic systems. It is, by definition, impossible in value semantic systems.
Specifically, CDOMObject will not allow any of its internal data to be modified by any means other than through a method called on CDOMObject. There are some items stored in lists in the CDOMObject, and those lists will be copied before they are returned to the calling method. The consequence of this is that any modification to the returned list will NOT impact the CDOMObject. Thus, there is never a need to make a defensive copy of any Collection returned by CDOMObject.
CDOMObject stores information in maps based on keys
CDOMObject will store information indirectly in Maps, with keys used to index the information stored within the CDOMObject.
This minimizes the number of fields within CDOMObject to less than ten. This helps to minimize the size of the code that has to be considered when evaluating changes. This also provides tremendous code reuse, as there are a limited number of methods used to access data in a CDOMObject.
This also eliminates almost all fixed knowledge within CDOMObjects. Since the information is stored in a key-value pair, the CDOMObject class and derivaties don't require changes when new keys (and thus new behavior) are defined and added to PCGen.
There are two important components to the storage of information in maps within the CDOMObject. The first is the maps themselves. There are individual maps for different types of objects, including Integers, Strings, Formulas, Variables, and Lists. There is also a "catch all" Object map.
Each of these maps has an associated enumeration serving as the keys to the map. These enumerations are found in pcgen.cdom.enumeration.
Some of these keys are defined in the enumeration class to have default values. This is especially valuable for Integers, Formulas, and Objects (specifically when the Object is a Boolean). Using the getSafe methods within CDOMObject allows a calling method to get either the value set for the given key within the CDOMObject, or if there is no defined value for the given key, then the default value defined in the enumeration is returned. As a side effect, the default values for all of the keys of a given type are available within a single file (the enumeration class file).
CDOMLists do not contain objects
CDOMLists do not contain CDOMObjects. Rather they are an identifier used to identify a list of objects. The CDOMObjects contained within the CDOMList for a given PlayerCharacter must be stored independently of the CDOMList.
Lists of CDOMObjects (such as Spell Lists and Skill Lists) may be modified by tokens contained within CDOMObjects added to a PlayerCharacter. An Ability may allow a specific Spell to be added to the Spell List of a Class, for example. In order to allow this dynamic modification of lists while avoiding the storage of redundant information, a CDOMList may not contain the objects in the list; rather, it must be an identifier to the list of objects.
A Master List Interface is used to store global lists (such as Class Spell Lists). Modifications to those Master Lists (or to other lists that are built dynamically as a Player Character is generated) can be stored within CDOMObjects. These list modifications are indexed by the CDOMList object identifying the list to be modified. Specific associations to the list (such as spell level or skill cost) are stored as a target with the CDOMObject. (The list modifications are stored as a two key map: The first key is the CDOMList, the second key is the CDOMObject(s) to be added to the list, the target of the map is a list of associations.)
Class Levels are separate objects
Class Levels are separate objects from the Class object.
This is primarily done in order to simplify management of class levels. Substitution classes (Which replace class level behavior) are easily implemented in a system that stores class levels as separate objects. In addition, this use of a separate objects avoids contracts in the addition of variables to the Class Level (as individual variables don't need to be cleared for substitution classes, an entirely new class level can be created)
Class levels are separate objects, but are stored within the PCClass.
Equipment Heads are distinct objects
Equipment heads are stored as distinct objects from the Equipment.
The current Rules Persistence System assumes that a piece of Equipment can have only two heads, but this is an arbitrary limitation. It also causes code duplication when primary and alternative methods are required in order to query information out of the Equipment.
EquipmentHead is now a separate CDOMObject used to store information that is specific to a specific "head" (such as damage, critical hit range, etc.). The EquipmentHead is known to (and stored in) the Equipment.
Characteristics/Weaknesses of the existing system
- Value semantic systems can suffer a performance penalty, because items like lists must be copied before they are returned by a "get" method on a value-semantic object. The initial design for the Rules Data Store is a fully value-semantic Rules Data Store, Any specific performance isuses once the Rules Data Store is fully implemented.
- In PCGen 5.16, the enumeration classes require modification to add a new key. However, adding the capability to define new keys dynamically is likely to be added to a future version of PCGen.
- The CDOMObject stores information in maps based on keys design choice has a mixed effect on memory usage. In PCGen 5.16, all of the maps are constructed, even if they are empty. This provides a memory overhead that may be removed by using lazy instantiation in a future version of PCGen.
- The ReferenceContext serves both the Rules Data Store and the Rules Persistence System, and we should probably break out this behavior of Reference Context from the behavior in the Rules Persistence System. It may be confusing to have two functions using the same name, especially as this one is intended to be much more limited than the Reference Context behavior in the Rules Persistence System. This may simply be done by having the Reference Context implement a read-only interface designed to be used at runtime.
- Depending on the ReferenceContext naming above, The Master List Interface may need a different name.