# Godot Project

# Godot project architecture

## Application Scopes

<p class="callout info">In GFF, an application scope is an entry point available during a specific period of time in the application lifetime.</p>

Application is structured within two scopes:

- **App** (`res://sources/App.cs`): The **App** scope, initialized at the start of the application, released before quitting. App act like a singleton.
- **Game** (`res://sources/Game.cs`): The **Game** scope, initialized when starting or loading a game, released when going back to main menu.

Each scope contains `Services` that can be accessed from anywhere within the scope. Services are declared in the `RegisterServices` method of an application scope by filling the dependency builder with instances.

Current rendering driver is accessible in static field `ApplicationScope.RenderingDriver`. It can be checked to enable some compatibility features.

### Application Version

Current version of the application can be accessed from the static field `ApplicationScope.Version`. It is modified when using the command `gff release-version` (you can also modify it manually in the csproj file).

You may need to execute some code when moving from a version to another (migrate some settings, checking for unlock of newly created achievements, etc ...). You can do this by registering a delegate using `ApplicationScope.RegisterVersionMigrationCallback(ApplicationVersionChangedEventHandler migrationCallback)`. Registered delegate will be called only the first time after a version change.

### App Scope

The App scope is the first entry point of the Godot application. It's constructor parse command line arguments so they are accessible during the whole execution of the application (`App.Args`). They are parsed by the [CommandLine](https://github.com/commandlineparser/commandline) library.

It also have a helper to checks for the availability of a feature (method `HasFeature(string tagName)`). This method replace Godot's helper `OS.HasFeature`. In Debug mode, this method allows developers to force features (e.g., "demo", "steam") to report as available by checking specific arguments, overriding Godot feature status.

## Services

<p class="callout info">The [**service locator pattern**](https://en.wikipedia.org/wiki/Service_locator_pattern) is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task.</p>

The service locator pattern provides global access to services without the testability issues of singletons. This pattern allows swapping implementations for testing or different platforms while maintaining a simple API for service access.

The API allows to declare services like this:

```c#
// Register a service by giving its type. A new instance will automatically be created.
dependencyBuilder.AddSingleton<Application.SaveManager>()
  
// Abstract a service behind an interface. You can get the instance using the interface type. 
// This allows to change the instance depending on some conditions without having to change the code on the user side.
if (App.HasFeature("steam"))
{
  dependencyBuilder.AddSingleton<IDrmService, Application.Store.SteamManager>();
}
else
{
  dependencyBuilder.AddSingleton<IDrmService, Application.Store.NoDrmManager>();
}

// It's also possible to manually instantiate the service (useful when having specific instanciation rules).
dependencyBuilder.AddSingleton<Application.AppSettings>(new Application.AppSettings(this.GetTree().Root))

// You can also provide instances from the scene tree to make them globally accessible.
dependencyBuilder.AddSingleton<Core.UI.LoadingScreen>(this.GetChildren<LoadingScreen>().First())
```

Services are then accessible from anywhere within the scope of its service locator (the Application Scope):

```c#
AppSettings appSettings = App.Services.Get<AppSettings>();
IDrmService drmService = App.Services.Get<IDrmService>();
```

A service must implement the interface `ISingleton<TServiceLocator>` and must declare a method named `Bind` that will receive as parameters other services that it depends on. This method is called by the dependency builder using dependency injection to fill parameters.

```c#
// Declare IDrmService parameter to create a dependency to this service.
public void Bind(IDrmService drmService)
{
  // Then, use drmService to initialize the service...
}
```

There is 3 methods in `ISingleton` interface, `Initialize` and `Release` are called when the Application Scope start / exit. `Update` is called every frame. To ensure a specific order of call, you can set a dependency between two services.

## App State

The execution flow of the application is controlled by a [hierarchical state machine](https://frog-doc.collectivit.org/books/gff-documentation/page/hierarchical-state-machine "Hierarchical State Machine") (HSM). It is registered as a service in the App scope (`res://sources/application/AppState.cs`). HSM allow you to easily add new states without having to break your existing state machine. You can control general behavior using parent states, and more specific one with child states. You can add any number of hierarchy level you need.

Another HSM is used to control game execution flow (`res://sources/gameplay/GameState.cs`).

# Hierarchical State Machine

## Definition

<p class="callout info">The state pattern allows objects to change behavior based on their internal state, effectively implementing finite state machines (FSM). This pattern is essential for managing AI behavior, player character states, game flow states, and animation controllers.  
</p>

State machines prevent spaghetti code from proliferating conditional statements and ensure clear state transitions.

<p class="callout info">Complex AI and game flow often require hierarchical state machines (HSM) where states can contain sub-states.</p>

Hierarchical state machines reduce code duplication by allowing states to share common behavior through parent states. When transitioning between child states, the parent state remains active and continues executing its logic.

## Implementation

In GFF, a HSM is implemented by creating a class that derives from `HierarchicalStateMachine<TState, TTransition>`.

`TState` and `TTransition` are enum types. It is recommended to declare theses enums inside the HSM class since they are highly specific to its implementation.

The HSM structure must be declared before updating it. It is recommended to do it in the constructor, then to call `AssertCorrectness` that verify the integrity of the HSM.

You can register a new state using the method `RegisterState`, then you register transitions to other states of the state machine with `RegisterTransitionTo`. You also can register a pass (a delegate) for any trigger type with `RegisterTransitionTo`.

Here are the available types:

- Begin: the pass is called when entering the state.
- Update: the pass is called each update while being in the state (after Begin the first time it enters the state, and before End the last time).
- End: the pass is called before leaving the state.

Each parent state must have one and only one default child. An implicit transition is created between the parent and its default child. When a transition has a parent state as destination, the HSM will automatically transition to the default child. If you don't want to go to the default child, it means you need to declare a transition to a child state directly.

### Example

For example, if we want to create the following state machine:

<div drawio-diagram="236"><img src="https://frog-doc.collectivit.org/uploads/images/drawio/2026-07/drawing-4-1784722125.png" alt=""/></div>

```c#
using Core;

public partial class MyHSM : HierarchicalStateMachine<MyHSM.Id, MyHSM.Transition>
{
    public AppState() : base(Id.Bootstrap)
    {
        this.RegisterState(Id.Initial)
            .RegisterTransitionTo(Transition.Next, Id.Main);

        {
            StateInfo main =
            this.RegisterState(Id.Main)
                .RegisterPass(PassType.Begin, this.Main_Begin)
                .RegisterPass(PassType.End, this.Main_End)
                .RegisterTransitionTo(Transition.Next, Id.Final);

            outGame.RegisterState(Id.Main_SubState, defaultState: true)
                .RegisterPass(PassType.End, this.Main_SubState_Update)
                .RegisterTransitionTo(Transition.Toggle, Id.Main_AnotherSubState);
          
            outGame.RegisterState(Id.Main_AnotherSubState)
                .RegisterPass(PassType.End, this.Main_AnotherSubState_Update)
                .RegisterTransitionTo(Transition.Toggle, Id.Main_SubState);
        }

        this.RegisterState(Id.Final);

        this.AssertCorrectness();
    }

    private void Main_Begin()
    {
    }
  
    private void Main_End()
    {
    }
  
    private void Main_SubState_Update()
    {
    }
  
    private void Main_AnotherSubState_Update()
    {
    }

    public enum Id
    {
        Initial,

        Main,
        Main_SubState,
        Main_AnotherSubState,

        Final,
    }

    public enum Transition
    {
        Next,
        Toggle,
    }
}
```

You can also see `res://sources/application/AppState.cs` and `res://sources/gameplay/GameState.cs` as real life example. They are setup to handle the application and game control flow in GFF.

### Usage

Once your HSM is declared, your must call the Update method to run it once. During an update, the state machine will call the Update passes for all active states (from children to parent) then it will proceed to transition changes if there is pending one.

To ask for a transition change, you must call the method `void QueueTransition(TTransition transition)`. If the method is called from outside of the state machine (not in a pass during the update) the transition change is applied directly.

You can ask the state machine if you are in a specific state with the method `bool IsInState(TState state)`. It returns `true` for the current active state and all its parents. You can also ask if a specific transition is available in the current state with `bool HasTransitionAvailable(TTransition transition)`, it makes it really easy to disable actions that are not available in the game in the current state.

<p class="callout info">You can [serialize](https://frog-doc.collectivit.org/books/gff-documentation/page/serializer "Serializer") the HSM. It will restore its current state on load automatically.</p>

<details id="bkmrk-sources-https%3A%2F%2Fgene"><summary>Sources</summary>

https://generalistprogrammer.com/game-design-patterns#state-pattern

</details>

# Serializer

Serialization tools can be found in namespace `Core.Serialization`.

## Data Serialization

Low level tool to serialize information.

### Overview

In GFF, serialization is done with an abstract Serializer that uses a symetric declaration pattern (symetric read and write). Here is as an example how to describe the serialization of an object:

```c#
public class SerializableObject : ISerializable
{
    private int intField;
    private EnumType enumField;
    private SerializedObject child;
    
    public void Serialize(Serializer serializer)
    {
        this.intField = serializer.SerializeValue("Integer", this.intField);
        this.enumField = serializer.SerializeAsValue("Enum", this.enumField);
        this.child = serializer.Serialize("Child", this.child);
    }
}
```

<p class="callout info">*Symetric serialization*: It is important to send your value as parameter **and** to retrieve it from the return value of serialization methods. This way the system can handle Read and Write operation for reference types and value types in a safe way.</p>

You can then serialize it in any C# stream using a serializer (for now only JsonSerializer exists):

```c#
using (MemoryStream stream = new MemoryStream())
{
    var objectToSerialize = new SerializableObject();
    using (var serializer = new JsonSerializer(stream, JsonSerializer.SerializeMode.Writer))
    {
        objectToSerialize.Serialize(serializer);
    }
    
    var derializedObject = new SerializableObject();
    using (var serializer = new JsonSerializer(stream, JsonSerializer.SerializeMode.Reader))
    {
        derializedObject.Serialize(serializer);
    }
}

```

### API

This section describe how to serialize data in a `Serialize` method.

#### Object Serialization

You can serialize any object instance, array or list of instances that implement the `ISerializable` interface using the `Serialize` methods.

```c#
T Serialize<T>(string key, in T serializableObject) where T : ISerializable;
T[] Serialize<T>(string key, in T[] array) where T : ISerializable;
List<T> Serialize<T>(string key, in List<T> list) where T : ISerializable;
```

#### Value Serialization

You can serialize any value type, array or list of value types using the `SerializeValue` methods.

```c#
T SerializeValue<T>(string key, in T value);
T[] SerializeValue<T>(string key, in T[] array);
List<T> SerializeValue<T>(string key, in List<T> list);
```

#### Serialize simple objects as value

Some data structure are simple enough to be serialized as value but are not value type. This special cases are handled using the `SerializeAsValue` methods. For now these methods can serialize enum as integer or string, C# type as string or event Godot `RandomNumberGenerator`. You can also implement the `IValueConvertible` interface to create a specific conversion on types you own.

```c#
T SerializeAsValue<T, TValue>(string key, in T serializableObject) where T : IValueConvertible<T, TValue>;
Type SerializeAsValue(string key, in Type type);
RandomNumberGenerator SerializeAsValue(string key, in RandomNumberGenerator rng);
T SerializeAsValue<T>(string key, in T enumValue, bool serializeAsInteger = false) where T : struct, Enum;
Queue<T> SerializeAsValue<T>(string key, in Queue<T> enumQueue, bool serializeAsInteger = false) where T : struct, Enum;
```

#### Versioning

Handling version in serialization is mandatory if you want to handle backward compatibility in your game saves. You can write a version number to any `Serialize` method by using the `SerializeVersion` method. If no version is found the method return 0.

```c#
public void Serialize(Serializer serializer)
{
    int version = serializer.SerializeVersion(1);
    
    this.Legacyfield = serialize.Serialize("Legacyfield", this.Legacyfield);
    if (version >= 1)
    {
        this.Newfield = serialize.Serialize("NewField", this.NewField);
    }
}

```

<p class="callout info">The version number is unique per type so you can have different version number in inheritance level of a class hierarchy.</p>

## Save Manager

High level manager that handle game saves.

### Game Save System

The `SaveManager` contains a list of save slots. It allows you to create, delete or edit any save slot. A save slot is represented by a single archive file in the save folder (it is a zip file using the `sav` extension).

#### Save Info

When you retrieve a save slot you get a `SaveInfo` reference. This structure contains the metadata about the save slot content. These metadata are stored as a config file in the game slot archive. You can add any custom metadata to the save (allowing you to gather information about the save without deserializing everything to display it in menus).

Metadata are organized in sections, the Main section contains all general metadata. Then there is one section per save entry.

#### Save Entries

You can store any instance that implement `ISerializationEntry` in a save slot as an entry. It creates one file per entry in the archive (plus an associated section in the metadata).

Use the `WriteEntry` and`ReadEntry` to store and retrieve entry instances from a save slot.

### Post Load Pass

Sometimes you need to serialize references to other serialized objects and you want to resolve these references during loading to just have a C# reference to use in your code. `PostLoad` pass just serve this purpose, it calls the `PostLoad` method on all instances that implements `ISerializableWithPostLoad` interfaces. This call is done after the deserialization process so all instances exists and have valid deserialized data.

<p class="callout info">PostLoad methods are called in reverse order than the Serialize methods (from child to parent).</p>