RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
save_manager.gd
Go to the documentation of this file.
5
6var save_path := ""
7var base_save_path := ""
8var current_profile := ""
9var migrations := {}
10var pending_state:Dictionary = {}
11var pending_save:bool = false
12var pending_save_timer:float = 0.0
13
14
15func _init(path:String):
16 save_path = path
17 base_save_path = path
18
19
20
21func set_path(path:String) -> void:
22 save_path = path
23 base_save_path = path
24
25
26
27func use_profile(profile_id:String) -> void:
28 current_profile = profile_id
29 save_path = get_profile_path(profile_id)
30
31
32
33func get_profile_path(profile_id:String) -> String:
34 if profile_id == "":
35 return base_save_path
36 var extension_index = base_save_path.rfind(".")
37 if extension_index < 0:
38 return "%s_%s" % [base_save_path, profile_id]
39 return "%s_%s%s" % [
40 base_save_path.substr(0, extension_index),
41 profile_id,
42 base_save_path.substr(extension_index)
43 ]
44
45
46func load_state() -> Dictionary:
47 var save_file = ConfigFile.new()
48 var error = save_file.load(save_path)
49 if error != OK:
50 return {}
51
52 var state = {}
53 for section in save_file.get_sections():
54 state[section] = {}
55 for key in save_file.get_section_keys(section):
56 state[section][key] = save_file.get_value(section, key)
57 return migrate_state(state)
58
59
60func save_state(state:Dictionary) -> void:
61 pending_save = false
62 pending_save_timer = 0.0
63 pending_state.clear()
64 var save_file = ConfigFile.new()
65 if not state.has("meta"):
66 state["meta"] = {}
67 state["meta"]["save_version"] = int(state["meta"].get("save_version", get_latest_migration_version()))
68 if current_profile != "":
69 state["meta"]["profile"] = current_profile
70 for section in state.keys():
71 var section_values = state[section]
72 if not section_values is Dictionary:
73 continue
74 for key in section_values.keys():
75 save_file.set_value(str(section), str(key), section_values[key])
76 save_file.save(save_path)
77
78
79
80func queue_save_state(state:Dictionary, delay:float = 2.0) -> void:
81 pending_state = state.duplicate(true)
82 pending_save = true
83 pending_save_timer = max(0.0, delay)
84
85
86
87func step_pending_save(delta:float) -> bool:
88 if not pending_save:
89 return false
90 pending_save_timer = max(0.0, pending_save_timer - delta)
91 if pending_save_timer > 0.0:
92 return false
93 flush_pending_save()
94 return true
95
96
97
98func flush_pending_save() -> bool:
99 if not pending_save:
100 return false
101 save_state(pending_state.duplicate(true))
102 return true
103
104
105
106func has_pending_save() -> bool:
107 return pending_save
108
109
110
111func save_section(section:String, values:Dictionary) -> void:
112 var state = load_state()
113 state[section] = values
114 save_state(state)
115
116
117
118func load_section(section:String, default_value:Dictionary = {}) -> Dictionary:
119 return load_state().get(section, default_value)
120
121
122
123func collect_manager_state(managers:Dictionary) -> Dictionary:
124 var state := {}
125 for section in managers.keys():
126 var manager = managers[section]
127 if manager != null and manager.has_method("get_state"):
128 state[str(section)] = manager.get_state()
129 return state
130
131
132
133func apply_manager_state(managers:Dictionary, state:Dictionary) -> void:
134 for section in managers.keys():
135 var manager = managers[section]
136 if manager != null and manager.has_method("apply_state"):
137 manager.apply_state(state.get(str(section), {}))
138
139
140
141func delete_save() -> void:
142 if FileAccess.file_exists(save_path):
143 DirAccess.remove_absolute(ProjectSettings.globalize_path(save_path))
144
145
146
147func has_save() -> bool:
148 return FileAccess.file_exists(save_path)
149
150
151
153func register_migration(version:int, migration:Callable) -> void:
154 migrations[version] = migration
155
156
157
158func get_latest_migration_version() -> int:
159 var latest := 0
160 for version in migrations.keys():
161 latest = max(latest, int(version))
162 return latest
163
164
165
166func migrate_state(state:Dictionary) -> Dictionary:
167 if state.is_empty():
168 return state
169 var migrated = state.duplicate(true)
170 if not migrated.has("meta"):
171 migrated["meta"] = {}
172 var current_version = int(migrated["meta"].get("save_version", 0))
173 var versions = migrations.keys()
174 versions.sort()
175 for version in versions:
176 var target_version = int(version)
177 if target_version <= current_version:
178 continue
179 var migration = migrations[version]
180 if migration.is_valid():
181 migrated = migration.call(migrated)
182 migrated["meta"]["save_version"] = target_version
183 return migrated
184
185
186
187func get_profile_summary(profile_id:String) -> Dictionary:
188 var previous_path = save_path
189 var previous_profile = current_profile
190 use_profile(profile_id)
191 var state = load_state()
192 save_path = previous_path
193 current_profile = previous_profile
194 return state.get("meta", {}) if not state.is_empty() else {}
195
196
197func get_value(state:Dictionary, section:String, key:String, default_value = null):
198 if not state.has(section):
199 return default_value
200 return state[section].get(key, default_value)