Skip to content

State Managing (Save and Load)

Vadim Gromov edited this page Jan 27, 2021 · 18 revisions

State loading mechanism was implemented as follows:
In general, every class that needs to save and load something, must implement two methods from the IAcceptsStateChange interface: GetState and RestoreState. On GetState it stores everything it needs to a snippet class. Snippet has a number of properties that will contain the data that is being stored. If the class has not only simple properties/fields that need to be saved, but also has child objects (like controllers, or monitors, or treatments, etc.), these subobjects should also implement IAcceptsStateChange, and their states should be stored inside the snippet in the ChildStates collection. Same way, when restoring state, local fields and simple properties are restored from the properties of a snippet, and states of subobjects are restored via the RestoreState call on them passing an object from the ChildStates collection.
Here is an example of a simple state implementation with 4 simple properties and one subobject:

#region State Manage

public IStateSnippet GetState()
{
    var state = new InventoryHealthEffectsSnippet
    {
        PlayerRunSpeedBonus = this.PlayerRunSpeedBonus,
        PlayerWalkSpeedBonus = this.PlayerWalkSpeedBonus,
        PlayerCrouchSpeedBonus = this.PlayerCrouchSpeedBonus,
        IsFreezed = this.IsFreezed
    };

    state.ChildStates.Add("FreezedByInventoryOverloadEvent", _freezedByInventoryOverloadEvent.GetState());

    return state;
}

public void RestoreState(IStateSnippet savedState)
{
    var state = (InventoryHealthEffectsSnippet)savedState;

    PlayerRunSpeedBonus = state.PlayerRunSpeedBonus;
    PlayerWalkSpeedBonus = state.PlayerWalkSpeedBonus;
    PlayerCrouchSpeedBonus = state.PlayerCrouchSpeedBonus;
    IsFreezed = state.IsFreezed;

    _freezedByInventoryOverloadEvent.RestoreState(state.ChildStates["FreezedByInventoryOverloadEvent"]);
}

#endregion 

Some snippets does not have any properties, but have a number of child states, some have properties, but does not have any child states, and some have both. Snippets are the layer that "talks" to the IAcceptsStateChange-based class itself. But what is being saved to a final state object are contracts. These are very simple entities that contain only basic data types (ints, floats, strings, arrays) and other contracts. Snippet has ToContract and FromContract methods that put their properties into the contract at the same time simplifying them, and read them back respectively. Every subobject state from the ChildStates collection is also must be put into the contract object as a separate property (field). FromContract does everything ToContract does -- but is reverse.
Contracts operate with simpliest types and arrays, and snippets -- with more friendly types and Lists. Snippet example (it has data properties as well as one subobject):

public class InventoryHealthEffectsSnippet : SnippetBase
{

    public InventoryHealthEffectsSnippet() : base() { }
    public InventoryHealthEffectsSnippet(object contract) : base(contract) { }

    #region Data Fields

    public float PlayerWalkSpeedBonus { get; set; }
    public float PlayerRunSpeedBonus { get; set; }
    public float PlayerCrouchSpeedBonus { get; set; }
    public bool IsFreezed { get; set; }

    #endregion 

    public override object ToContract()
    {
        var c = new InventoryHealthEffectsContract
        {
          PlayerWalkSpeedBonus = this.PlayerWalkSpeedBonus,
          PlayerRunSpeedBonus = this.PlayerRunSpeedBonus,  
          PlayerCrouchSpeedBonus = this.PlayerCrouchSpeedBonus,
          IsFreezed = this.IsFreezed
        };

        c.FreezedByInventoryOverloadEvent = (FixedEventContract)ChildStates["FreezedByInventoryOverloadEvent"].ToContract();

        return c;
    }

    public override void FromContract(object o)
    {
        var c = (InventoryHealthEffectsContract)o;

        PlayerWalkSpeedBonus = c.PlayerWalkSpeedBonus;
        PlayerRunSpeedBonus = c.PlayerRunSpeedBonus;
        PlayerCrouchSpeedBonus = c.PlayerCrouchSpeedBonus;
        IsFreezed = c.IsFreezed;

        ChildStates.Clear();

        ChildStates.Add("FreezedByInventoryOverloadEvent", new FixedEventSnippet(c.FreezedByInventoryOverloadEvent));
    }
}

Using this approach, we can save any complex class into the contract that contains simple fields and child contracts inside. You can imagine this like a tree that goes from couple of objects on top and bursting into branches as it goes deeper.

Contracts layer allows to set up and change how the overall state is being serialized, without altering the save-load logic at all. Saving logic that lives inside every IAcceptsStateChange-based class is totally separated from the serialization and deserialization logic.

If you are using serialization that require special attributes to be applied (like XML serialization), change contract objects to your liking in this folder: /Zara/Essentials/StateManaging/Contracts
As long as you do not remove or rename any of the existing fields, nothing will break. All contract data is presented by fields, but you can change them to properties for example if you need so, and add some attributes to them.