Configuration Loading

This page describes how to load and use grail configuration data stored in .gcf files. If you’re using Unity or Unreal 4, 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 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.

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 UtilityReasonerConfigInterpreter class to spawn Utility Reasoners in separation 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::IConsiderationProvider
{
public:
  ConsiderationProvider(const std::map<std::string, std::function<std::shared_ptr<grail::utility::Consideration<grail::EntityBlackboardPair>>()>>& mapping)
    : factoryFunctionMapping{ mapping }
  {
  }

  virtual ~ConsiderationProvider() override = default;

  std::shared_ptr<grail::utility::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::utility::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 <grail/../libs/yaml-cpp/include/yaml-cpp/yaml.h>
#include <grail/../libs/yaml-cpp-converters/YamlConvertersConfig.h>
#include <grail/ConfigHelpers/ConfigHelpers.h>

grail::GrailConfiguration configuration = YAML::LoadFile("myFile.gcf").as<grail::GrailConfiguration>();
std::vector<grail::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<grail::BehaviorBlueprint> behaviorBlueprints;
//add utility behavior blueprints here

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

grail::UtilityReasonerConfigInterpreter 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 UtilityReasonerConfigInterpreter(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 assign 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 id’s to reasoner instances. An example implementation is shown below.

  • C++

  • C#

class DummyFactory : public grail::IReasonerFactory
{
  virtual std::unique_ptr<grail::Reasoner> CreateReasoner(const std::string& reasonerId) const override
  {
    //create an empty UtilityReasoner
    return std::unique_ptr<grail::UtilityReasoner>(new grail::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 <grail/../libs/yaml-cpp/include/yaml-cpp/yaml.h>
#include <grail/../libs/yaml-cpp-converters/YamlConvertersConfig.h>
#include <grail/ConfigHelpers/ConfigHelpers.h>
#include <grail/AIManager.hh>

grail::AIManager manager{ 0 };

grail::GrailConfiguration configuration = YAML::LoadFile("myFile.gcf").as<grail::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<grail::BehaviorBlueprint> behaviorBlueprints;
//add utility behavior blueprints here

std::shared_ptr<ConsiderationProvider> considerationProvider
{
  new ConsiderationProvider
  {
    {
      {"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::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::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::ConfigInterpreter interpreter{ configuration, considerationProvider, behaviorBlueprints , {} };

//if you don't want to use Utility Reasoner config
grail::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);