Files
AppleHillsProduction/docs/SaveableStateMachine_AutoGeneration.md
2025-11-03 01:34:34 +01:00

7.4 KiB

SaveableStateMachine - Auto-Generated Save IDs Implementation

Date: November 3, 2025
Status: COMPLETE - Auto-generation pattern implemented


Implementation Complete

What Changed

Before:

  • Required manual Save ID entry in inspector
  • Silent failure if Save ID was empty
  • Users had to remember to set unique IDs

After:

  • Auto-generates Save IDs from scene name + hierarchy path
  • Optional custom Save ID for manual override
  • Same pattern as Pickup.cs and SaveableInteractable
  • Zero configuration required - works out of the box

🎯 How It Works

Auto-Generation Pattern

public string GetSaveId()
{
    string sceneName = GetSceneName();
    
    if (!string.IsNullOrEmpty(customSaveId))
    {
        // User provided custom ID
        return $"{sceneName}/{customSaveId}";
    }

    // Auto-generate from hierarchy path
    string hierarchyPath = GetHierarchyPath();
    return $"{sceneName}/StateMachine_{hierarchyPath}";
}

Example Auto-Generated IDs:

  • MainScene/StateMachine_LawnMower
  • MainScene/StateMachine_Gardener/StateMachine
  • QuarryScene/StateMachine_Enemies/Boss/StateMachine

Example Custom IDs:

  • User sets customSaveId = "GardenerAI"MainScene/GardenerAI
  • User sets customSaveId = "PlayerController"MainScene/PlayerController

📋 Field Changes

Old Implementation

[SerializeField]
[Tooltip("Unique identifier for the save system")]
private string saveId = "";

New Implementation

[SerializeField]
[Tooltip("Optional custom save ID. If empty, will auto-generate from scene name and hierarchy path.")]
private string customSaveId = "";

🔧 Migration Tool Updates

Auto-Sets Custom Save ID During Migration:

// Migration tool sets customSaveId to GameObject name
var customSaveIdProperty = newSO.FindProperty("customSaveId");
if (customSaveIdProperty != null)
{
    customSaveIdProperty.stringValue = gameObject.name;
    Debug.Log($"[Migration] Set custom Save ID: '{gameObject.name}'");
}

Result:

  • Migrated SaveableStateMachines get readable custom IDs
  • Example: GameObject "GardenerStateMachine" → customSaveId = "GardenerStateMachine"
  • Final Save ID: SceneName/GardenerStateMachine

Benefits

For Users

  • Zero configuration - just add SaveableStateMachine component
  • No more errors - auto-generation ensures valid Save ID
  • No duplicate management - hierarchy path ensures uniqueness
  • Optional customization - can override with custom ID if needed

For Developers

  • Consistent pattern - matches SaveableInteractable implementation
  • Scene-scoped IDs - includes scene name to prevent cross-scene conflicts
  • Hierarchy-based - automatically unique within scene
  • Debug-friendly - readable IDs in logs and save files

📖 Usage Examples

Example 1: Default Auto-Generation

GameObject: LawnMowerController
Scene: Quarry
Custom Save ID: (empty)

Generated Save ID: Quarry/StateMachine_LawnMowerController

Example 2: Nested GameObject Auto-Generation

GameObject: StateMachine (under Enemies/Boss)
Scene: MainLevel
Custom Save ID: (empty)

Generated Save ID: MainLevel/StateMachine_Enemies/Boss/StateMachine

Example 3: Custom Save ID

GameObject: GardenerBehavior
Scene: Quarry
Custom Save ID: "GardenerAI"

Generated Save ID: Quarry/GardenerAI

Example 4: After Migration

GameObject: OldStateMachine
Scene: TestScene
Custom Save ID: "OldStateMachine" (set by migration tool)

Generated Save ID: TestScene/OldStateMachine


🔍 Comparison with SaveableInteractable

Both now use the exact same pattern:

Feature SaveableInteractable SaveableStateMachine
Auto-generation Scene + Hierarchy Scene + Hierarchy
Custom ID field customSaveId customSaveId
Prefix (none) StateMachine_
Scene scoping Yes Yes
Required config None None

Only difference: SaveableStateMachine adds "StateMachine_" prefix to auto-generated IDs to make them more identifiable.


🎓 When to Use Custom Save ID

Use Auto-Generation When:

  • GameObject has unique name in scene
  • GameObject hierarchy is stable
  • You don't need specific ID format

Use Custom Save ID When:

  • GameObject name might change
  • Multiple instances need different IDs
  • You want specific naming convention
  • You need IDs to match across scenes
  • You're doing manual save data management

🧪 Testing & Validation

Validation on Start()

  • No validation needed - auto-generation ensures valid ID
  • Always registers with SaveLoadManager
  • Never fails silently

Validation in Editor (OnValidate)

  • Logs auto-generated ID if verbose mode enabled
  • Helps debug Save ID issues
  • Shows what ID will be used

Context Menu Tools

  • "Log Save ID" - Shows current Save ID in console
  • "Test Serialize" - Shows serialized state data
  • Both work in editor and play mode

📝 Code Quality Improvements

Before Fix

private void Start()
{ 
    if (!string.IsNullOrEmpty(saveId))  // ❌ Silent failure!
    {
        RegisterWithSaveSystem();
    }
}

public string GetSaveId()
{
    return saveId;  // ❌ Could be empty!
}

After Fix

private void Start()
{ 
    // ✅ Always registers - ID auto-generated
    RegisterWithSaveSystem();
}

public string GetSaveId()
{
    string sceneName = GetSceneName();
    
    if (!string.IsNullOrEmpty(customSaveId))
    {
        return $"{sceneName}/{customSaveId}";
    }
    
    // ✅ Always returns valid ID
    string hierarchyPath = GetHierarchyPath();
    return $"{sceneName}/StateMachine_{hierarchyPath}";
}

Verification Checklist

For Auto-Generation:

  • GetSaveId() never returns empty string
  • Scene name included for cross-scene uniqueness
  • Hierarchy path ensures uniqueness within scene
  • No manual configuration required
  • Works with nested GameObjects

For Custom IDs:

  • customSaveId field is optional
  • Scene name still prepended to custom ID
  • Migration tool sets custom ID to GameObject name
  • Users can modify custom ID in inspector

For Save/Load System:

  • Always registers with SaveLoadManager
  • No silent failures
  • SerializeState() works correctly
  • RestoreState() works correctly
  • Unregisters on destroy

🎉 Summary

Problem Solved: SaveableStateMachine required manual Save ID configuration and failed silently if empty.

Solution Implemented: Auto-generate Save IDs from scene name + hierarchy path, with optional custom override.

Pattern Used: Matches SaveableInteractable and Pickup.cs - proven, consistent, user-friendly.

Result:

  • Zero configuration required
  • No silent failures
  • Always generates unique IDs
  • Optional customization available
  • Migration tool sets sensible defaults

Status: Complete, tested, zero compilation errors


Files Modified:

  • SaveableStateMachine.cs - Implemented auto-generation
  • StateMachineMigrationTool.cs - Updated to set custom IDs

Documentation:

  • This file
  • state_machine_save_load_FINAL_SUMMARY.md (should be updated)
  • SaveableStateMachine_Review.md (should be updated)