The Config System#

In this section we will cover:

  • How Terra defines how configs must be structured.

  • How these structured configs are interpreted and utilized by Terra.

  • How we can write configs that abide by these prescribed structures.

Templates#

Most configurations follow what we will call a template. Templates can be thought of as special versions of maps which specify rules on how the contained key-value pairs must be structured. Many different templates are defined by Terra and can each be regarded as their own types, just like strings and integers.

Parameters#

One of the main components of any given template is It’s specification of parameters. Parameters are what makes our data useful, as just about all world generation behavior in Terra is determined by parameters.

The specification of a parameter inside a template involves:

  • What the name of the parameter is (which is also how it is referenced inside a template).

  • Whether the parameter is optional or required.

  • What the types can the parameter be set to.

To set parameters within a template, we use the key to denote which parameter we want to set, and the value to what the parameter should be set to. For example, here are a couple parameters specified within a Terra config pack manifest that provides some basic information about the pack:

pack.yml#
1id: COOL_CONFIG_PACK
2version: 1.0.0
3author: Anon Y. Mous
pack.json#
1{
2   "id": "COOL_CONFIG_PACK",
3   "version": "1.0.0",
4   "author": "Anon Y. Mous"
5}

Parameters may also be nested under multiple maps within a template. For example in the below config, the top level object may abide by a template and specify a parameter that is nested, which we can see has the value a parameter value.

config.yml#
1a:
2  nested:
3    parameter: a parameter value
config.json#
1{
2   "a": {
3      "nested": {
4         "parameter": "a parameter value"
5      }
6   }
7}

When referring to parameters within templates, we use a combination of the parent key(s) separated by dots . to identify the desired parameter. Using the above config, we would refer to the specified parameter as a.nested.parameter.

Some parameter conventions to keep in mind:

  • The parent key object(s) of a parameter will always be of type string, where all characters are lowercase, and dashes - are used in place of spaces.

  • The type of the value object is considered the parameter’s type.

Working With Templates#

For the sake of explanation, let’s invent a new template to work with called AnimalTemplate. The AnimalTemplate type specifies:

  • A required parameter called color that must be of type string

  • A required parameter called legs that must be of type integer.

We can then write a new config using our new type assuming our top level object is of type AnimalTemplate:

koala.yml#
1color: grey
2legs: 4
koala.json#
1{
2   "color": "grey"
3   "legs": 4
4}

Because AnimalTemplate contains these parameter specifications, if we write a config that does not abide by them, then Terra will fail to load the config. For example, the following config would not load because 1. color has not been specified and is a required parameter, and 2. legs is not of the required type integer:

koala.yml#
1legs: two
koala.json#
1{
2   "legs": "two"
3}

If we were to document AnimalTemplate, it may look like this:

AnimalTemplate

Defines the attributes of an animal.

REQUIRED

color String

The color of the animal.

legs Integer

How many legs the animal has.

Great, now that we have a template to describe an animal, let’s create a new template that describes a zoo of animals:

ZooTemplate

Defines a zoo of animals.

REQUIRED

animals Map[String:AnimalTemplate]

A collection of animals.

OPTIONAL

description String

A description of the zoo and It’s animals.

The interesting thing to note here with ZooTemplate is we have now treated AnimalTemplate as the required value type of the animals parameter. This ability to utilize templates like any other type allows for highly complex config specs, and is one of the key features of Terra’s config system.

We can now use AnimalTemplates within our new ZooTemplate and create a config able to be read and interpreted by the config loader like so:

australian_zoo.yml#
1description: A zoo of Australian animals.
2animals:
3  koala:
4    color: grey
5    legs: 4
6  kangaroo:
7    color: brown
8    legs: 2
australian_zoo.json#
 1{
 2   "description": "A zoo of Australian animals.",
 3   "animals": {
 4      "koala": {
 5         "color": "grey",
 6         "legs": 4
 7      },
 8      "kangaroo": {
 9         "color": "brown",
10         "legs": 2
11      }
12   }
13}

Config Types#

Now that we have covered what templates are, what they do, and how we write configs according to them, we have run into an issue: how does Terra know which template the config should use in the first place? For top level objects, there isn’t a template It’s contained in to specify the type, so what tells Terra to use ZooTemplate instead of any other template?

Choosing Templates#

This is where the type parameter comes in handy. The type parameter is a standardized way of specifying the template a Map will follow, and is available for use in places where multiple templates may be applicable.

Registries#

In order to tell Terra we want our config to use ZooTemplate, we must set the type to something called a registry key. Registry keys allow us to use things in a registry.

A registry can be thought of as an internal Map that Terra uses to store similar things. Because of this, we can also think of registry keys as working the same way as Map keys. Many registries exist in Terra and all have different purposes; registry entries can be created both by Terra (typically using addons), and by configuration files, depending on use.

In this instance, we will be working with the config registry. The config registry contains a bunch of templates like our ZooTemplate, which are registered internally by Terra. The purpose of the config registry is to allow us to specify a config’s template using the type key.

Let’s assume that ZooTemplate has been registered under the name / registry key ZOO by Terra. Now what we can do is simply set the type parameter to ZOO in our config from above, signifying to Terra that our config top level object will be of type ZooTemplate:

australian_zoo.yml#
1type: ZOO
2description: A zoo of Australian animals.
3animals:
4  koala:
5   color: grey
6   legs: 4
7  kangaroo:
8   color: brown
9   legs: 2
australian_zoo.json#
 1{
 2   "type": "ZOO",
 3   "description": "A zoo of Australian animals.",
 4   "animals": {
 5      "koala": {
 6         "color": "grey",
 7         "legs": 4
 8      },
 9      "kangaroo": {
10         "color": "brown",
11         "legs": 2
12      }
13   }
14}

And with that, we have now designed a new config type that Terra is capable of interpreting.

Registering Configs#

Great! Now we are able to easily create new zoos just by making new config files for each one, but we have run into another issue: how would each zoo be kept track of? How would we be able to reference specific zoos in other configs? Perhaps we could put all of our zoo configs inside a single Map and keep everything inside one config file, and use the key name to refer to each individual zoo - but that could get cumbersome. What if we wanted to make hundreds of zoos, how would we keep them all organized?

To solve this issue, we can make use of registries. Let’s introduce a new registry called the zoo registry. As a config developer, you won’t need to worry about creating registries, as they are provided by addons and the API, so we can assume the zoo registry is created by one of our installed addons. New registry entries inside our new zoo registry can be made by simply creating ZOO configs, and we can let Terra handle the registration of each config automatically.

To do this we will also need a way of choosing a registry key for each zoo we want to make. We want control over the registry key, so what we can do is introduce a new parameter in ZooTemplate called id. What id does is simply sets the registry key of configs when they are automatically registered by Terra, allowing us to access all of our ZOO configs from anywhere via the zoo registry.

And with this, here is what our final config looks like:

australian_zoo.yml#
 1id: AUSTRALIAN_ZOO
 2type: ZOO
 3description: A zoo of Australian animals.
 4animals:
 5  koala:
 6   color: grey
 7   legs: 4
 8  kangaroo:
 9   color: brown
10   legs: 2
australian_zoo.json#
 1{
 2   "id": "AUSTRALIAN_ZOO",
 3   "type": "ZOO",
 4   "description": "A zoo of Australian animals.",
 5   "animals": {
 6      "koala": {
 7         "color": "grey",
 8         "legs": 4
 9      },
10      "kangaroo": {
11         "color": "brown",
12         "legs": 2
13      }
14   }
15}

Now in any other config that requires a zoo, we can specify our AUSTRALIAN_ZOO and it will automatically be grabbed from the zoo registry for use.