Configuration Loading

This page describes how to load and use Grail configuration data stored in .gcf files. If you’re using Unity or Unreal, most of these operations are done for you by the plugins we provide. So unless you want to do something unusual, read the proper plugin’s documentation first:

Loading config data from file

Grail’s configuration data is stored in .gcf files. Despite the extension, they are ordinary YAML files, so they can be loaded by a standard-compliant parser. Another great feature of this format is that it’s human readable. Alongside Grail, we ship YamlDotNet and yaml-cpp for C# and C++ Grail versions, respectively.

Example configuration file

Below, you can see the contents of an example configuration file.

EntityGroupModels: # definitions of entity grous
- Name: group_1
  EntityPriorityPairs: # entities contained within this entity group
  - EntityName: entity_type_1
    RelativePriority: 0
  - EntityName: entity_type_2
    RelativePriority: 0
  GroupBlackboardMapping: # connections to group shared blackboards
  - EntityId: entity_type_1
    BlackboardName: blackboard_1
  - EntityId: entity_type_2
    BlackboardName: blackboard_1
  GlobalBlackboardMapping: # connections to global shared blackboards
  - EntityId: entity_type_1
    BlackboardName: global_blackboard
EntityModels: #entity id to reasoner id mappings
- Id: entity_type_1
  ReasonerId: test_utility_system_1
- Id: entity_type_2
  ReasonerId: test_utility_system_2
EvaluatorTreeModels: # utility evaluator definitions
- TreeName: behavior_eval_1
  RootEvaluatorNode:
    EvaluatorTypeId: AGGREGATOR_MIN
    InputEvaluatorModels:
    - EvaluatorTypeId: CURVE_LINEAR
      CurveDataModel:
        CurveParameters:
        - 1
        - 0
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_0
    - EvaluatorTypeId: CURVE_LINEAR
      CurveDataModel:
        CurveParameters:
        - 1
        - 0
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_1
- TreeName: behavior_eval_2
  RootEvaluatorNode:
    EvaluatorTypeId: AGGREGATOR_MIN
    InputEvaluatorModels:
    - EvaluatorTypeId: CURVE_CONSTANT
      CurveDataModel:
        CurveParameters:
        - 0.5
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_0
- TreeName: alt_behavior_eval_1
  RootEvaluatorNode:
    EvaluatorTypeId: AGGREGATOR_MIN
    InputEvaluatorModels:
    - EvaluatorTypeId: CURVE_LINEAR
      CurveDataModel:
        CurveParameters:
        - 1
        - 0
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_0
- TreeName: alt_behavior_eval_2
  RootEvaluatorNode:
    EvaluatorTypeId: AGGREGATOR_AVERAGE
    InputEvaluatorModels:
    - EvaluatorTypeId: CURVE_POWER
      CurveDataModel:
        CurveParameters:
        - 2
        - 1
        - 0
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_0
    - EvaluatorTypeId: CURVE_POWER
      CurveDataModel:
        CurveParameters:
        - 2
        - 1
        - 0
      InputEvaluatorModels:
      - EvaluatorTypeId: CONSIDERATION
        ConsiderationModel:
          Name: consideration_1
UtilityReasoners: # utility reasoner configuration
- Name: test_utility_system_1
  SelectorModel:
    SelectionMethod: MAX
    EvaluatorAssignments: # behavior to evaluator mapping
    - BehaviorName: behavior_1
      EvaluatorName: behavior_eval_1
    - BehaviorName: behavior_2
      EvaluatorName: behavior_eval_2
- Name: test_utility_system_2
  SelectorModel:
    SelectionMethod: MAX
    EvaluatorAssignments:
    - BehaviorName: behavior_1
      EvaluatorName: alt_behavior_eval_1
    - BehaviorName: behavior_2
      EvaluatorName: alt_behavior_eval_2

Config interpreter

ConfigInterpreter class provided in Grail allows you to easily use configuration data structures to produce Entities, EntityGroups and Utility Reasoners. You can also use UtilityReasonerFactory class to spawn Utility Reasoners separately from AI Entities.

Creating a Utility Reasoner

This section shows how to create a Utility Reasoner from a configuration file.

Implementing Consideration Provider

First you have to implement a Consideration Provider that maps names provided in the configuration data to Consideration class instances. A simple consideration provider code is presented below.

  • C++

  • C#

class ConsiderationProvider : public grail::evaluator::IConsiderationProvider
{
public:
  ConsiderationProvider(const std::map<std::string, std::function<std::shared_ptr<grail::evaluator::Consideration<grail::EntityBlackboardPair>>()>>& mapping)
    : factoryFunctionMapping{ mapping }
  {
  }

  virtual ~ConsiderationProvider() override = default;

  std::shared_ptr<grail::evaluator::Consideration<grail::EntityBlackboardPair>> GetConsiderationByName(const std::string& name) const override
  {
    return factoryFunctionMapping.at(name)();
  }
private:
  std::map<std::string, std::function<std::shared_ptr<grail::evaluator::Consideration<grail::EntityBlackboardPair>>()>> factoryFunctionMapping;
};
class ConsiderationProvider : IConsiderationProvider
{
  private readonly Dictionary<string, Func<Consideration<(AIEntity, Blackboard)>>> factoryFunctionMapping =
    new Dictionary<string, Func<Consideration<(AIEntity, Blackboard)>>>();
  public ConsiderationProvider(params (string, Func<Consideration<(AIEntity, Blackboard)>>)[] factoryFunctions)
  {
    foreach (var entry in factoryFunctions)
    {
      factoryFunctionMapping.Add(entry.Item1, entry.Item2);
    }
  }

  public Consideration<(AIEntity, Blackboard)> GetConsiderationByName(string name)
  {
    return factoryFunctionMapping[name].Invoke();
  }
}

Spawning a Reasoner

To spawn a reasoner you have to provide Grail with considerations and behavior blueprints. Then you can create reasoners by passing their ids.

  • C++

  • C#

#include <libs/yaml-cpp/include/yaml-cpp/yaml.h>
#include <libs/yaml-cpp-converters/YamlConvertersConfig.h>
#include <GrailUtilityAI/ConfigHelpers/ConfigHelpers.h>

using grail::utility::helpers::BehaviorBlueprint;

grail::data::GrailConfiguration configuration = YAML::LoadFile("myFile.gcf").as<grail::data::GrailConfiguration>();
std::vector<BehaviorBlueprint> behaviorBlueprints;

//if you want to use Utility Reasoner config, you have to provide behavior brefabs
//and a class implementing IConsiderationProvider interface
//that returns properly constructed considerations for a given string id

std::vector<BehaviorBlueprint> behaviorBlueprints;
//add utility behavior blueprints here

std::shared_ptr<MyConsiderationProvider> considerationProvider
{
  new MyConsiderationProvider
  {
    {
      {"consideration_0", [] {return std::make_shared<MyConsideration_0>();}},
      {"consideration_1", [] {return std::make_shared<MyConsideration_1>();}},
    }
  }
};

grail::utility::helpers::UtilityReasonerFactory utilityInterpreter{ configuration.utilityReasonerModels, considerationProvider, behaviorBlueprints };

auto reasoner = utilityInterpreter.CreateReasoner("reasoner_id");
var serializer = new YamlSerializer();
GrailConfiguration configuration;
using (TextReader reader = new StringReader(testConfig))
{
  configuration = serializer.Deserialize<GrailConfiguration>(reader);
}

//if you want to use Utility Reasoner config, you have to provide behavior brefabs
//and a class implementing IConsiderationProvider interface
//that returns properly constructed considerations for a given string id

var behaviorBlueprints = new List<BehaviorBlueprint>();
//add utility behavior blueprints here

var considerationProvider = new ConsiderationProvider(("consideration_0", () => new DummyConsideration("consideration_0")),
  ("consideration_1", () => new DummyConsideration("consideration_1")));

var utilityInterpreter = new UtilityReasonerFactory(configuration.UtilityReasoners, considerationProvider, behaviorBlueprints);

var reasoner = utilityInterpreter.CreateReasoner("reasoner_id");

Creating a single Entity

This code example shows how to create a single AI Entity from a configuration file.

Reasoner factories

To use assigned default reasoners that are not defined in config files, you have to provide factory classes for them. Analogously to IConsiderationProvider, their role is to map string IDs to reasoner instances. An example implementation is shown below.

  • C++

  • C#

class DummyFactory : public grail::helpers::IReasonerFactory
{
  virtual std::unique_ptr<grail::Reasoner> CreateReasoner(const std::string& reasonerId) const override
  {
    //create an empty UtilityReasoner
    return std::make_unique<grail::utility::UtilityReasoner>({});
  }
};
public class DummyFactory : Grail.IReasonerFactory
{
  public Reasoner CreateReasoner(string reasonerId)
  {
    //create an empty UtilityReasoner
    return new UtilityReasoner(new Dictionary<IEvaluator<(AIEntity, Blackboard)>, BehaviorBlueprint>());
  }
}

Spawning Entities

After you’ve implemented your Reasoner Factory, it can be later used by configuration interpreter classes. A full code example is presented below.

  • C++

  • C#

#include <libs/yaml-cpp/include/yaml-cpp/yaml.h>
#include <libs/yaml-cpp-converters/YamlConvertersConfig.h>
#include <GrailUtilityAI/ConfigHelpers/ConfigHelpers.h>
#include <GrailCore/AIManager.hh>

using grail::utility::helpers::BehaviorBlueprint;

grail::AIManager manager{ 0 };

grail::data::GrailConfiguration configuration = YAML::LoadFile("myFile.gcf").as<grail::data::GrailConfiguration>();

//if you want to use Utility Reasoner config, you have to provide behavior blueprints
//and a consideration provider, just like in the previous example

std::vector<BehaviorBlueprint> behaviorBlueprints;
//add utility behavior blueprints here

std::shared_ptr<MyConsiderationProvider> considerationProvider
{
  new MyConsiderationProvider
  {
    {
      {"consideration_0", [] {return std::make_shared<MyConsideration_0>();}},
      {"consideration_1", [] {return std::make_shared<MyConsideration_1>();}},
    }
  }
};

//no reasoner factories provided. Only utility config will be used
grail::helpers::ConfigInterpreter interpreter{ configuration, considerationProvider, behaviorBlueprints , {} };

//if you don't want to use Utility Reasoner config, you don't have to pass a consideration provider
//or behaviorBlueprints. I this case, only DummyFactory will be used
grail::helpers::ConfigInterpreter interpreterNoUtilityReasoners{ configuration, {std::make_shared<DummyFactory>()}};

auto utilityReasonerEntity = interpreter.CreateAndRegisterEntity("entity_type_1", 0, manager);
auto noUtilityReasonerEntity = interpreterNoUtilityReasoners.CreateAndRegisterEntity("entity_type_2", 0, manager);
var manager = new AIManager();
var serializer = new YamlSerializer();
GrailConfiguration configuration;
using (TextReader reader = new StringReader(testConfig))
{
  configuration = serializer.Deserialize<GrailConfiguration>(reader);
}

//if you want to use Utility Reasoner config, you have to provide behavior blueprints
//and a consideration provider, just like in the previous example

var behaviorBlueprints = new List<BehaviorBlueprint>();
//add utility behavior blueprints here

var considerationProvider = new ConsiderationProvider(("consideration_0", () => new DummyConsideration("consideration_0")),
  ("consideration_1", () => new DummyConsideration("consideration_1")));

//no reasoner factories provided. Only utility config will be used
var interpreter = new ConfigInterpreter(configuration, considerationProvider, behaviorBlueprints);

//if you don't want to use Utility Reasoner config, you don't have to pass a consideration provider
//or behaviorBlueprints. I this case, only DummyFactory will be used
var interpreterNoUtilityReasoners = new ConfigInterpreter(configuration, new DummyFactory());

var createdEntity = interpreter.CreateAndRegisterEntity("entity_type_1", 0, manager);
var noUtilityReasonerEntity = interpreterNoUtilityReasoners.CreateAndRegisterEntity("entity_type_2", 0, manager);

Creating an Entity Group

This code example shows how to create and configure an Entity Group from configuration data. Boilerplate code is omitted.

  • C++

  • C#

//if you want to use Utility Reasoner config
grail::helpers::ConfigInterpreter interpreter{ configuration, considerationProvider, behaviorBlueprints , {} };

//if you don't want to use Utility Reasoner config
grail::helpers::ConfigInterpreter interpreterNoUtilityReasoners{ configuration, {std::make_shared<DummyFactory>()}};

auto entityGroup = interpreter.CreateAndRegisterEntityGroup("group_1", manager);
//after creating the group you can perform initialization logic by accesing individual entities
entityGroup->GetEntityEntries()[0].entity->GetBlackboard().SetValue<int>("int_value", 1);
auto anotherGroup = interpreterNoUtilityReasoners.CreateAndRegisterEntityGroup("group_2", manager);
//if you want to use Utility Reasoner config
var interpreter = new ConfigInterpreter(configuration, considerationProvider, behaviorBlueprints);

//if you don't want to use Utility Reasoner config
var interpreterNoUtilityReasoners = new ConfigInterpreter(configuration, new DummyFactory());

var entityGroup = interpreter.CreateAndRegisterEntityGroup("group_1", manager);
//after creating the group you can perform initialization logic by accesing individual entities
entityGroup.EntityEntries[0].entity.Blackboard.SetValue<int>("int_value", 1);
var anotherGroup = interpreterNoUtilityReasoners.CreateAndRegisterEntityGroup("group_2", manager);