Schemas and Configs
OpenGameData uses a complex web of Schema subclasses to manage configuration of systems, as well as documentation of features and data.
Overview of Config and Schema Classes in OGD-Common Library
This section will give an overview of the categories and class hierarchies of each config and schema class in the opengamedata-common Python library, which we will also refer to informally as OGD-Common or Common, for short.
In order to manage configuration data, as well as descriptions of the configuration or structure of external data and data sources, OpenGameData uses a broad collection of classes called Schemas.
Within this broad collection is a subcategory of classes called Configs.
The distinction between these groups is as follows:
Configclasses are used for the case of data related to the configuration of an OpenGameData tool. This may include things like the location of a local file for writing, or the credentials to access a database.Schemaclasses are used for any other case where data related to external data or tools are needed. This may include things like descriptions of events logged by a particular game, or the mapping of columns from an external data source to the internal structure used by OpenGameData.
Examples Uses of Config and Schema
The two clearest, most practical examples of this distinction relate to data connections, and feature extraction:
Data Connections :
ogd-commonusesStorageConnectorsubclasses, discussed in the chapter on “Interfaces,” to connect to data stores either to import or export data. Data stores may include a local file, an external file host, or a database:--- title: Basic Data Stores --- flowchart TD StorageConnector --> local[(Local File)] StorageConnector --> remote[(Remote Fileserver)] StorageConnector --> Database[(Database)]However, each data store might in turn contain multiple resources, such as files or database tables. To handle this, there are several classes of the
Configvariety that handle the configuration of aStorageConnector, such as the location and credentials to access a given data storage resource. On the other hand, the location and table structure of individual resources within a data store are properties of that external store, thus they are handled with classes of theSchemavariety.Feature Extraction : The writing and execution of feature extractor modules requires both a knowledge of the event types logged by a game, and a way to specify which extractor modules to execute. Both the game-defined events and executed extractor modules are included in the
README.mdfiles included with each data export. Classes of theSchemavariety are used for managing the specifications of game events, andConfigclasses are used to manage the specification of extractor modules.
Config Categories and Classes
games Configs
---
title: games Configs
---
classDiagram
note "Showing subsets of public properties"
class Config
class GameGeneratorsConfig {
+ GameID
+ Detectors : DetectorMapConfig
+ Extractors : ExtractorMapConfig
+ LevelRange
+ OtherRanges : Dict[str, range]
}
class FeatureMapConfig {
+ AggregateFeatures
+ PerCountFeatures
+ LegacyPerLevelFeatures
+ LegacyMode : bool
}
class DetectorMapConfig {
+ AggregateDetectors
+ PerCountDetectors
+ PerLevelDetectors
}
class GeneratorConfig
class FeatureConfig
class DetectorConfig
class SubfeatureConfig {
+ ReturnType
+ Description
}
class DetectorConfig {
+ Enabled
+ Description
+ ReturnType
+ Subfeatures
}
class AggregateConfig {
+ Enabled
+ Description
+ ReturnType
+ Subfeatures
}
class PerCountConfig {
+ Enabled
+ Description
+ Prefix
+ Count
+ ReturnType
+ Subfeatures
}
GameGeneratorsConfig *-- DetectorMapConfig
GameGeneratorsConfig *-- FeatureMapConfig
DetectorMapConfig o-- DetectorConfig
FeatureMapConfig o-- AggregateConfig
FeatureMapConfig o-- PerCountConfig
GeneratorConfig <|-- DetectorConfig
GeneratorConfig <|-- FeatureConfig
FeatureConfig <|-- AggregateConfig
FeatureConfig <|-- PerCountConfig
AggregateConfig *-- SubfeatureConfig
PerCountConfig *-- SubfeatureConfig
Storage Config Schemas
With the hierarchy separating connection from data read/write, we can also improve how configs are set up.
So, with the new class hierarchy, we should also have a more formal splitting up of configs and schemas, and then let core adjust to what makes sense for common.
First, we separate the “config” portion, which defines the data source and/or destination.
In particular, each StorageConnector will take a DataStoreConfig that has a location and credential to access the storage. DataStoreConfig will be a base class, though not necessarily abstract, with subclasses written to pair with individual, specific StorageConnector subclasses, which will check for the appropriate DataStoreConfig subclass.
That way, we can support configs being specific to storage type.
Credential Configs
For the “credential” element of a config schema, we’ll have a CredentialConfig base class, with subclasses for PasswordCredentialConfig containing a username-password pair, as well as a KeyCredentialConfig with just a path to a key file. These can be expanded as needed.
Location Configs
For the “location” element of a config schema, we’ll have a LocationConfig base class, which has just a “location” string. Subclasses include HostLocationConfig, which will add a port and a “host” prop mapped to location var; and FileLocationConfig, which will add a folder path, call “location” the file path, but treat combined path as the true “location.” Or something like that.
Overview of New Storage Configs
The resulting diagram looks like:
classDiagram
DataStoreConfig *-- "1" CredentialConfig
DataStoreConfig *-- "1" LocationConfig
CredentialConfig <|-- PasswordCredentialConfig
CredentialConfig <|-- KeyCredentialConfig
LocationConfig <|-- HostLocationConfig
LocationConfig <|-- FileLocationConfig
StorageConnector *-- "1" DataStoreConfig
Interface Schemas
So, if the DataStoreConfigs have the information to connect to a storage location, the actual interface/outerface classes need information to locate the necessary data within that location.
These will be “schemas” in the sense defined by #64, as they describe the structure of a resource, rather than configuring how/where to connect to the resource.
For that, we’ll have a TableSchema that includes both a TableStructureSchema and a TableLocationSchema.
Table Structure Schemas
The TableStructureSchema is the thing currently called TableSchema.
It will have subclasses for EventTableStructureSchema and FeatureTableStructureSchema, which look for event-specific and feature-specific columns.
Table Location Schemas
A TableLocationSchema has whatever information is needed to locate the table within the given resource.
The main subclass would be DatabaseTableLocationSchema, which would have a database and a table.
classDiagram
TableSchema *-- "1" TableStructureSchema
TableSchema *-- "1" TableLocationSchema
EventTableStructureSchema <|-- TableStructureSchema
FeatureTableStructureSchema <|-- TableStructureSchema
TableLocationSchema <|-- DatabaseTableLocationSchema
Configuration Wrapper
All of the various configs and schemas can be wrapped in a GameDataConfig or something, which has schemas defining all the input (or all the output) info for a given game. This means up to two TableSchemas, where one is for events and the other for features.
Schema Categories and Classes
Schema
This is the base class for all Schema and Config classes in Common.
It defines the following abstract functions for all other Schema and Configs to implement:
AsMarkdown: A property returning a markdown-formatted string summarizing theSchemasubclass data.FromDict: A function to instantiate theSchemasubclass from a JSON-style dictionary.Default: A function to generate a default instance of theSchemasubclass.
Schema also provides the following utility functions as part of the interface for all its subclasses:
FromFile: A simple function to instantiate theSchemaclass/subclass from a file, assumed to contain a JSON-style dictionary. It invokes a_fromFileprivate function whose default implementation assumes a file with.jsonextension, and JSON-style formatting of the file contents.Schemasubclasses that are meant to be loaded from other file formats may overload the_fromFilefunction.ParseElement: A utility function that retrieves a specified element from a dictionary of yet-unparsed schema elements, utilizing theconversionmodule functions inogd.common.utils.typeto ensure the retrieved value has been converted to the desired type.ParseElementalso allows for multiple candidate keys to be given for the element, in order to support ‘legacy’ formats. For example, if an older specification had an element with key"DB"and a newer specification uses"DATABASE", the old key name can be used as a fallback in case the newer key is missing.Further,
ParseElementsupports multiple candidate types to be given for the element, in order to support formats that allow different kinds of data to be used for the same key. For example, if a “range” element is allowed to be either a 2-element list or 2-element tuple of ints, bothListandTuplecan be given for the type.
Structurally, the Schema base class only stores a schema name and a dictionary of “non-standard elements.”
When a Schema subclass is instantiated from a JSON-style dictionary with the FromDict or FromFile functions, any elements not used by classes in the inheritance chain go into the “non-standard elements.”
Both the name and non-standards are accessible outside the class via properties:
NameNonStandardElementsNonStandardElementNames
Dataset Schemas
Event Logging Schemas
Table Schemas
TableSchema
This class stores a location and structure for a database table containing feature or event data.
These are each stored using component Schema subclasses, which are accessed by properties:
Structure: ATableStructureSchemasubclass, eitherEventTableStructureSchemaorFeatureTableStructureSchema, which have differing elements based on which columns are in the respective standard elements for Events and Features. For full details on these differences, read the unit 2 section on event schemas and the unit 3 section on feature data TODO: Link to the sections (note the unit 3 section is not yet written).Columns: TheColumnsspecifications stored within theStructureobject - this is provided for convenience of access.Location: ATableLocationSchema, or a subclass. This contains information on how a table is accessed within a resource, via aStorageConnector. For example, aStorageConnectormay be configured with aDataStoreConfigto grant access to a database, and theTableLocationSchemacontains information on where the given table is located within the database.
Table Structures vs. Data Structure Specifications
In order to understand the functionality of the schema classes that manage table structures, it is important to make a distinction between the use of the terms “table” and “data structure” for this discussion.
OpenGameData tools use internal representations of events and features. These each have specifications, which are given elsewhere. TODO: Link to the specs, wherever we have them right now. We will refer to these internal representations as the data structures, and the individual pieces of data within those structures as elements.
On the other hand, game data is stored in files, databases, or other table-based (tabular) formats. We call these data tables, and each data table is made up of columns.
In a perfect world, the structure of all data tables would match our data structures
It is useful to think of data structures in this context as ideal abstractions of events and features, and of data tables as the concrete, real-world implementations. Then in order for OpenGameData tools to operate on any data, we need a way to use the columns of data tables to fill the elements in an instantiation of the corresponding data structures.
TableStructureSchemas
The TableStructureSchema base class is meant, via its subclasses, to store information about the structure of a table storing game data.
Table structures can be complicated to manage, because over time and across organizations, the details of columns in data tables may vary.
This could be due to changes in the data structure specification (e.g. an element of the structure is removed in newer specs), or due to porting an existing system to operate with OpenGameData (e.g. an existing system contains elements in its structure that are not found in the spec).
These issues become apparent when column sets differ across data tables.
The TableStructureSchema classes allow us to:
Document the columns of the source data table
Define a mapping of data table columns to data structure elements. This is an important distinction. Tables have columns, which are real-world and vary over time and across organizations. The specification of an OGD
EventorFeatureDatahas elements, and OGD will always use the latest specification internally. Thus, we have this mapping to indicate how the literal table columns should be used to construct instances of OGD’s internal data structures.
In fact, these correspond the exact structural data available from a TableStructureSchema, or any of its subclasses, via properties:
Columns: AListofColumnSchemaobjects. WhileColumnSchemais not given its own section here, these are simply schemas whose own structural elements are as follows:ReadableName: A human-readable version of the column name - theNameproperty provided by theSchemabase class will contain the actual name of the column within the data table.Description: A simple text description of what data the column stores.ValueType: The data type stored in the column.
The
ColummnSchemaclass does nothing else special or unique - it simply makes the specification of a real-world column’s contents available.ColumnNames: A list of all theNamevalues from theColumnSchemas given by theColumnsproperty. Note this is not a list of theReadableNamevalues.ColumnMap: A mapping from element names toColumnMapIndexvalues.ColumnMapIndexis simply an alias for a value that could be anint, aList[int], aDict[str, int], orNone.Other Properties : The class also has a series of properties for accessing individual parts of the column mapping. For each element common to event and feature data structures, there is a pair of properties. These are the “index” and “column” props, named like
<ElementName>Indexand<ElementName>Column. The “index” property gets theColumnMapIndexfor the element, indicating which column(s) of the table is/are mapped to the element. The “column” property gets the name of the column mapped to the element, or string with comma-separated names of the column._fromDictAbstract Function : Finally,TableStructureSchemaimplements aFromDictfunction, allowing it to handle construction of the data it holds. In turn, it defers to its subclasses to implement a new abstract function called_fromDict, which is meant to handle parsing for the specific data added by subclasses.
EventTableStructureSchema and FeatureTableStructureSchema
These subclasses of TableStructureSchema add more properties for the specific elements of their data structures.
In addition, they implement the abstract functions for AsMarkdown and Default, which TableStructureSchema does not.
Further, they implement the _fromDict abstract function introduced by TableStructureSchema.
Finally, they implement RowToEvent and RowToFeatureData, respectively, for converting raw data from a data table into the specific data structures.
Database Schemas
Below are the table schemas for each of the existing databases.
OpenGameData
The OpenGameData database (named opengamedata in MySQL, no upper-case letters) contains one table for each game; however, the table schema is identical across tables.
The table names are:
AQUALAB
BACTERIA
BALLOON
CRYSTAL
CYCLE_CARBON
CYCLE_NITROGEN
CYCLE_WATER
EARTHQUAKE
ICECUBE
JOWILDER
LAKELAND
MAGNET
MASHOPOLIS
PENGUINS
SHADOWSPECT
SHIPWRECKS
STEMPORTS
WAVES
WIND
Column Name |
Column Type |
Nullable |
|---|---|---|
id |
int(32) |
NO |
session_id |
varchar(32) |
NO |
user_id |
varchar(64) |
YES |
user_data |
text |
YES |
client_time |
datetime |
NO |
client_time_ms |
smallint(6) |
NO |
client_offset |
time |
YES |
server_time |
timestamp |
NO |
event_source |
enum(‘GAME’,’GENERATED’) |
NO |
game_state |
text |
YES |
app_version |
smallint(6) |
NO |
app_branch |
varchar(32) |
YES |
log_version |
smallint(6) |
NO |
event_sequence_index |
bigint(64) |
NO |
remote_addr |
varchar(32) |
NO |
http_user_agent |
text |
YES |
Logger
The Logger database (named logger in MySQL, no upper-case letters) contains a single table named log.
It has the following schema:
Column Name |
Column Type |
Nullable |
|---|---|---|
id |
int(32) |
NO |
app_id |
varchar(32) |
NO |
app_id_fast |
enum(‘UNDEFINED’,’WAVES’,’CRYSTAL’,’JOWILDER’,’LAKELAND’) |
NO |
app_version |
int(32) |
NO |
session_id |
varchar(32) |
YES |
persistent_session_id |
varchar(32) |
YES |
player_id |
varchar(6) |
YES |
level |
int(32) |
NO |
event |
enum(‘BEGIN’,’COMPLETE’,’SUCCEED’,’FAIL’,’CUSTOM’,’UNDEFINED’) |
YES |
event_custom |
int(32) |
NO |
event_data_simple |
int(32) |
NO |
event_data_complex |
text |
YES |
client_time |
timestamp |
NO |
client_time_ms |
int(32) |
NO |
server_time |
timestamp |
NO |
remote_addr |
varchar(32) |
YES |
req_id |
bigint(64) |
NO |
session_n |
bigint(64) |
NO |
http_user_agent |
text |
YES |