Code/Storage/SaveStateTracker.cs
using System;
using System.Text.Json;
using System.Threading.Tasks;
namespace Sandbox;
/// <summary>
/// Tracks the save state of endpoint calls for HUD feedback.
/// Wraps NetworkStorage.CallEndpoint with automatic state management and logging.
///
/// Usage:
/// var tracker = new SaveStateTracker();
/// var result = await tracker.Call( "mine-ore", new { ore_id = "moon_ore", kg = 5 } );
/// if ( result.HasValue ) Apply( result.Value );
///
/// Properties:
/// tracker.State → Idle, Saving, Saved, Error
/// tracker.IsBusy → true while any call is in progress
/// tracker.LastError → error message from last failure
/// </summary>
public class SaveStateTracker
{
public enum SaveState { Idle, Saving, Saved, Error }
public SaveState State { get; private set; } = SaveState.Idle;
public TimeSince TimeSinceStateChange { get; private set; }
public int PendingCalls { get; private set; }
public string LastError { get; private set; }
public bool IsBusy => PendingCalls > 0;
/// <summary>
/// Call an endpoint with automatic save state tracking and logging.
/// Sets State to Saving → Saved/Error. Logs to NetLog.
/// </summary>
public async Task<JsonElement?> Call( string slug, object input = null )
{
PendingCalls++;
SetState( SaveState.Saving );
if ( NetworkStorageLogConfig.LogRequests )
NetLog.Info( slug, $"Calling endpoint... input={( input != null ? JsonSerializer.Serialize( input ) : "null" )}" );
var result = await NetworkStorage.CallEndpoint( slug, input );
PendingCalls--;
if ( result.HasValue )
{
if ( PendingCalls <= 0 )
SetState( SaveState.Saved );
if ( NetworkStorageLogConfig.LogResponses )
NetLog.Info( slug, "Success" );
}
else
{
var error = $"{slug} failed — server returned null";
SetState( SaveState.Error, error );
if ( NetworkStorageLogConfig.LogErrors )
NetLog.Error( slug, error );
}
return result;
}
/// <summary>
/// Call an endpoint, apply the result to a callback on success, revert on failure.
/// Handles the optimistic update → server confirm → revert pattern.
/// </summary>
public async Task<bool> CallAndApply( string slug, object input, Action applyOptimistic, Action<JsonElement> applyServer, Action revert )
{
applyOptimistic?.Invoke();
var result = await Call( slug, input );
if ( result.HasValue )
{
applyServer?.Invoke( result.Value );
return true;
}
revert?.Invoke();
return false;
}
public void Reset()
{
SetState( SaveState.Idle );
PendingCalls = 0;
LastError = null;
}
private void SetState( SaveState state, string error = null )
{
State = state;
TimeSinceStateChange = 0;
if ( error != null ) LastError = error;
}
}