Save and Sync

From Space Engineers Wiki
Jump to navigation Jump to search

In mod scripts there comes a time where you need to save and synchronize data, this combo is most often needed for terminal controls.

Ways to save and/or sync

CustomData

This can only be used on terminal blocks and it covers both save and sync. However it is player-facing and you pretty much have to use MyIni for compatibility with PB scripts and other mod scripts.

Usage is similar to PB so the linked guide above would cover most of it.


MyModStorageComponent

This is per-entity meaning it covers fat blocks (not deformable armor) and other entities like grids, characters, voxelmaps, etc.

It is only for saving but it is also sent with the entity when it's streamed to players which helps simplify the synchronization later on (if necessary).

Guid & sbc

First you need to generate a GUID to act as a unique key for your settings. Generally only one is needed per mod but you can have more if you wish (for example if block settings contain some very big amounts of data like a ship blueprint, you can separate them into two, settings and blueprint guids).

You can ask Visual Studio to generate a GUID for you or use an online tool like guidgenerator.com.

Next you need to claim said GUID as your mod's by creating a sbc file with:

<Definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <EntityComponents>
    <EntityComponent xsi:type="MyObjectBuilder_ModStorageComponentDefinition">
      <Id>
        <TypeId>ModStorageComponent</TypeId>
        <SubtypeId>YourModNameHere</SubtypeId>
      </Id>
      <RegisteredStorageGuids>
        <guid>your-guid-string-here</guid>
      </RegisteredStorageGuids>
    </EntityComponent>
  </EntityComponents>
</Definitions>

The <RegisteredStorageGuids> can have multiple <guid> elements in it if desired.

Storage property

All entities have a Storage property, it defaults to null therefore your first step before writing is to:

if(Entity.Storage == null)
    Entity.Storage = new MyModStorageComponent();

Then to write some data to it:

public static Guid SettingsKey = new Guid("your-guid-string-here");

//...

Entity.Storage[SettingsKey] = "stuff to save";

And that's it, once you save the world this string will be attached to the entity.

The saved data will be passed along to copies, blueprints and as mentioned before: to other players when they receive this entity from server.

This component is on the entity itself so that means all mods have access to the same instance, try not to break other mods.

Data format

You can use whatever you wish as long as it can be turned into a string and back.
Since the key is tied to your mod you don't need to worry about other mods writing there (unless they intentionally copy your GUID and do it).

One way is to use a Protobuf class serialized to binary (which can also be sent over the network to synchronize) and then that byte[] array can be turned into base64 string (see #Gravity Collector below for details).

Can also use MyIni for it as well.


MyObjectBuilder_ModCustomComponent

This is only for saving for entity components, similar to the #MyModStorageComponent but a different approach.

Because mods can't quite add new types (because scripts will load after the world is deserialized), there's an intermediary serializable objectbuilder called MyObjectBuilder_ModCustomComponent which can be used in the Serialize() & Deserialize() methods in components.

Example

Not tested.

[MyComponentType(typeof(ExampleSerializableGamelogic))] // unsure if required
[MyEntityComponentDescriptor(typeof(MyObjectBuilder_Thrust), false)] // this is regular gamelogic stuff, look up usage elsewhere
public class ExampleSerializableGamelogic : MyGameLogicComponent
{
    public override bool IsSerialized()
    {
        return true; // required for Serialize&Deserialize to be called
    }

    public override MyObjectBuilder_ComponentBase Serialize(bool copy = false)
    {
        return new MyObjectBuilder_ModCustomComponent
        {
            CustomModData = "your saved data",

            // unsure how these affect anything
            ComponentType = nameof(ExampleSerializableGamelogic),
            SubtypeName = nameof(ExampleSerializableGamelogic),

            // setting this true will remove all other components of this type, probably not going to go well with multiple mods using this method with this true
            RemoveExistingComponentOnNewInsert = false,
        };
    }

    public override void Deserialize(MyObjectBuilder_ComponentBase ob)
    {
        var custom = ob as MyObjectBuilder_ModCustomComponent;
        if(custom != null)
        {
            // read custom.CustomModData
        }
    }
}


MySync

This is for synchronizing some data on an entity, requires a component to link properly to the entity's sync layer.

It can only use blittable types but not all of them, for example byte[] does not work. Also bool is not blittable but does work.

Confirmed working types are: bool, int, float, double, enum, Color, Vector3, MyFixedPoint.

NOTE: Using gamelogic means it will add to the existing "sync properties" cap of 32 shared with blocks, some are very close to the limit such as rotors having around 30.

A separate component would have its own sync property limit but it is unclear how we're supposed to create&use those.

Usage

First your component needs to implement IMyEventProxy.

Next, declare unassigned fields of MySync<T, SyncDirection>, like:

MySync<float, SyncDirection.BothWays> SyncFloat;
MySync<Color, SyncDirection.BothWays> ExampleColor;
MySync<Base6Directions.Direction, SyncDirection.FromServer> DirOrWhatever;

The game will assign them using reflection.

The SyncDirection.BothWays allows clients to set the value and it be synchronized to server and from there to everyone else, otherwise SyncDirection.FromServer will not accept sync from clients on this property, only server can set it.

Wherever you load the values from their stored location, you can use SyncFloat.SetLocalValue() to set it only locally.

Then reading and writing are done entirely with SyncFloat.Value - writing to that will automatically synchronize it.
Keep in mind that this does not do a whole lot of validation for the value, you should ensure it's within the expected parameters.

There's also SyncFloat.ValueChanged event if you need it.


Packet sending

This is for synchronizing any sort of data you wish, regardless if it's tied to a block/entity or not.

The one case it can't be used is if you have a server config and want clients to get it ASAP (in LoadData()), for that case use this instead: Example_ServerConfig_Basic.cs

A set of helper classes have been made to aid with the process: Example_NetworkProtobuf - instructions and everything are inside the files.


Examples

Gravity Collector

Uses MyModStorageComponent and Packet sending, the gist of where to look: