diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index 3bc61428af..64703a9cbd 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -235,7 +235,7 @@ namespace OpenRA .ToList(); foreach (var uid in queryUids) - previews[uid].UpdateRemoteSearch(MapStatus.Searching, null, null); + previews[uid].BeginRemoteSearch(); Task.Run(async () => { @@ -251,29 +251,21 @@ namespace OpenRA try { var result = await client.GetStreamAsync(url); - var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool); - foreach (var kv in yaml) - previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived); - - foreach (var uid in batchUids) - { - var p = previews[uid]; - if (p.Status != MapStatus.DownloadAvailable) - p.UpdateRemoteSearch(MapStatus.Unavailable, null, null); - } + foreach (var kv in MiniYaml.FromStream(result, url, stringPool: stringPool)) + previews[kv.Key].CompleteRemoteSearch(kv.Value, mapDetailsReceived); } catch (Exception e) { Log.Write("debug", "Remote map query failed with error:"); Log.Write("debug", e); Log.Write("debug", $"URL was: {url}"); + } - foreach (var uid in batchUids) - { - var p = previews[uid]; - p.UpdateRemoteSearch(MapStatus.Unavailable, null, null); - mapQueryFailed?.Invoke(p); - } + foreach (var uid in batchUids) + { + var p = previews[uid]; + if (p.Status == MapStatus.Searching) + p.CompleteRemoteSearch(null, mapQueryFailed); } } } diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 460d6a9263..f703283945 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -196,6 +196,7 @@ namespace OpenRA } static readonly CPos[] NoSpawns = []; + readonly object syncRoot = new(); readonly MapCache cache; readonly ModData modData; IReadOnlyPackage package; @@ -452,28 +453,37 @@ namespace OpenRA newData.ModifiedDate = File.GetLastWriteTime(p.Name); // Assign the new data atomically - innerData = newData; + // Local maps have higher precedence than remote/generated maps, + // so should always replace their metadata + lock (syncRoot) + innerData = newData; } - public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action parseMetadata = null) + public void BeginRemoteSearch() { var newData = innerData.Clone(); - newData.Status = status; newData.Class = MapClassification.Remote; + newData.Status = MapStatus.Searching; - if (status == MapStatus.DownloadAvailable) + // We may have been resolved to a local/generated map by another + // async task. Make sure we don't stomp over their state! + lock (syncRoot) + if (innerData.Class == MapClassification.Unknown || innerData.Class == MapClassification.Remote) + innerData = newData; + } + + public void CompleteRemoteSearch(MiniYaml yaml, Action parseMetadata = null) + { + var newData = innerData.Clone(); + newData.Class = MapClassification.Remote; + newData.Status = MapStatus.Unavailable; + + if (yaml != null) { try { var r = FieldLoader.Load(yaml); - - // Map download has been disabled server side - if (!r.downloading) - { - newData.Status = MapStatus.Unavailable; - return; - } - + newData.Status = r.downloading ? MapStatus.DownloadAvailable : MapStatus.Unavailable; newData.Title = r.title; newData.Categories = r.categories; newData.Author = r.author; @@ -520,19 +530,27 @@ namespace OpenRA { Log.Write("debug", "Failed parsing mapserver response:"); Log.Write("debug", e); + newData.Status = MapStatus.Unavailable; } + } - // Commit updated data before running the callbacks - innerData = newData; + // We may have been resolved to a local/generated map by another + // async task. Make sure we don't stomp over their state! + MapClassification mapClassification; + lock (syncRoot) + { + mapClassification = innerData.Class; + if (mapClassification == MapClassification.Remote) + innerData = newData; + } + if (mapClassification == MapClassification.Remote) + { if (innerData.Preview != null) cache.CacheMinimap(this); parseMetadata?.Invoke(this); } - - // Update the status and class unconditionally - innerData = newData; } public void Install(string mapRepositoryUrl) @@ -605,7 +623,11 @@ namespace OpenRA public void Invalidate() { - innerData.Status = MapStatus.Unavailable; + lock (syncRoot) + { + innerData.Class = MapClassification.Unknown; + innerData.Status = MapStatus.Unavailable; + } } public void Dispose()