Skip to content

Commit ba50fb3

Browse files
committed
fix wavedash presentation crash in another way
1 parent 5d64d1b commit ba50fb3

File tree

3 files changed

+15
-265
lines changed

3 files changed

+15
-265
lines changed

SpeedrunTool/Source/SaveLoad/Implements/DeepClonerUtils.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ private static void Config() {
4444
|| type == typeof(GraphicsDeviceManager)
4545
|| type == typeof(Monocle.Commands)
4646
|| type == typeof(BitTag)
47+
|| type == typeof(Atlas)
4748

4849
// XNA GraphicsResource
4950
|| type.IsSubclassOf(typeof(GraphicsResource))
@@ -80,13 +81,6 @@ private static void Config() {
8081
}
8182

8283
lock (sourceObj) {
83-
if (sourceObj is Atlas atlas) {
84-
if (StateManager.Instance.State == State.Saving && Thread.CurrentThread.IsMainThread()) {
85-
GraphicResourcesHandler.Add(atlas);
86-
}
87-
return atlas;
88-
}
89-
9084
if (sourceObj is VirtualAsset virtualAsset) {
9185
if (StateManager.Instance.State == State.Loading || !Thread.CurrentThread.IsMainThread()) {
9286
// 预克隆的资源需要等待 LoadState 中移除实体之后才能判断是否需要 Reload,必须等待主线程中再操作

SpeedrunTool/Source/SaveLoad/SaveLoadAction.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public static void SaveStaticMemberValues(Dictionary<Type, Dictionary<string, ob
260260
}
261261

262262
if (!values.TryGetValue(type, out Dictionary<string, object> dict)) {
263-
values[type] = dict = new Dictionary<string, object>();
263+
values[type] = dict = [];
264264
}
265265

266266
foreach (string memberName in memberNames) {
Lines changed: 13 additions & 257 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Collections.Generic;
2-
using System.Linq;
2+
using System.IO;
33

44
namespace Celeste.Mod.SpeedrunTool.SaveLoad.Utils;
55
internal static class GraphicResourcesHandler {
@@ -8,13 +8,9 @@ public static void Add(VirtualAsset asset) {
88
VirtualAssetsHandler.Add(asset);
99
}
1010

11-
public static void Add(Atlas atlas) {
12-
AtlasHandler.Add(atlas);
13-
}
14-
1511
internal static void AddSaveLoadAction() {
16-
AtlasHandler.AddAction();
1712
VirtualAssetsHandler.ReloadVirtualAssets();
13+
AtlasHandler.AddAction();
1814
}
1915

2016
private static class MemoryLeakHandler {
@@ -36,14 +32,14 @@ private static void Handle() {
3632

3733
private static class VirtualAssetsHandler {
3834

39-
private static readonly List<VirtualAsset> VirtualAssets = new(); // added and cleared when loading, so no need to be individual
35+
private static readonly List<VirtualAsset> VirtualAssets = []; // added and cleared when loading, so no need to be individual
4036

4137
internal static void Add(VirtualAsset asset) {
4238
VirtualAssets.Add(asset);
4339
}
4440
internal static void ReloadVirtualAssets() {
4541
SaveLoadAction.InternalSafeAdd(
46-
loadState: (_, _) => {
42+
loadState: static (_, _) => {
4743
List<VirtualAsset> list = new List<VirtualAsset>(VirtualAssets);
4844
// if load too frequently and switching between different slots, then collection might be modified? idk
4945
// so we avoid the crash in this way
@@ -65,12 +61,8 @@ internal static void ReloadVirtualAssets() {
6561

6662
VirtualAssets.Clear();
6763
},
68-
clearState: () => {
69-
VirtualAssets.Clear();
70-
},
71-
preCloneEntities: () => {
72-
VirtualAssets.Clear();
73-
}
64+
clearState: VirtualAssets.Clear,
65+
preCloneEntities: VirtualAssets.Clear
7466
);
7567
}
7668
}
@@ -79,254 +71,18 @@ private static class AtlasHandler {
7971

8072
// This Fixes: WaveDashPresentation 中存档, 结束, 再读档, 会崩溃
8173

82-
/* 每个存档带有信息: 它存档时持有的 atlas, 以及截止到存档时所有在它的历史上本应释放但没释放的 atlas
83-
* 外加 CurrentGame 带有: 它所对应的那个存档的全部信息, 外加读档后新增加的本应释放的 atlas
84-
*
85-
* 每次清除存档时, 尝试释放这个存档持有的且在其他某个存档/CurrentGame 上本应释放的 atlas
86-
* 每次读档时, 将 CurrentGame 的历史回退到读取的档上
87-
* 每次存档时, 将 CurrentGame 的历史存入, 持有 atlas
88-
* 每次 Dispose 但被阻碍时, 存入 CurrentGame
89-
*/
90-
91-
private static Holder CurrentSlot => new Holder(SaveSlotsManager.SlotName, isSlot: true);
92-
93-
private static readonly Holder CurrentGame = new Holder($"SpeedrunTool/{nameof(AtlasHandler)}/CurrentGame", isSlot: false);
94-
95-
[Load]
96-
private static void Load() {
97-
On.Monocle.Atlas.Dispose += Atlas_Dispose;
98-
}
99-
100-
[Unload]
101-
private static void Unload() {
102-
On.Monocle.Atlas.Dispose -= Atlas_Dispose;
103-
}
104-
105-
private static void Atlas_Dispose(On.Monocle.Atlas.orig_Dispose orig, Atlas self) {
106-
bool b = false;
107-
if (BipartiteGraph.HasDisposeBarrier(self)) {
108-
BipartiteGraph.AddDisposeEdge(self, CurrentGame);
109-
b = BipartiteGraph.HasDisposeBarrier(self); // check again. since CurrentGame can be the only barrier
110-
}
111-
if (b) {
112-
Logger.Info($"SpeedrunTool/{nameof(AtlasHandler)}", $"{self.DataPath} was to be disposed but stopped by us.");
113-
}
114-
else {
115-
Logger.Info($"SpeedrunTool/{nameof(AtlasHandler)}", $"Dispose {self.DataPath}");
116-
orig(self);
117-
}
118-
}
119-
120-
internal static void SafeDispose(Atlas atlas) {
121-
// not safe. Just make it easier to debug
122-
atlas?.Dispose();
123-
}
124-
125-
internal static void Add(Atlas atlas) {
126-
BipartiteGraph.AddHoldEdge(atlas, CurrentSlot);
127-
}
128-
12974
internal static void AddAction() {
13075
SaveLoadAction.InternalSafeAdd(
131-
saveState: (_, _) => {
132-
BipartiteGraph.AddTo(CurrentGame, CurrentSlot);
133-
},
134-
loadState: (_, _) => {
135-
BipartiteGraph.Clone(CurrentSlot, CurrentGame);
136-
},
137-
clearState: () => {
138-
BipartiteGraph.ClearAndDispose(CurrentSlot);
139-
}
140-
);
141-
}
142-
143-
private static void DebugLog() {
144-
BipartiteGraph.Log();
145-
}
146-
147-
148-
internal static class BipartiteGraph {
149-
internal enum State { Hold, Dispose } // Hold: don't dispose; Dispose: wanna dispose but failed to
150-
151-
private static readonly HashSet<Atlas> VerticesAtlas = new HashSet<Atlas>();
152-
153-
private static readonly HashSet<Holder> VerticesHolder = new HashSet<Holder>();
154-
155-
private static readonly Dictionary<Atlas, HashSet<Holder>> Atlas2Hold = new(); // only contains Hold edges
156-
157-
private static readonly Dictionary<Atlas, HashSet<Holder>> Atlas2Dispose = new(); // only contains Dispose edges
158-
159-
private static readonly Dictionary<Holder, HashSet<Atlas>> Hold2Atlas = new(); // only contains Hold edges
160-
161-
private static readonly Dictionary<Holder, HashSet<Atlas>> Dispose2Atlas = new(); // only contains Dispose edges
162-
163-
private static void SafeAdd<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> dict, TKey key, TValue value) {
164-
if (dict.TryGetValue(key, out HashSet<TValue> list)) {
165-
list.Add(value);
166-
}
167-
else {
168-
dict[key] = [value];
169-
}
170-
}
171-
172-
private static bool SafeRemove<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> dict, TKey key, TValue value) {
173-
bool b = false;
174-
if (dict.TryGetValue(key, out HashSet<TValue> list)) {
175-
b = list.Remove(value);
176-
if (list.IsNullOrEmpty()) {
177-
dict.Remove(key);
178-
}
179-
}
180-
return b;
181-
}
182-
183-
private static void SafeAdd<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> dict1, Dictionary<TValue, HashSet<TKey>> dict2, TKey key, TValue value) {
184-
SafeAdd(dict1, key, value);
185-
SafeAdd(dict2, value, key);
186-
}
187-
188-
private static bool SafeRemove<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> dict1, Dictionary<TValue, HashSet<TKey>> dict2, TKey key, TValue value) {
189-
bool b1 = SafeRemove(dict1, key, value);
190-
bool b2 = SafeRemove(dict2, value, key);
191-
return b1 || b2;
192-
}
193-
194-
private static bool SafeRemove<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> dict1, Dictionary<TValue, HashSet<TKey>> dict2, TKey key) {
195-
if (dict1.TryGetValue(key, out HashSet<TValue> list)) {
196-
foreach (TValue value in list) {
197-
SafeRemove(dict2, value, key);
198-
}
199-
dict1.Remove(key);
200-
return true;
201-
}
202-
return false;
203-
}
204-
205-
internal static void AddHoldEdge(Atlas atlas, Holder holder) {
206-
VerticesAtlas.Add(atlas);
207-
VerticesHolder.Add(holder);
208-
SafeAdd(Atlas2Hold, Hold2Atlas, atlas, holder);
209-
SafeRemove(Atlas2Dispose, Dispose2Atlas, atlas, holder);
210-
}
211-
212-
internal static void AddDisposeEdge(Atlas atlas, Holder holder) {
213-
VerticesAtlas.Add(atlas);
214-
VerticesHolder.Add(holder);
215-
SafeRemove(Atlas2Hold, Hold2Atlas, atlas, holder);
216-
SafeAdd(Atlas2Dispose, Dispose2Atlas, atlas, holder);
217-
}
218-
219-
internal static void Clear(Atlas atlas) {
220-
VerticesAtlas.Remove(atlas);
221-
SafeRemove(Atlas2Hold, Hold2Atlas, atlas);
222-
SafeRemove(Atlas2Dispose, Dispose2Atlas, atlas);
223-
}
224-
225-
internal static void Clear(Holder holder) {
226-
VerticesHolder.Remove(holder);
227-
SafeRemove(Hold2Atlas, Atlas2Hold, holder);
228-
SafeRemove(Dispose2Atlas, Atlas2Dispose, holder);
229-
}
230-
231-
internal static void ClearAndDispose(Holder holder) {
232-
VerticesHolder.Remove(holder);
233-
SafeRemove(Hold2Atlas, Atlas2Hold, holder);
234-
HashSet<Atlas> unholding = [.. VerticesAtlas.Where(x => !Atlas2Hold.ContainsKey(x))];
235-
foreach (Atlas atlas in unholding.Where(Atlas2Dispose.ContainsKey)) {
236-
SafeDispose(atlas);
237-
}
238-
foreach (Atlas atlas2 in unholding) {
239-
Clear(atlas2);
240-
}
241-
SafeRemove(Dispose2Atlas, Atlas2Dispose, holder);
242-
HashSet<Holder> holders = [.. VerticesHolder.Where(x => !Hold2Atlas.ContainsKey(x) && !Dispose2Atlas.ContainsKey(x))];
243-
foreach (Holder holder2 in holders) {
244-
Clear(holder2);
245-
}
246-
}
247-
248-
internal static void Clone(Holder from, Holder to) {
249-
ClearAndDispose(to);
250-
if (Dispose2Atlas.TryGetValue(from, out HashSet<Atlas> list2)) {
251-
foreach (Atlas atlas in list2) {
252-
AddDisposeEdge(atlas, to);
253-
}
254-
}
255-
if (Hold2Atlas.TryGetValue(from, out HashSet<Atlas> list1)) {
256-
foreach (Atlas atlas in list1) {
257-
AddHoldEdge(atlas, to);
258-
}
259-
}
260-
}
261-
262-
internal static void AddTo(Holder from, Holder to) {
263-
if (Dispose2Atlas.TryGetValue(from, out HashSet<Atlas> list2)) {
264-
foreach (Atlas atlas in list2) {
265-
AddDisposeEdge(atlas, to);
266-
}
267-
}
268-
if (Hold2Atlas.TryGetValue(from, out HashSet<Atlas> list1)) {
269-
foreach (Atlas atlas in list1) {
270-
AddHoldEdge(atlas, to);
271-
}
272-
}
273-
}
274-
275-
276-
internal static bool HasDisposeBarrier(Atlas atlas) {
277-
return Atlas2Hold.ContainsKey(atlas);
278-
}
279-
280-
internal static void Log() {
281-
#if DEBUG
282-
System.Text.StringBuilder sb = new();
283-
sb.Append("BipartiteGraph Structure:");
284-
foreach (Atlas atlas in VerticesAtlas) {
285-
sb.Append($"\n{atlas.DataPath}");
286-
if (Atlas2Hold.TryGetValue(atlas, out HashSet<Holder> list1)) {
287-
sb.Append($" | hold by -> ");
288-
foreach (Holder holder in list1) {
289-
sb.Append($" {holder.Name}, ");
290-
}
291-
}
292-
if (Atlas2Dispose.TryGetValue(atlas, out HashSet<Holder> list2)) {
293-
sb.Append($" | dispose by -> ");
294-
foreach (Holder holder in list2) {
295-
sb.Append($" {holder.Name}, ");
296-
}
297-
}
298-
}
299-
sb.Append('\n');
300-
foreach (Holder holder in VerticesHolder) {
301-
sb.Append($"\n{holder.Name}");
302-
if (Hold2Atlas.TryGetValue(holder, out HashSet<Atlas> list1)) {
303-
sb.Append($" | hold -> ");
304-
foreach (Atlas atlas in list1) {
305-
sb.Append($" {atlas.DataPath}, ");
306-
}
307-
}
308-
if (Dispose2Atlas.TryGetValue(holder, out HashSet<Atlas> list2)) {
309-
sb.Append($" | dispose -> ");
310-
foreach (Atlas atlas in list2) {
311-
sb.Append($" {atlas.DataPath}, ");
76+
loadState: static (_, level) => {
77+
if (level.Tracker.GetEntitiesTrackIfNeeded<WaveDashPresentation>() is { } list && list.IsNotNullOrEmpty()) {
78+
foreach (WaveDashPresentation presentation in list) {
79+
presentation.Gfx?.Dispose();
80+
presentation.Gfx = Atlas.FromAtlas(Path.Combine("Graphics", "Atlases", "WaveDashing"), Atlas.AtlasDataFormat.Packer);
81+
presentation.loading = false;
31282
}
31383
}
31484
}
315-
Logger.Debug($"SpeedrunTool/{nameof(Holder)}", sb.ToString());
316-
#endif
317-
}
318-
}
319-
320-
internal struct Holder(string name, bool isSlot) {
321-
public enum HolderType { Slot, CurrentRunningGame }
322-
323-
public string Name = name;
324-
325-
public HolderType Type = isSlot ? HolderType.Slot : HolderType.CurrentRunningGame;
326-
327-
public override readonly string ToString() {
328-
return $"{nameof(Holder)}[{(Type == HolderType.Slot ? Name : "CurrentGame")}]";
329-
}
85+
);
33086
}
33187
}
33288
}

0 commit comments

Comments
 (0)