Skip to content

Commit b8c5a7c

Browse files
committed
Save file backup implementation
1 parent aa84382 commit b8c5a7c

3 files changed

Lines changed: 183 additions & 134 deletions

File tree

Runtime/AssetFormatter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ public class AssetFormatter<T> : IMessagePackFormatter<T> where T : Object
88
{
99
public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options)
1010
{
11-
DataSerializer.Container.TryGetId(value, out var id);
11+
DataSerializer.Container.TryResolveId(value, out var id);
1212

1313
writer.WriteUInt16(id);
1414
}
1515

1616
public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
1717
{
18-
var resolved = DataSerializer.Container.TryGetObject(reader.ReadUInt16(), out var value);
18+
var resolved = DataSerializer.Container.TryResolveReference(reader.ReadUInt16(), out var value);
1919

20-
return resolved ? (T)value : null;
20+
return resolved ? value as T : null;
2121
}
2222
}
23-
}
23+
}

Runtime/AssetsContainer.cs

Lines changed: 103 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -8,109 +8,109 @@
88

99
namespace ToolBox.Serialization
1010
{
11-
public sealed class AssetsContainer : ScriptableObject
12-
{
13-
[SerializeField] private Object[] _savedAssets;
14-
[SerializeField] private string[] _paths;
15-
16-
public bool TryGetObject(ushort id, out Object entry)
17-
{
18-
entry = null;
19-
20-
if (id == 0 || id >= _savedAssets.Length)
21-
{
22-
return false;
23-
}
24-
25-
entry = _savedAssets[id];
26-
return true;
27-
}
28-
29-
public bool TryGetId(Object value, out ushort id)
30-
{
31-
id = 0;
32-
33-
for (ushort i = 1; i < _savedAssets.Length; i++)
34-
{
35-
if (_savedAssets[i] != value)
36-
{
37-
continue;
38-
}
39-
40-
id = i;
41-
return true;
42-
}
43-
44-
return false;
45-
}
11+
public sealed class AssetsContainer : ScriptableObject
12+
{
13+
[SerializeField] private Object[] _savedAssets;
14+
[SerializeField] private string[] _paths;
4615

4716
#if UNITY_EDITOR
48-
public void LoadAssets()
49-
{
50-
if (_paths == null)
51-
{
52-
return;
53-
}
54-
55-
_paths = _paths.Where(x => !string.IsNullOrEmpty(x) && AssetDatabase.IsValidFolder(x)).ToArray();
56-
57-
if (_paths.Length == 0)
58-
{
59-
return;
60-
}
61-
62-
// ReSharper disable once UseArrayEmptyMethod
63-
_savedAssets ??= new Object[0];
64-
65-
var assets = AssetDatabase
66-
.FindAssets("t:Object", _paths)
67-
.Select(AssetDatabase.GUIDToAssetPath)
68-
.Select(AssetDatabase.LoadAssetAtPath<Object>)
69-
.Where(x =>
70-
{
71-
var fileNamespace = x.GetType().Namespace;
72-
73-
return x != null && (fileNamespace == null || !fileNamespace.Contains("UnityEditor"));
74-
})
75-
.ToList();
76-
77-
var newEntries = new List<Object>();
78-
79-
foreach (var asset in assets)
80-
{
81-
if (!TryGetId(asset, out _))
82-
{
83-
newEntries.Add(asset);
84-
}
85-
86-
var children = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GetAssetPath(asset));
87-
88-
foreach (var child in children)
89-
{
90-
if (TryGetId(child, out _))
91-
{
92-
continue;
93-
}
94-
95-
newEntries.Add(child);
96-
}
97-
}
98-
99-
ArrayUtility.AddRange(ref _savedAssets, newEntries.ToArray());
100-
101-
if (_savedAssets.Length == 0 || _savedAssets[0] != null)
102-
{
103-
ArrayUtility.Insert(ref _savedAssets, 0, null);
104-
}
105-
106-
EditorUtility.SetDirty(this);
107-
}
108-
109-
public void Clear()
110-
{
111-
_savedAssets = null;
112-
EditorUtility.SetDirty(this);
113-
}
17+
public void LoadAssets()
18+
{
19+
if (_paths == null)
20+
{
21+
return;
22+
}
23+
24+
_paths = _paths.Where(x => !string.IsNullOrEmpty(x) && AssetDatabase.IsValidFolder(x)).ToArray();
25+
26+
if (_paths.Length == 0)
27+
{
28+
return;
29+
}
30+
31+
// ReSharper disable once UseArrayEmptyMethod
32+
_savedAssets ??= new Object[0];
33+
34+
var assets = AssetDatabase
35+
.FindAssets("t:Object", _paths)
36+
.Select(AssetDatabase.GUIDToAssetPath)
37+
.Select(AssetDatabase.LoadAssetAtPath<Object>)
38+
.Where(x =>
39+
{
40+
var fileNamespace = x.GetType().Namespace;
41+
42+
return x != null && (fileNamespace == null || !fileNamespace.Contains("UnityEditor"));
43+
})
44+
.ToList();
45+
46+
var newEntries = new List<Object>();
47+
48+
foreach (var asset in assets)
49+
{
50+
if (!TryResolveId(asset, out _))
51+
{
52+
newEntries.Add(asset);
53+
}
54+
55+
var children = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GetAssetPath(asset));
56+
57+
foreach (var child in children)
58+
{
59+
if (TryResolveId(child, out _))
60+
{
61+
continue;
62+
}
63+
64+
newEntries.Add(child);
65+
}
66+
}
67+
68+
ArrayUtility.AddRange(ref _savedAssets, newEntries.ToArray());
69+
70+
if (_savedAssets.Length == 0 || _savedAssets[0] != null)
71+
{
72+
ArrayUtility.Insert(ref _savedAssets, 0, null);
73+
}
74+
75+
EditorUtility.SetDirty(this);
76+
}
77+
78+
public void Clear()
79+
{
80+
_savedAssets = null;
81+
EditorUtility.SetDirty(this);
82+
}
11483
#endif
115-
}
116-
}
84+
85+
public bool TryResolveReference(ushort id, out Object entry)
86+
{
87+
entry = null;
88+
89+
if (id == 0 || id >= _savedAssets.Length)
90+
{
91+
return false;
92+
}
93+
94+
entry = _savedAssets[id];
95+
return true;
96+
}
97+
98+
public bool TryResolveId(Object value, out ushort id)
99+
{
100+
id = 0;
101+
102+
for (ushort i = 1; i < _savedAssets.Length; i++)
103+
{
104+
if (_savedAssets[i] != value)
105+
{
106+
continue;
107+
}
108+
109+
id = i;
110+
return true;
111+
}
112+
113+
return false;
114+
}
115+
}
116+
}

Runtime/DataSerializer.cs

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.IO;
34
using System.Threading;
@@ -13,7 +14,7 @@ public static class DataSerializer
1314
private static Dictionary<string, byte[]> _data = new();
1415
private static readonly string _persistentDataPath = Application.persistentDataPath;
1516

16-
internal static AssetsContainer Container { get; private set; }
17+
public static AssetsContainer Container { get; private set; }
1718

1819
public static MessagePackSerializerOptions Options { get; set; } = ContractlessStandardResolverAllowPrivate.Options;
1920

@@ -52,6 +53,16 @@ public static bool TryLoad<T>(string key, out T data)
5253
return hasKey;
5354
}
5455

56+
public static T LoadOrDefault<T>(string key, T defaultValue)
57+
{
58+
if (_data.TryGetValue(key, out var bytes))
59+
{
60+
return Deserialize<T>(bytes);
61+
}
62+
63+
return defaultValue;
64+
}
65+
5566
public static bool HasKey(string key)
5667
{
5768
return _data.ContainsKey(key);
@@ -69,52 +80,90 @@ public static void DeleteAll()
6980

7081
public static void SaveFile(string fileName)
7182
{
72-
var path = GetPath(fileName);
73-
var bytes = Serialize(_data);
74-
75-
File.WriteAllBytes(path, bytes);
76-
77-
#if UNITY_WEBGL
78-
Application.ExternalEval("_JS_FileSystem_Sync();");
79-
#endif
80-
}
83+
if (_data == null || _data.Count == 0)
84+
{
85+
return;
86+
}
8187

82-
public static async Task SaveFileAsync(string fileName, CancellationToken token = default)
83-
{
8488
var path = GetPath(fileName);
85-
var bytes = Serialize(_data);
89+
var tempPath = path + ".tmp";
8690

87-
await File.WriteAllBytesAsync(path, bytes, token);
91+
try
92+
{
93+
var fileExists = File.Exists(path);
94+
95+
File.WriteAllBytes(tempPath, Serialize(_data));
96+
97+
if (TryLoadFile(tempPath, out var tempData))
98+
{
99+
if (fileExists)
100+
{
101+
File.Delete(path);
102+
}
103+
104+
File.Move(tempPath, path);
105+
}
106+
else
107+
{
108+
throw new Exception("Temporary save file validation failed. Aborting save.");
109+
}
110+
}
111+
catch (Exception ex)
112+
{
113+
Debug.LogException(ex);
88114

89-
#if UNITY_WEBGL
90-
Application.ExternalEval("_JS_FileSystem_Sync();");
91-
#endif
115+
if (File.Exists(tempPath))
116+
{
117+
File.Delete(tempPath);
118+
}
119+
}
92120
}
93121

94122
public static void LoadFile(string fileName)
95123
{
96124
var path = GetPath(fileName);
97-
var bytes = File.ReadAllBytes(path);
98125

99-
if (bytes.Length == 0)
126+
if (TryLoadFile(path, out var data))
127+
{
128+
_data = data;
129+
File.Copy(path, path + ".bak", true);
130+
return;
131+
}
132+
133+
Debug.LogWarning("Primary save file is invalid or does not exist. Attempting to load from backup.");
134+
135+
if (TryLoadFile(path + ".bak", out var backupData))
100136
{
137+
_data = backupData;
101138
return;
102139
}
103140

104-
_data = Deserialize<Dictionary<string, byte[]>>(bytes);
141+
Debug.LogWarning("No valid backup save file found. Initializing to an empty state.");
142+
_data = new Dictionary<string, byte[]>();
105143
}
106144

107-
public static async Task LoadFileAsync(string fileName, CancellationToken token = default)
145+
private static bool TryLoadFile(string filePath, out Dictionary<string, byte[]> data)
108146
{
109-
var path = GetPath(fileName);
110-
var bytes = await File.ReadAllBytesAsync(path, token);
111-
112-
if (bytes.Length == 0)
147+
try
113148
{
114-
return;
149+
if (File.Exists(filePath))
150+
{
151+
var bytes = File.ReadAllBytes(filePath);
152+
153+
if (bytes.Length > 0)
154+
{
155+
data = Deserialize<Dictionary<string, byte[]>>(bytes);
156+
return true;
157+
}
158+
}
159+
}
160+
catch (Exception ex)
161+
{
162+
Debug.LogException(ex);
115163
}
116164

117-
_data = Deserialize<Dictionary<string, byte[]>>(bytes);
165+
data = null;
166+
return false;
118167
}
119168

120169
public static byte[] Serialize<T>(T data)

0 commit comments

Comments
 (0)