Skip to content

Instantly share code, notes, and snippets.

@ChrisPritchard
Created October 4, 2025 05:06
Show Gist options
  • Select an option

  • Save ChrisPritchard/88e285e700ea1bedcd4a6bd73ba0cedb to your computer and use it in GitHub Desktop.

Select an option

Save ChrisPritchard/88e285e700ea1bedcd4a6bd73ba0cedb to your computer and use it in GitHub Desktop.
An example config loader node script in Godot (C#) making use of my yamlrecords project
[Tool]
public partial class ConfigLoader : Node
{
public static GameConfig Config { get; private set; }
private string config_path;
[Export(PropertyHint.File, hintString: "*.yml")]
public string ConfigPath
{
get => config_path; set
{
config_path = value;
if (!string.IsNullOrEmpty(config_path))
TryLoad(value);
}
}
private readonly List<string> errors = [];
public override void _Ready()
{
if (Engine.IsEditorHint())
return;
if (string.IsNullOrEmpty(config_path))
HardFail("the config file path must be set");
else if (!TryLoad(config_path))
HardFail("failed to load config file:\n" + string.Join('\n', errors));
}
private void HardFail(string message)
{
GD.PrintErr(message);
GetTree().Quit(1);
}
private bool TryLoad(string value)
{
errors.Clear();
try
{
using (var config_yml = FileAccess.Open(value, FileAccess.ModeFlags.Read))
Config = YamlRecords.Deserialize<GameConfig>(config_yml.GetAsText());
return CheckKeys();
}
catch (Exception ex)
{
errors.Add($"config parsing error: {ex.Message}");
GD.PrintRich($"[color=red]config parsing error: {ex.Message}[color=white]");
UpdateConfigurationWarnings();
return false;
}
}
private bool CheckKeys()
{
var unknown_cards = Config.StartingCards.Where(c => !Config.CardTypes.ContainsKey(c)).Distinct().ToArray();
if (unknown_cards.Length > 0)
errors.Add("the following starting cards do not exist in card types: " + string.Join(", ", unknown_cards));
var unknown_flows = Config.StartingFlows.Where(c => !Config.GameFlows.ContainsKey(c)).Distinct().ToArray();
if (unknown_flows.Length > 0)
errors.Add("the following starting flows do not exist in game flows: " + string.Join(", ", unknown_flows));
foreach (var flow in Config.GameFlows)
{
var flow_key = flow.Key;
var flow_body = flow.Value;
if (flow_body.States.Count == 0)
{
errors.Add($"flow {flow_key} has no states defined");
continue;
}
if (string.IsNullOrEmpty(flow_body.StartState) && flow_body.States.Count > 1)
errors.Add($"flow {flow_key} does not have a start state set, but has multiple states");
foreach (var state in flow_body.States)
{
var state_key = state.Key;
var state_body = state.Value;
if (state_body.Variants.Count == 0)
{
errors.Add($"state {state_key} in flow {flow_key} has no variants defined");
continue;
}
if (string.IsNullOrEmpty(state_body.DefaultVariant) && state_body.Variants.Count > 1)
errors.Add($"state {state_key} in flow {flow_key} does not have a default variant set, but has multiple variants");
// todo, validate all actions
}
}
if (errors.Count == 0)
return true;
foreach (var error in errors)
GD.PrintRich($"[color=red]config parsing error: {error}[color=white]");
UpdateConfigurationWarnings();
return false;
}
public override string[] _GetConfigurationWarnings() => string.IsNullOrEmpty(config_path) ? ["the config file path must be set"] : [.. errors];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment