Skip to content

Commit 17533c5

Browse files
authored
Fix memory leaks (#55)
* remove a lot of useless observables, cleanup code and comments * remove static object manager since it isn't necessary any more * remove commented out code * rearrange some classes
1 parent 03e2db4 commit 17533c5

26 files changed

+361
-555
lines changed

AvaGui/Models/FileSystemItemGroup.cs

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,9 @@
1-
using Avalonia.Data.Converters;
21
using OpenLoco.ObjectEditor.Data;
32
using OpenLoco.ObjectEditor.Objects;
4-
using System;
5-
using System.Collections.Generic;
63
using System.Collections.ObjectModel;
7-
using System.Globalization;
84

95
namespace AvaGui.Models
106
{
11-
public class ObjectTypeToMaterialIconConverter : IValueConverter
12-
{
13-
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14-
{
15-
if (Enum.TryParse<ObjectType>(value as string, out var objType) && ObjectMapping.TryGetValue(objType, out var objectIcon))
16-
{
17-
return objectIcon;
18-
}
19-
20-
if (Enum.TryParse<VehicleType>(value as string, out var vehType) && VehicleMapping.TryGetValue(vehType, out var vehicleIcon))
21-
{
22-
return vehicleIcon;
23-
}
24-
25-
if (value is SourceGame sourceType && SourceGameMapping.TryGetValue(sourceType, out var sourceIcon))
26-
{
27-
return sourceIcon;
28-
}
29-
30-
return null;
31-
}
32-
33-
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
34-
=> throw new NotImplementedException();
35-
36-
public static Dictionary<ObjectType, string> ObjectMapping = new Dictionary<ObjectType, string>()
37-
{
38-
{ ObjectType.InterfaceSkin, "Monitor" },
39-
{ ObjectType.Sound, "Speaker" },
40-
{ ObjectType.Currency, "Cash" },
41-
{ ObjectType.Steam, "Smoke" },
42-
{ ObjectType.CliffEdge, "Landslide" },
43-
{ ObjectType.Water, "Waves" },
44-
{ ObjectType.Land, "Terrain" },
45-
{ ObjectType.TownNames, "NoteText" },
46-
{ ObjectType.Cargo, "ShippingPallet" },
47-
{ ObjectType.Wall, "Wall" },
48-
{ ObjectType.TrainSignal, "TrafficLight" },
49-
{ ObjectType.LevelCrossing, "RailroadLight" },
50-
{ ObjectType.StreetLight, "CeilingLight" },
51-
{ ObjectType.Tunnel, "Tunnel" },
52-
{ ObjectType.Bridge, "Bridge" },
53-
{ ObjectType.TrainStation, "Domain" },
54-
{ ObjectType.TrackExtra, "FenceElectric" },
55-
{ ObjectType.Track, "Fence" },
56-
{ ObjectType.RoadStation, "Monitor" },
57-
{ ObjectType.RoadExtra, "RoadVariant" },
58-
{ ObjectType.Road, "Road" },
59-
{ ObjectType.Airport, "Airport" },
60-
{ ObjectType.Dock, "Ferry" },
61-
{ ObjectType.Vehicle, "PlaneTrain" },
62-
{ ObjectType.Tree, "PineTreeVariant" },
63-
{ ObjectType.Snow, "Snowflake" },
64-
{ ObjectType.Climate, "WeatherPartlyCloudy" },
65-
{ ObjectType.HillShapes, "Map" },
66-
{ ObjectType.Building, "OfficeBuilding" },
67-
{ ObjectType.Scaffolding, "Crane" },
68-
{ ObjectType.Industry, "Factory" },
69-
{ ObjectType.Region, "Earth" },
70-
{ ObjectType.Competitor, "AccountTie" },
71-
{ ObjectType.ScenarioText, "ScriptText" },
72-
};
73-
74-
public static Dictionary<VehicleType, string> VehicleMapping = new Dictionary<VehicleType, string>()
75-
{
76-
{ VehicleType.Train, "Train" },
77-
{ VehicleType.Bus, "Bus" },
78-
{ VehicleType.Truck, "Truck" },
79-
{ VehicleType.Tram, "Tram" },
80-
{ VehicleType.Aircraft, "Airplane" },
81-
{ VehicleType.Ship, "Sailboat" },
82-
};
83-
84-
public static Dictionary<SourceGame, string> SourceGameMapping = new Dictionary<SourceGame, string>()
85-
{
86-
{ SourceGame.Custom, "AccountEdit" },
87-
{ SourceGame.DataFile, "File" },
88-
{ SourceGame.Vanilla, "AccountTieHat" },
89-
};
90-
}
917

928
public abstract record FileSystemItemBase(string Path, string Name, ObservableCollection<FileSystemItemBase>? SubNodes = null)
939
{

AvaGui/Models/IndexObjectHeader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
namespace AvaGui.Models
66
{
7-
public record IndexObjectHeader(string Name, ObjectType ObjectType, SourceGame SourceGame, UInt32 Checksum, VehicleType? VehicleType);
7+
public record IndexObjectHeader(string Name, ObjectType ObjectType, SourceGame SourceGame, uint32_t Checksum, VehicleType? VehicleType);
88
}

AvaGui/Models/ObjectEditorModel.cs

Lines changed: 50 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,7 @@
2020

2121
namespace AvaGui.Models
2222
{
23-
public class VersionCheckBody
24-
{
25-
[JsonPropertyName("tag_name")]
26-
public string TagName { get; set; }
27-
}
28-
29-
public class ObjectEditorModel : ReactiveObject // todo: only viewmodels should be reactive
23+
public class ObjectEditorModel
3024
{
3125
public EditorSettings Settings { get; private set; }
3226

@@ -36,8 +30,6 @@ public class ObjectEditorModel : ReactiveObject // todo: only viewmodels should
3630

3731
public HeaderIndex HeaderIndex { get; private set; } = [];
3832

39-
public ObjectCache ObjectCache { get; private set; } = [];
40-
4133
public PaletteMap PaletteMap { get; set; }
4234

4335
public G1Dat? G1 { get; set; }
@@ -134,73 +126,57 @@ public void SaveSettings()
134126
File.WriteAllText(SettingsFilePath, text);
135127
}
136128

137-
public bool TryGetObject(string path, out UiLocoFile? uiLocoFile, bool reload = false)
129+
public bool TryLoadObject(string filename, out UiLocoFile? uiLocoFile)
138130
{
139-
if (ObjectCache.TryGetValue(path, out var obj) && !reload)
131+
if (string.IsNullOrEmpty(filename))
140132
{
141-
uiLocoFile = obj;
142-
return true;
133+
uiLocoFile = null;
134+
return false;
143135
}
144-
else if (File.Exists(path))
136+
137+
(var fileInfo, var locoObject) = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: Logger);
138+
139+
if (locoObject == null)
145140
{
146-
var loadResult = LoadSingleObjectFile(path, HeaderIndex, ObjectCache, out var _);
147-
if (loadResult)
148-
{
149-
uiLocoFile = ObjectCache[path];
150-
return true;
151-
}
141+
Logger?.Error($"Unable to load {filename}. FileInfo={fileInfo}");
142+
uiLocoFile = null;
143+
return false;
152144
}
153145

154-
uiLocoFile = null;
155-
return false;
146+
uiLocoFile = new UiLocoFile() { DatFileInfo = fileInfo, LocoObject = locoObject };
147+
return true;
156148
}
157149

158150
// this method loads every single object entirely. it takes a long time to run
159151
void CreateIndex(string[] allFiles, IProgress<float>? progress)
160152
{
153+
Logger?.Info($"Creating index on {allFiles.Length} files");
154+
161155
ConcurrentDictionary<string, IndexObjectHeader> ccHeaderIndex = new(); // key is full path/filename
162-
ConcurrentDictionary<string, UiLocoFile> ccObjectCache = new(); // key is full path/filename
163156

164157
var count = 0;
165-
166158
ConcurrentDictionary<string, TimeSpan> timePerFile = new();
167159

168-
Logger?.Info($"Creating index on {allFiles.Length} files");
169160
var sw = new Stopwatch();
170161
sw.Start();
171162

172-
_ = Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 16 }, (file) =>
173-
//foreach (var file in allFiles)
174-
{
175-
try
176-
{
177-
var startTime = sw.Elapsed;
178-
_ = LoadSingleObjectFile(file, ccHeaderIndex, ccObjectCache, out var fileInfo);
179-
var elapsed = sw.Elapsed - startTime;
180-
181-
if (fileInfo != null)
182-
{
183-
_ = timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed);
184-
}
185-
}
186-
catch (Exception ex)
187-
{
188-
Logger?.Error($"Failed to load \"{file}\"", ex);
163+
var fileCount = allFiles.Length;
164+
var parallelise = false;
189165

190-
//var obj = SawyerStreamReader.LoadS5HeaderFromFile(file);
191-
//var indexObjectHeader = new IndexObjectHeader(obj.Name, obj.ObjectType, obj.SourceGame, obj.Checksum, null);
192-
//_ = ccHeaderIndex.TryAdd(file, indexObjectHeader);
193-
}
194-
finally
166+
if (parallelise)
167+
{
168+
_ = Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 16 }, (filename)
169+
=> count = LoadAndIndexFile(count, filename));
170+
}
171+
else
172+
{
173+
foreach (var filename in allFiles)
195174
{
196-
_ = Interlocked.Increment(ref count);
197-
progress?.Report(count / (float)allFiles.Length);
175+
count = LoadAndIndexFile(count, filename);
198176
}
199-
//}
200-
});
177+
}
201178

202179
HeaderIndex = ccHeaderIndex.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
203-
ObjectCache = ccObjectCache.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
204180

205181
sw.Stop();
206182
Logger?.Info("Finished creating index");
@@ -219,39 +195,33 @@ void CreateIndex(string[] allFiles, IProgress<float>? progress)
219195

220196
var median = timePerFile.OrderBy(x => x.Value).Skip(timePerFile.Count / 2).Take(1).Single();
221197
Logger?.Debug($"Median time={median.Value}ms");
222-
}
223198

224-
private bool LoadSingleObjectFile(string file, IDictionary<string, IndexObjectHeader> ccHeaderIndex, IDictionary<string, UiLocoFile> ccObjectCache, out DatFileInfo? fileInfo)
225-
{
226-
(fileInfo, var locoObject) = SawyerStreamReader.LoadFullObjectFromFile(file, logger: Logger);
227-
228-
if (locoObject == null)
229-
{
230-
Logger?.Error($"Unable to load {file}. FileInfo={fileInfo}");
231-
return false;
232-
}
233-
234-
var newUiLocoFile = new UiLocoFile { DatFileInfo = fileInfo, LocoObject = locoObject };
235-
if (!ccObjectCache.TryAdd(file, newUiLocoFile))
199+
int LoadAndIndexFile(int count, string filename)
236200
{
237-
// replace the old file
238-
ccObjectCache[file] = newUiLocoFile;
239-
}
201+
var startTime = sw.Elapsed;
202+
var loadResult = TryLoadObject(filename, out var uiLocoFile);
203+
var elapsed = sw.Elapsed - startTime;
240204

241-
VehicleType? veh = null;
242-
if (locoObject.Object is VehicleObject vo)
243-
{
244-
veh = vo.Type;
245-
}
205+
if (loadResult && uiLocoFile != null)
206+
{
207+
_ = ccHeaderIndex.TryAdd(filename, new IndexObjectHeader(
208+
uiLocoFile.DatFileInfo.S5Header.Name,
209+
uiLocoFile.DatFileInfo.S5Header.ObjectType,
210+
uiLocoFile.DatFileInfo.S5Header.SourceGame,
211+
uiLocoFile.DatFileInfo.S5Header.Checksum,
212+
uiLocoFile.LocoObject.Object is VehicleObject veh ? veh.Type : null));
213+
214+
_ = timePerFile.TryAdd(uiLocoFile.DatFileInfo.S5Header.Name, elapsed);
215+
}
216+
else
217+
{
218+
Logger?.Error($"Failed to load \"{filename}\"");
219+
}
246220

247-
var indexObjectHeader = new IndexObjectHeader(fileInfo.S5Header.Name, fileInfo.S5Header.ObjectType, fileInfo.S5Header.SourceGame, fileInfo.S5Header.Checksum, veh);
248-
if (!ccHeaderIndex.TryAdd(file, indexObjectHeader))
249-
{
250-
// replace the old file
251-
ccHeaderIndex[file] = indexObjectHeader;
221+
_ = Interlocked.Increment(ref count);
222+
progress?.Report((float)count / fileCount);
223+
return count;
252224
}
253-
254-
return true;
255225
}
256226

257227
public void SaveFile(string path, UiLocoFile obj)
@@ -384,25 +354,5 @@ static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex,
384354

385355
return JsonSerializer.Deserialize<HeaderIndex>(json, GetOptions()) ?? [];
386356
}
387-
388-
public UiLocoFile? LoadAndCacheObject(string filename)
389-
{
390-
if (string.IsNullOrEmpty(filename) || !filename.EndsWith(".dat", StringComparison.InvariantCultureIgnoreCase) || !File.Exists(filename))
391-
{
392-
return null;
393-
}
394-
395-
if (ObjectCache.TryGetValue(filename, out var value))
396-
{
397-
return value;
398-
}
399-
else
400-
{
401-
var obj = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: Logger);
402-
var uiObj = new UiLocoFile { DatFileInfo = obj.DatFileInfo, LocoObject = obj.LocoObject };
403-
_ = ObjectCache.TryAdd(filename, uiObj);
404-
return uiObj;
405-
}
406-
}
407357
}
408358
}

0 commit comments

Comments
 (0)