Rework MapCache API.

This clarifies the ownership of the underlying
IReadOnlyPackage objects, and avoids passing
around some unnecessary state.
This commit is contained in:
Paul Chote
2025-03-02 21:19:50 +00:00
committed by Gustas Kažukauskas
parent 5c0b82e65b
commit e186dc428e
7 changed files with 72 additions and 62 deletions

View File

@@ -978,7 +978,7 @@ namespace OpenRA
Order.Command($"state {Session.ClientState.Ready}")
};
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.PackageName) == launchMap);
var map = ModData.MapCache.SingleOrDefault(m => m.Uid == launchMap || Path.GetFileName(m.Path) == launchMap);
if (map == null)
throw new ArgumentException($"Could not find map '{launchMap}'.");

View File

@@ -87,8 +87,6 @@ namespace OpenRA
if (!modData.Manifest.Contains<MapGrid>())
return;
var mapGrid = modData.Manifest.Get<MapGrid>();
// Enumerate map directories
foreach (var kv in modData.Manifest.MapFolders)
{
@@ -120,28 +118,29 @@ namespace OpenRA
}
mapLocations.Add(package, classification);
mapDirectoryTrackers.Add(new MapDirectoryTracker(mapGrid, package, classification));
mapDirectoryTrackers.Add(new MapDirectoryTracker(package, classification));
}
// PERF: Load the mod YAML once outside the loop, and reuse it when resolving each maps custom YAML.
var modDataRules = modData.GetRulesYaml();
var gridType = modData.Manifest.Get<MapGrid>().Type;
foreach (var kv in MapLocations)
{
foreach (var map in kv.Key.Contents)
LoadMapInternal(map, kv.Key, kv.Value, mapGrid, null, modDataRules);
LoadMapInternal(map, kv.Key, kv.Value, null, gridType, modDataRules);
}
// We only want to track maps in runtime, not at loadtime
LastModifiedMap = null;
}
public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap)
public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, string oldMap)
{
LoadMapInternal(map, package, classification, mapGrid, oldMap, null);
LoadMapInternal(map, package, classification, oldMap);
}
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap,
IEnumerable<List<MiniYamlNode>> modDataRules)
void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, string oldMap,
MapGridType? gridType = null, IEnumerable<List<MiniYamlNode>> modDataRules = null)
{
IReadOnlyPackage mapPackage = null;
try
@@ -152,10 +151,8 @@ namespace OpenRA
if (mapPackage != null)
{
var uid = Map.ComputeUID(mapPackage);
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules);
// Freeing the package to save memory if there is a lot of Maps
previews[uid].DisposePackage();
previews[uid].UpdateFromMapWithoutOwningPackage(mapPackage, package, classification, gridType, modDataRules);
mapPackage.Dispose();
if (oldMap != uid)
{
@@ -257,7 +254,7 @@ namespace OpenRA
var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool);
foreach (var kv in yaml)
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived);
previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
foreach (var uid in batchUids)
{

View File

@@ -20,7 +20,6 @@ namespace OpenRA
public sealed class MapDirectoryTracker : IDisposable
{
readonly FileSystemWatcher watcher;
readonly MapGrid mapGrid;
readonly IReadOnlyPackage package;
readonly MapClassification classification;
@@ -29,17 +28,16 @@ namespace OpenRA
bool dirty = false;
public MapDirectoryTracker(MapGrid mapGrid, IReadOnlyPackage package, MapClassification classification)
public MapDirectoryTracker(IReadOnlyPackage package, MapClassification classification)
{
this.mapGrid = mapGrid;
this.package = package;
this.classification = classification;
watcher = new FileSystemWatcher(package.Name);
watcher.Changed += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Update, e.FullPath);
watcher.Created += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Add, e.FullPath);
watcher.Deleted += (object sender, FileSystemEventArgs e) => AddMapAction(MapAction.Delete, e.FullPath);
watcher.Renamed += (object sender, RenamedEventArgs e) => AddMapAction(MapAction.Add, e.FullPath, e.OldFullPath);
watcher.Changed += (_, e) => AddMapAction(MapAction.Update, e.FullPath);
watcher.Created += (_, e) => AddMapAction(MapAction.Add, e.FullPath);
watcher.Deleted += (_, e) => AddMapAction(MapAction.Delete, e.FullPath);
watcher.Renamed += (_, e) => AddMapAction(MapAction.Add, e.FullPath, e.OldFullPath);
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
@@ -86,7 +84,7 @@ namespace OpenRA
dirty = false;
foreach (var mapAction in mapActionQueue)
{
var map = mapcache.FirstOrDefault(x => x.PackageName == mapAction.Key && x.Status == MapStatus.Available);
var map = mapcache.FirstOrDefault(x => x.Path == mapAction.Key && x.Status == MapStatus.Available);
if (map != null)
{
if (mapAction.Value == MapAction.Delete)
@@ -98,7 +96,7 @@ namespace OpenRA
{
Console.WriteLine(mapAction.Key + " was updated");
map.Invalidate();
mapcache.LoadMap(mapAction.Key.Replace(package.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, map.Uid);
mapcache.LoadMap(mapAction.Key.Replace(package.Name + Path.DirectorySeparatorChar, ""), package, classification, map.Uid);
}
}
else
@@ -106,7 +104,7 @@ namespace OpenRA
if (mapAction.Value != MapAction.Delete)
{
Console.WriteLine(mapAction.Key + " was added");
mapcache.LoadMap(mapAction.Key.Replace(package?.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, null);
mapcache.LoadMap(mapAction.Key.Replace(package?.Name + Path.DirectorySeparatorChar, ""), package, classification, null);
}
}
}

View File

@@ -198,19 +198,23 @@ namespace OpenRA
static readonly CPos[] NoSpawns = Array.Empty<CPos>();
readonly MapCache cache;
readonly ModData modData;
IReadOnlyPackage package;
public readonly string Uid;
public string PackageName { get; private set; }
IReadOnlyPackage package;
public IReadOnlyPackage Package
{
get
{
package ??= parentPackage.OpenPackage(PackageName, modData.ModFiles);
return package;
}
private set => package = value;
public string Path { get; private set; }
void LoadPackage()
{
if (package == null && parentPackage != null)
package = parentPackage.OpenPackage(Path, modData.ModFiles);
}
public Map ToMap()
{
LoadPackage();
using (new PerfTimer("Map"))
return new Map(modData, package);
}
IReadOnlyPackage parentPackage;
@@ -317,9 +321,28 @@ namespace OpenRA
};
}
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification,
string[] mapCompatibility, MapGridType gridType, IEnumerable<List<MiniYamlNode>> modDataRules)
/// <summary>
/// Updates internal state from a map without taking ownership of its package.
/// A new copy of the map package will be opened lazily when needed.
/// </summary>
public void UpdateFromMapWithoutOwningPackage(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification,
MapGridType? gridType = null, IEnumerable<List<MiniYamlNode>> modDataRules = null)
{
UpdateFromMap(p, classification, gridType, modDataRules);
parentPackage = parent;
package = null;
}
/// <summary>
/// Updates internal state from a map and takes ownership of its package.
/// The package remains in memory and must not be disposed.
/// </summary>
public void UpdateFromMap(IReadOnlyPackage p, MapClassification classification,
MapGridType? gridType = null, IEnumerable<List<MiniYamlNode>> modDataRules = null)
{
Path = p.Name;
package = p;
Dictionary<string, MiniYaml> yaml;
using (var yamlStream = p.GetStream("map.yaml"))
{
@@ -329,12 +352,9 @@ namespace OpenRA
yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary();
}
PackageName = p.Name;
parentPackage = parent;
var newData = innerData.Clone();
newData.GridType = gridType;
newData.Class = classification;
newData.GridType = gridType ?? modData.Manifest.Get<MapGrid>().Type;
if (yaml.TryGetValue("MapFormat", out var temp))
{
@@ -368,7 +388,7 @@ namespace OpenRA
if (yaml.TryGetValue("MapFormat", out temp))
newData.MapFormat = FieldLoader.GetValue<int>("MapFormat", temp.Value);
newData.Status = mapCompatibility == null || mapCompatibility.Contains(requiresMod) ?
newData.Status = modData.Manifest.MapCompatibility.Contains(requiresMod) ?
MapStatus.Available : MapStatus.Unavailable;
try
@@ -420,7 +440,7 @@ namespace OpenRA
innerData = newData;
}
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, string[] mapCompatibility, Action<MapPreview> parseMetadata = null)
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
{
var newData = innerData.Clone();
newData.Status = status;
@@ -478,7 +498,7 @@ namespace OpenRA
// Map is for a different mod: update its information so it can be displayed
// in the cross-mod server browser UI, but mark it as unavailable so it can't
// be selected in a server for the current mod.
if (!mapCompatibility.Contains(r.game_mod))
if (!modData.Manifest.MapCompatibility.Contains(r.game_mod))
newData.Status = MapStatus.Unavailable;
}
catch (Exception e)
@@ -553,12 +573,12 @@ namespace OpenRA
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
Log.Write("debug", $"Downloaded map to '{mapFilename}'");
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
if (package == null)
var p = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
if (p == null)
innerData.Status = MapStatus.DownloadError;
else
{
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType, null);
UpdateFromMapWithoutOwningPackage(p, mapInstallPackage, MapClassification.User, GridType);
Game.RunAfterTick(onSuccess);
}
}
@@ -577,11 +597,6 @@ namespace OpenRA
}
public void Dispose()
{
DisposePackage();
}
public void DisposePackage()
{
if (package != null)
{
@@ -593,14 +608,15 @@ namespace OpenRA
public void Delete()
{
Invalidate();
(parentPackage as IReadWritePackage)?.Delete(Package.Name);
(parentPackage as IReadWritePackage)?.Delete(Path);
}
Stream IReadOnlyFileSystem.Open(string filename)
{
// Explicit package paths never refer to a map
if (!filename.Contains('|') && Package.Contains(filename))
return Package.GetStream(filename);
LoadPackage();
if (!filename.Contains('|') && package.Contains(filename))
return package.GetStream(filename);
return modData.DefaultFileSystem.Open(filename);
}
@@ -616,7 +632,8 @@ namespace OpenRA
// Explicit package paths never refer to a map
if (!filename.Contains('|'))
{
s = Package.GetStream(filename);
LoadPackage();
s = package.GetStream(filename);
if (s != null)
return true;
}
@@ -627,7 +644,8 @@ namespace OpenRA
bool IReadOnlyFileSystem.Exists(string filename)
{
// Explicit package paths never refer to a map
if (!filename.Contains('|') && Package.Contains(filename))
LoadPackage();
if (!filename.Contains('|') && package.Contains(filename))
return true;
return modData.DefaultFileSystem.Exists(filename);

View File

@@ -148,11 +148,8 @@ namespace OpenRA
if (MapCache[uid].Status != MapStatus.Available)
throw new InvalidDataException($"Invalid map uid: {uid}");
Map map;
using (new Support.PerfTimer("Map"))
map = new Map(this, MapCache[uid].Package);
// Reinitialize all our assets
var map = MapCache[uid].ToMap();
InitializeLoaders(map);
map.Sequences.LoadSprites();

View File

@@ -247,7 +247,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (map.Package?.Name != combinedPath)
{
// When creating a new map or when file paths don't match
if (modData.MapCache.Any(m => m.Status == MapStatus.Available && m.PackageName == combinedPath))
if (modData.MapCache.Any(m => m.Status == MapStatus.Available && m.Path == combinedPath))
{
ConfirmationDialogs.ButtonPrompt(modData,
title: OverwriteMapFailedTitle,

View File

@@ -166,7 +166,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
.Select(p => new
{
Preview = p,
Index = missionMapPaths.IndexOf(Path.GetFileName(p.PackageName))
Index = missionMapPaths.IndexOf(Path.GetFileName(p.Path))
})
.Where(x => x.Index != -1)
.OrderBy(x => x.Index)