feat: Support for Escoria and Game migrations (#473)
Co-authored-by: Dennis Ploeger <develop@dieploegers.de>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/game/core-scripts/esc/_test/test_esc_compiler.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/escoria-core/_test/test_esc_compiler.gd" type="Script" id=1]
|
||||
|
||||
[node name="Testsuite" type="Control"]
|
||||
anchor_right = 1.0
|
||||
20
addons/escoria-core/_test/test_migrations.gd
Normal file
20
addons/escoria-core/_test/test_migrations.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
extends Control
|
||||
|
||||
|
||||
|
||||
func _on_CheckESCMigrationManager_pressed() -> bool:
|
||||
var savegame: ESCSaveGame = ESCSaveGame.new()
|
||||
|
||||
savegame.globals["test"] = "testa"
|
||||
|
||||
var migration_manager: ESCMigrationManager = ESCMigrationManager.new()
|
||||
savegame = migration_manager.migrate(
|
||||
savegame,
|
||||
"1.0.0",
|
||||
"2.0.0",
|
||||
"res://addons/escoria-core/_test/testversions"
|
||||
)
|
||||
|
||||
assert(savegame.globals["test"] == "testc")
|
||||
|
||||
return true
|
||||
25
addons/escoria-core/_test/test_migrations.tscn
Normal file
25
addons/escoria-core/_test/test_migrations.tscn
Normal file
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/escoria-core/_test/test_migrations.gd" type="Script" id=1]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="CheckESCMigrationManager" type="CheckButton" parent="VBoxContainer"]
|
||||
margin_right = 1280.0
|
||||
margin_bottom = 40.0
|
||||
text = "Check ESCMigrationManager"
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/CheckESCMigrationManager" to="." method="_on_CheckESCMigrationManager_pressed"]
|
||||
5
addons/escoria-core/_test/testversions/1.0.1.gd
Normal file
5
addons/escoria-core/_test/testversions/1.0.1.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends ESCMigration
|
||||
|
||||
func migrate():
|
||||
self._savegame.globals["test"] = "testb"
|
||||
|
||||
5
addons/escoria-core/_test/testversions/1.1.0.gd
Normal file
5
addons/escoria-core/_test/testversions/1.1.0.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends ESCMigration
|
||||
|
||||
func migrate():
|
||||
self._savegame.globals["test"] = "testc"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Base class for all migration version scripts. Extending scripts should be
|
||||
# named like the version they migrate the savegame to. (e.g. 1.0.0.gd, 1.0.1.gd)
|
||||
extends Object
|
||||
class_name ESCMigration
|
||||
|
||||
|
||||
var _savegame: ESCSaveGame
|
||||
|
||||
|
||||
# Set the savegame
|
||||
#
|
||||
# #### Parameters
|
||||
# - savegame: Savegame to modify
|
||||
func set_savegame(savegame: ESCSaveGame):
|
||||
_savegame = savegame
|
||||
|
||||
|
||||
# Get the savegame
|
||||
# **Returns** Savegame
|
||||
func get_savegame():
|
||||
return _savegame
|
||||
|
||||
|
||||
# Override this function in the version script with
|
||||
# the things that need to be applied to the savegame
|
||||
func migrate():
|
||||
pass
|
||||
@@ -0,0 +1,172 @@
|
||||
# Class that handles migrations between different game or escoria versions
|
||||
extends Object
|
||||
class_name ESCMigrationManager
|
||||
|
||||
|
||||
# Regular expression that matches a simple semver version string
|
||||
const VERSION_REGEX = "^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)$"
|
||||
|
||||
|
||||
# Compiled regex
|
||||
var version_regex: RegEx
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
version_regex = RegEx.new()
|
||||
version_regex.compile(VERSION_REGEX)
|
||||
|
||||
|
||||
# Migrates the specified savegame from a specified version to another version
|
||||
# based on a directory of migration scripts.
|
||||
#
|
||||
# The migration manager searches for scripts from after the given version up
|
||||
# to the target version in this directory, loads them and applies the version.
|
||||
#
|
||||
# Each migration will return a modified version of the given savegame
|
||||
func migrate(
|
||||
savegame: ESCSaveGame,
|
||||
from: String,
|
||||
to: String,
|
||||
versions_directory: String
|
||||
) -> ESCSaveGame:
|
||||
escoria.logger.info("Migrating from version %s to %s" % [
|
||||
from,
|
||||
to
|
||||
])
|
||||
|
||||
var from_info = version_regex.search(from)
|
||||
var to_info = version_regex.search(to)
|
||||
|
||||
var wrong_version: bool = false
|
||||
if from_info.get_string("major") > to_info.get_string("major"):
|
||||
wrong_version = true
|
||||
elif from_info.get_string("major") == to_info.get_string("major") and\
|
||||
from_info.get_string("minor") > to_info.get_string("minor"):
|
||||
wrong_version = true
|
||||
elif from_info.get_string("major") == to_info.get_string("major") and\
|
||||
from_info.get_string("minor") == to_info.get_string("minor") and\
|
||||
from_info.get_string("patch") > to_info.get_string("patch"):
|
||||
wrong_version = true
|
||||
|
||||
if wrong_version:
|
||||
escoria.logger.report_errors(
|
||||
"esc_migration_manager:migrate",
|
||||
[
|
||||
"Can not migrate savegame from version %s to version %s" % [
|
||||
from,
|
||||
to
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
var versions = _find_versions(versions_directory, from, to)
|
||||
versions.sort_custom(self, "_compare_version")
|
||||
if versions[0].get_file().get_basename() == from:
|
||||
versions.pop_front()
|
||||
|
||||
for version in versions:
|
||||
var migration_script = load(version).new()
|
||||
if not migration_script is ESCMigration:
|
||||
escoria.logger.report_errors(
|
||||
"esc_migration_manager:migrate",
|
||||
[
|
||||
"File %s is not a valid migration script" % version
|
||||
]
|
||||
)
|
||||
escoria.logger.debug("Migrating using %s" % version)
|
||||
(migration_script as ESCMigration).set_savegame(savegame)
|
||||
(migration_script as ESCMigration).migrate()
|
||||
savegame = (migration_script as ESCMigration).get_savegame()
|
||||
|
||||
return savegame
|
||||
|
||||
|
||||
# Find all fitting version scripts between the given versions in a directory
|
||||
# and all its subdirectories
|
||||
#
|
||||
# #### Parameters
|
||||
# - directory: Directory to search in
|
||||
# - from: Start version to check
|
||||
# - to: End version to check
|
||||
# **Returns** A list of version scripts
|
||||
func _find_versions(directory: String, from: String, to: String) -> Array:
|
||||
escoria.logger.trace("Searching directory %s" % directory)
|
||||
var versions = []
|
||||
var dir = Directory.new()
|
||||
dir.open(directory)
|
||||
dir.list_dir_begin(true, true)
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
var version = file_name.get_basename()
|
||||
var regex_result = version_regex.search(version)
|
||||
if dir.current_is_dir():
|
||||
versions += _find_versions(
|
||||
directory.plus_file(file_name),
|
||||
from,
|
||||
to
|
||||
)
|
||||
elif regex_result and _version_between(version, from, to):
|
||||
escoria.logger.trace("Found fitting migration script %s" % version)
|
||||
versions.append(
|
||||
directory.plus_file(file_name)
|
||||
)
|
||||
file_name = dir.get_next()
|
||||
return versions
|
||||
|
||||
|
||||
# Check, whether the given version is >= from and <= to
|
||||
#
|
||||
# #### Parameters
|
||||
# - version: Version to check
|
||||
# - from: Start version
|
||||
# - to: End version
|
||||
# **Returns** Whether the version matches
|
||||
func _version_between(version: String, from: String, to: String) -> bool:
|
||||
var version_info = version_regex.search(version)
|
||||
var from_info = version_regex.search(from)
|
||||
var to_info = version_regex.search(to)
|
||||
|
||||
if from_info.get_string("major") < version_info.get_string("major") and \
|
||||
version_info.get_string("major") < to_info.get_string("major"):
|
||||
return true
|
||||
elif from_info.get_string("major") == version_info.get_string("major") and \
|
||||
from_info.get_string("minor") < version_info.get_string("minor"):
|
||||
return true
|
||||
elif from_info.get_string("major") == version_info.get_string("major") and \
|
||||
from_info.get_string("minor") == \
|
||||
version_info.get_string("minor") and \
|
||||
from_info.get_string("patch") < version_info.get_string("patch"):
|
||||
return true
|
||||
elif to_info.get_string("major") == version_info.get_string("major") and \
|
||||
to_info.get_string("minor") > version_info.get_string("minor"):
|
||||
return true
|
||||
elif to_info.get_string("major") == version_info.get_string("major") and \
|
||||
to_info.get_string("minor") == version_info.get_string("minor") and\
|
||||
to_info.get_string("patch") > version_info.get_string("patch"):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
# Compare to version strings
|
||||
#
|
||||
# #### Parameters
|
||||
# - version_a: First version to compare
|
||||
# - version_b: Second version to compare
|
||||
# **Returns** true when version_b should be sorted after version_a
|
||||
func _compare_version(version_a: String, version_b: String) -> bool:
|
||||
var a_info = version_regex.search(version_a.get_file().get_basename())
|
||||
var b_info = version_regex.search(version_b.get_file().get_basename())
|
||||
|
||||
if a_info.get_string("major") < b_info.get_string("major"):
|
||||
return true
|
||||
elif a_info.get_string("major") == b_info.get_string("major") and \
|
||||
a_info.get_string("minor") < b_info.get_string("minor"):
|
||||
return true
|
||||
elif a_info.get_string("major") == b_info.get_string("major") and \
|
||||
a_info.get_string("minor") == b_info.get_string("minor") and \
|
||||
a_info.get_string("patch") < b_info.get_string("patch"):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
@@ -174,7 +174,39 @@ func load_game(id: int):
|
||||
"esc_save_manager.gd:load_game()",
|
||||
["Loading savegame %s" % str(id)])
|
||||
|
||||
var save_game: Resource = ResourceLoader.load(save_file_path)
|
||||
var save_game: ESCSaveGame = ResourceLoader.load(save_file_path)
|
||||
|
||||
var plugin_config = ConfigFile.new()
|
||||
plugin_config.load("res://addons/escoria-core/plugin.cfg")
|
||||
var escoria_version = plugin_config.get_value("plugin", "version")
|
||||
|
||||
# Migrate savegame through escoria versions
|
||||
|
||||
if escoria_version != save_game.escoria_version:
|
||||
var migration_manager: ESCMigrationManager = ESCMigrationManager.new()
|
||||
save_game = migration_manager.migrate(
|
||||
save_game,
|
||||
save_game.escoria_version,
|
||||
escoria_version,
|
||||
"res://addons/escoria-core/game/core-scripts/migrations/versions"
|
||||
)
|
||||
|
||||
# Migrate savegame through game versions
|
||||
|
||||
if ProjectSettings.get_setting("escoria/main/game_version") != \
|
||||
save_game.game_version and \
|
||||
ProjectSettings.get_setting(
|
||||
"escoria/main/game_migration_path"
|
||||
) != "":
|
||||
var migration_manager: ESCMigrationManager = ESCMigrationManager.new()
|
||||
save_game = migration_manager.migrate(
|
||||
save_game,
|
||||
save_game.game_version,
|
||||
ProjectSettings.get_setting("escoria/main/game_version"),
|
||||
ProjectSettings.get_setting(
|
||||
"escoria/main/game_migration_path"
|
||||
)
|
||||
)
|
||||
|
||||
escoria.event_manager.interrupt_running_event()
|
||||
|
||||
|
||||
@@ -177,6 +177,15 @@ func set_escoria_main_settings():
|
||||
"hint": PROPERTY_HINT_DIR
|
||||
}
|
||||
)
|
||||
|
||||
escoria.register_setting(
|
||||
"escoria/main/game_migration_path",
|
||||
"",
|
||||
{
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_DIR
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Prepare the settings in the Escoria debug category
|
||||
|
||||
@@ -264,6 +264,16 @@ _global_script_classes=[ {
|
||||
"language": "GDScript",
|
||||
"path": "res://addons/escoria-core/game/core-scripts/log/esc_logger.gd"
|
||||
}, {
|
||||
"base": "Object",
|
||||
"class": "ESCMigration",
|
||||
"language": "GDScript",
|
||||
"path": "res://addons/escoria-core/game/core-scripts/migrations/esc_migration.gd"
|
||||
}, {
|
||||
"base": "Object",
|
||||
"class": "ESCMigrationManager",
|
||||
"language": "GDScript",
|
||||
"path": "res://addons/escoria-core/game/core-scripts/migrations/esc_migration_manager.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": "ESCMovable",
|
||||
"language": "GDScript",
|
||||
@@ -596,6 +606,8 @@ _global_script_class_icons={
|
||||
"ESCItem": "res://addons/escoria-core/design/esc_item.svg",
|
||||
"ESCLocation": "res://addons/escoria-core/design/esc_location.svg",
|
||||
"ESCLogger": "",
|
||||
"ESCMigration": "",
|
||||
"ESCMigrationManager": "",
|
||||
"ESCMovable": "",
|
||||
"ESCMusicPlayer": "",
|
||||
"ESCObject": "",
|
||||
@@ -702,7 +714,7 @@ sound/sfx_volume=1
|
||||
sound/speech_volume=1
|
||||
sound/master_volume=1
|
||||
main/command_directories=[ "res://addons/escoria-core/game/core-scripts/esc/commands" ]
|
||||
debug/log_level="DEBUG"
|
||||
debug/log_level="TRACE"
|
||||
platform/skip_cache=false
|
||||
platform/skip_cache.mobile=true
|
||||
ui/items_autoregister_path="res://game/items/inventory"
|
||||
@@ -732,6 +744,7 @@ debug/crash_message="We're sorry, but the game crashed. Please send us the follo
|
||||
%s"
|
||||
ui/default_dialog_scene="res://addons/escoria-core/ui_library/dialogs/floating_dialog_player.tscn"
|
||||
esc/command_paths=[ "res://addons/escoria-core/game/core-scripts/esc/commands" ]
|
||||
main/game_migration_path=""
|
||||
|
||||
[input]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user