Update save data format and error handling.

This commit is contained in:
EliteMasterEric 2024-10-18 17:11:09 -04:00 committed by Cameron Taylor
parent 24ad7f4a39
commit dff4135fc9
7 changed files with 122 additions and 38 deletions

View file

@ -11,7 +11,7 @@
"name": "flixel",
"type": "git",
"dir": null,
"ref": "f2b090d6c608471e730b051c8ee22b8b378964b1",
"ref": "ffa691cb2d2d81de35b900a4411e4062ac84ab58",
"url": "https://github.com/FunkinCrew/flixel"
},
{

View file

@ -1,25 +1,23 @@
package funkin.save;
import flixel.util.FlxSave;
import funkin.util.FileUtil;
import funkin.input.Controls.Device;
import funkin.play.scoring.Scoring;
import funkin.play.scoring.Scoring.ScoringRank;
import funkin.save.migrator.RawSaveData_v1_0_0;
import funkin.save.migrator.SaveDataMigrator;
import funkin.save.migrator.SaveDataMigrator;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
import funkin.ui.debug.stageeditor.StageEditorState.StageEditorTheme;
import funkin.util.FileUtil;
import funkin.util.SerializerUtil;
import thx.semver.Version;
import thx.semver.Version;
@:nullSafety
class Save
{
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.6";
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.1.0";
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=2.1.0 <2.2.0";
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
static final SAVE_PATH:String = 'FunkinCrew';
@ -965,32 +963,57 @@ class Save
FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH);
if (FlxG.save.isEmpty())
switch (FlxG.save.status)
{
trace('[SAVE] Save data is empty, checking for legacy save data...');
var legacySaveData = fetchLegacySaveData();
if (legacySaveData != null)
{
trace('[SAVE] Found legacy save data, converting...');
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
case EMPTY:
trace('[SAVE] Save data in slot ${slot} is empty, checking for legacy save data...');
var legacySaveData = fetchLegacySaveData();
if (legacySaveData != null)
{
trace('[SAVE] Found legacy save data, converting...');
var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData);
FlxG.save.mergeData(gameSave.data, true);
return gameSave;
}
else
{
trace('[SAVE] No legacy save data found.');
var gameSave = new Save();
FlxG.save.mergeData(gameSave.data, true);
return gameSave;
}
case ERROR(_):
return handleSaveDataError(slot);
case BOUND(_, _):
trace('[SAVE] Loaded existing save data in slot ${slot}.');
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
FlxG.save.mergeData(gameSave.data, true);
return gameSave;
}
else
{
trace('[SAVE] No legacy save data found.');
var gameSave = new Save();
FlxG.save.mergeData(gameSave.data, true);
return gameSave;
}
}
}
/**
* Call this when there is an error loading the save data in slot X.
*/
static function handleSaveDataError(slot:Int):Save
{
var msg = 'There was an error loading your save data in slot ${slot}.';
msg += '\nPlease report this issue to the developers.';
lime.app.Application.current.window.alert(msg, "Save Data Failure");
// Don't touch that slot anymore.
// Instead, load the next available slot.
var nextSlot = slot + 1;
if (nextSlot < 1000)
{
return loadFromSlot(nextSlot);
}
else
{
trace('[SAVE] Found existing save data.');
var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
FlxG.save.mergeData(gameSave.data, true);
return gameSave;
throw "End of save data slots. Can't load any more.";
}
}
@ -1055,7 +1078,15 @@ class Save
{
var targetSaveData = new FlxSave();
targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH);
return !targetSaveData.isEmpty();
switch (targetSaveData.status)
{
case EMPTY:
return false;
case ERROR(_):
return false;
case BOUND(_, _):
return true;
}
}
/**

View file

@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.1.0] - 2024-10-18
This version introduces changes to save data loading in order to improve compatibility with older versions.
### Changed
- `optionsStageEditor.theme` converted from an Enum to a String to fix save data compatibility issues.
- In the future, Enum values should not be used in order to prevent incompatibilities caused by introducing new types to the save data that older versions cannot parse.
- `optionsChartEditor.theme` converted from an Enum to a String to fix save data compatibility issues.
- `optionsChartEditor.chartEditorLiveInputStyle` converted from an Enum to a String to fix save data compatibility issues.
## [2.0.6] - 2024-10-11
### Added
- `optionsStageEditor` to `Save` for storing user preferences for the stage editor.

View file

@ -32,6 +32,10 @@ class SaveDataMigrator
var save:Save = new Save(saveDataWithDefaults);
return save;
}
else if (VersionUtil.validateVersion(version, "2.0.x"))
{
return migrate_v2_0_0(inputData);
}
else
{
var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.';
@ -43,6 +47,20 @@ class SaveDataMigrator
}
}
static function migrate_v2_0_0(inputData:Dynamic):Save
{
// Import the structured data.
var saveDataWithDefaults:RawSaveData = cast thx.Objects.deepCombine(Save.getDefault(), inputData);
// Reset these values to valid ones.
saveDataWithDefaults.optionsChartEditor.chartEditorLiveInputStyle = funkin.ui.debug.charting.ChartEditorLiveInputStyle.None;
saveDataWithDefaults.optionsChartEditor.theme = funkin.ui.debug.charting.ChartEditorTheme.Light;
saveDataWithDefaults.optionsStageEditor.theme = funkin.ui.debug.stageeditor.StageEditorTheme.Light;
var save:Save = new Save(saveDataWithDefaults);
return save;
}
/**
* Migrate from 1.x to the latest version.
*/

View file

@ -0,0 +1,26 @@
package funkin.save.migrator;
// Internal enums used to ensure old save data can be parsed by the default Haxe unserializer.
// In the future, only primitive types and abstract enums should be used in save data!
@:native("funkin.ui.debug.stageeditor.StageEditorTheme")
enum StageEditorTheme
{
Light;
Dark;
}
@:native("funkin.ui.debug.charting.ChartEditorTheme")
enum ChartEditorTheme
{
Light;
Dark;
}
@:native("funkin.ui.debug.charting.ChartEditorLiveInputStyle")
enum ChartEditorLiveInputStyle
{
None;
NumberKeys;
WASDKeys;
}

View file

@ -5654,7 +5654,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleHelpKeybinds():Void
{
// F1 = Open Help
if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen) {
if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen)
{
this.openUserGuideDialog();
}
}
@ -6543,22 +6544,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
/**
* Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds.
*/
enum ChartEditorLiveInputStyle
enum abstract ChartEditorLiveInputStyle(String)
{
/**
* No hotkeys to place notes at the playbar.
*/
None;
var None;
/**
* 1/2/3/4 to place notes on opponent's side, 5/6/7/8 to place notes on player's side.
*/
NumberKeys;
var NumberKeys;
/**
* WASD to place notes on opponent's side, Arrow keys to place notes on player's side.
*/
WASDKeys;
var WASDKeys;
}
typedef ChartEditorParams =
@ -6577,15 +6578,15 @@ typedef ChartEditorParams =
/**
* Available themes for the chart editor state.
*/
enum ChartEditorTheme
enum abstract ChartEditorTheme(String)
{
/**
* The default theme for the chart editor.
*/
Light;
var Light;
/**
* A theme which introduces darker colors.
*/
Dark;
var Dark;
}

View file

@ -1458,17 +1458,17 @@ class StageEditorState extends UIState
/**
* Available themes for the stage editor state.
*/
enum StageEditorTheme
enum abstract StageEditorTheme(String)
{
/**
* The default theme for the stage editor.
*/
Light;
var Light;
/**
* A theme which introduces stage colors.
*/
Dark;
var Dark;
}
enum StageEditorDialogType