WorldTime uses WorldTimeSerializer to persist time state to JSON. The serializer preserves exact microsecond precision for time-of-day values.

Overview

WorldTimeSerializer handles:

  • WorldTimeStateComponent — Current date, time, time scale, pause state
  • TimeOfDayComponent — Per-entity time tracking (for day/night lighting)
  • GameCalendar — Calendar structure with months, years, event days

Saving State

Basic Save with WorldTimeEcsRuntime

using MoonBark.CalendarTime.ECS;
using MoonBark.CalendarTime.ECS.Serialization;

// Get runtime from ECS world
var runtime = world.GetPlugin<WorldTimeEcsPlugin>();

// Serialize to JSON string
string saveJson = runtime.TimeProvider.SaveToJson();

// Write to file
var file = FileAccess.Open("user://worldtime_save.json", FileAccess.ModeFlags.Write);
file.StoreString(saveJson);
file.Close();

GD.Print("WorldTime saved");

Save Using EntityStore Directly

using MoonBark.CalendarTime.ECS.Serialization;

var store = world.Store;
string json = WorldTimeSerializer.Serialize(store);

var file = FileAccess.Open("user://worldtime_save.json", FileAccess.ModeFlags.Write);
file.StoreString(json);
file.Close();

Loading State

Basic Load with WorldTimeEcsRuntime

using MoonBark.CalendarTime.ECS;
using MoonBark.CalendarTime.ECS.Serialization;

var runtime = world.GetPlugin<WorldTimeEcsPlugin>();

// Read from file
var file = FileAccess.Open("user://worldtime_save.json", FileAccess.ModeFlags.Read);
string json = file.GetAsText();
file.Close();

// Restore state
runtime.TimeProvider.LoadFromJson(json);

GD.Print("WorldTime loaded");

Load Using EntityStore Directly

using MoonBark.CalendarTime.ECS.Serialization;

var file = FileAccess.Open("user://worldtime_save.json", FileAccess.ModeFlags.Read);
string json = file.GetAsText();
file.Close();

var store = world.Store;
WorldTimeSerializer.Deserialize(store, json);

Save Data Format

{
  "FormatVersion": 1,
  "WorldState": {
    "Id": "main",
    "Calendar": { ... },
    "TimeScale": { ... },
    "CurrentDateTime": { ... },
    "ElapsedGameSeconds": 12345.0,
    "WorldAge": 42,
    "IsPaused": false,
    "AccumulatedRealMicroseconds": 86400000000
  },
  "EntityTimeOfDay": [
    { "EntityId": 1, "TotalHours": 14.5, "DayLengthHours": 24.0, "DayIndex": 5 }
  ]
}

Pause and Resume

The serializer preserves pause state. When loading a paused game, time remains paused:

// Save while paused
runtime.TimeProvider.SetPaused(true);
string json = runtime.TimeProvider.SaveToJson();

// Load - still paused
runtime.TimeProvider.LoadFromJson(json);
GD.Print(runtime.TimeProvider.IsPaused);  // true

Custom Save Integration

Adding WorldTime to Game Save System

public partial class GameSaveManager : Node
{
    [Export] private WorldTimeEcsPlugin? _worldTimePlugin;

    public void SaveGame(string path)
    {
        var saveData = new Dictionary<string, Variant>
        {
            ["version"] = 1,
            ["save_time"] = Time.GetDatetimeStringFromSystem(),
            ["world_time"] = _worldTimePlugin.TimeProvider.SaveToJson(),
            // ... other game data
        };

        var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
        file.StoreString(JSON.stringify(saveData));
        file.Close();
    }

    public void LoadGame(string path)
    {
        var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
        var json = file.GetAsText();
        file.Close();

        var saveData = JSON.parse_string(json) as Dictionary<string, Variant>;

        // Restore WorldTime
        string timeJson = (string)saveData["world_time"];
        _worldTimePlugin.TimeProvider.LoadFromJson(timeJson);

        // ... load other game data
    }
}

Time Precision Guarantee

The serializer preserves exact microseconds for HoursTime:

// Save/load preserves exact microseconds
var original = new HoursTime(14, 30, 500000);  // 14:30.5
string json = WorldTimeSerializer.Serialize(store);
WorldTimeSerializer.Deserialize(store, json);

var restored = runtime.TimeProvider.GetTimeOfDay(entityId);
// restored.Microseconds == original.Microseconds — exact match

This means repeated save/load cycles don't accumulate drift.