Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add map refresh #19885

Merged
merged 2 commits into from Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 54 additions & 21 deletions OpenRA.Game/Map/MapCache.cs
@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
Expand Down Expand Up @@ -39,6 +39,14 @@ public sealed class MapCache : IEnumerable<MapPreview>, IDisposable

public Dictionary<string, string> StringPool { get; } = new Dictionary<string, string>();

readonly List<MapDirectoryTracker> mapDirectoryTrackers = new List<MapDirectoryTracker>();

/// <summary>
/// If a map was added oldUID will be null, if updated oldUId will point to the outdated map
/// Event is not called when map is deleted
/// </summary>
public event Action<string, string> MapUpdated = (oldUID, newUID) => { };

public MapCache(ModData modData)
{
this.modData = modData;
Expand All @@ -48,12 +56,20 @@ public MapCache(ModData modData)
sheetBuilder = new SheetBuilder(SheetType.BGRA);
}

public void UpdateMaps()
PunkPun marked this conversation as resolved.
Show resolved Hide resolved
{
foreach (var tracker in mapDirectoryTrackers)
tracker.UpdateMaps(this);
}

public void LoadMaps()
{
// Utility mod that does not support maps
if (!modData.Manifest.Contains<MapGrid>())
return;

var mapGrid = modData.Manifest.Get<MapGrid>();

// Enumerate map directories
foreach (var kv in modData.Manifest.MapFolders)
{
Expand Down Expand Up @@ -85,36 +101,42 @@ public void LoadMaps()
}

mapLocations.Add(package, classification);
mapDirectoryTrackers.Add(new MapDirectoryTracker(mapGrid, package, classification));
}

var mapGrid = modData.Manifest.Get<MapGrid>();
foreach (var kv in MapLocations)
{
foreach (var map in kv.Key.Contents)
LoadMap(map, kv.Key, kv.Value, mapGrid, null);
}
PunkPun marked this conversation as resolved.
Show resolved Hide resolved
}

public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap)
{
IReadOnlyPackage mapPackage = null;
try
{
using (new Support.PerfTimer(map))
{
IReadOnlyPackage mapPackage = null;
try
mapPackage = package.OpenPackage(map, modData.ModFiles);
if (mapPackage != null)
{
using (new Support.PerfTimer(map))
{
mapPackage = kv.Key.OpenPackage(map, modData.ModFiles);
if (mapPackage == null)
continue;
var uid = Map.ComputeUID(mapPackage);
previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type);

var uid = Map.ComputeUID(mapPackage);
previews[uid].UpdateFromMap(mapPackage, kv.Key, kv.Value, modData.Manifest.MapCompatibility, mapGrid.Type);
}
}
catch (Exception e)
{
mapPackage?.Dispose();
Console.WriteLine("Failed to load map: {0}", map);
Console.WriteLine("Details: {0}", e);
Log.Write("debug", "Failed to load map: {0}", map);
Log.Write("debug", "Details: {0}", e);
if (oldMap != uid)
MapUpdated(oldMap, uid);
}
}
}
catch (Exception e)
{
mapPackage?.Dispose();
Console.WriteLine("Failed to load map: {0}", map);
Console.WriteLine("Details: {0}", e);
Log.Write("debug", "Failed to load map: {0}", map);
Log.Write("debug", "Details: {0}", e);
}
}

public IEnumerable<IReadWritePackage> EnumerateMapDirPackages(MapClassification classification = MapClassification.System)
Expand Down Expand Up @@ -345,10 +367,18 @@ public string ChooseInitialMap(string initialUid, MersenneTwister random)
return initialUid;
}

public MapPreview this[string key] => previews[key];
public MapPreview this[string key]
{
get
{
UpdateMaps();
return previews[key];
}
}

public IEnumerator<MapPreview> GetEnumerator()
{
UpdateMaps();
return previews.Values.GetEnumerator();
}

Expand All @@ -368,6 +398,9 @@ public void Dispose()
foreach (var p in previews.Values)
p.Dispose();

foreach (var t in mapDirectoryTrackers)
t.Dispose();

// We need to let the loader thread exit before we can dispose our sheet builder.
// Ideally we should dispose our resources before returning, but we don't to block waiting on the loader thread to exit.
// Instead, we'll queue disposal to be run once it has exited.
Expand Down
129 changes: 129 additions & 0 deletions OpenRA.Game/Map/MapDirectoryTracker.cs
@@ -0,0 +1,129 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;

namespace OpenRA
{
public sealed class MapDirectoryTracker : IDisposable
{
readonly FileSystemWatcher watcher;
PunkPun marked this conversation as resolved.
Show resolved Hide resolved
readonly MapGrid mapGrid;
readonly IReadOnlyPackage package;
readonly MapClassification classification;

enum MapAction { Add, Delete, Update }
readonly Dictionary<string, MapAction> mapActionQueue = new Dictionary<string, MapAction>();

bool dirty = false;

public MapDirectoryTracker(MapGrid mapGrid, 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.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}

public void Dispose()
{
watcher.Dispose();
}

void AddMapAction(MapAction mapAction, string fullpath, string oldFullPath = null)
{
lock (mapActionQueue)
{
dirty = true;

// if path is not root, update map instead
var path = RemoveSubDirs(fullpath);
if (fullpath == path)
mapActionQueue[path] = mapAction;
else
mapActionQueue[path] = MapAction.Update;

// called when file has been renamed / changed location
if (oldFullPath != null)
{
var oldpath = RemoveSubDirs(oldFullPath);
if (oldpath != null)
if (oldFullPath == oldpath)
mapActionQueue[oldpath] = MapAction.Delete;
else
mapActionQueue[oldpath] = MapAction.Update;
}
}
}

public void UpdateMaps(MapCache mapcache)
{
lock (mapActionQueue)
{
if (!dirty)
return;

dirty = false;
foreach (var mapAction in mapActionQueue)
{
var map = mapcache.FirstOrDefault(x => x.Package?.Name == mapAction.Key && x.Status == MapStatus.Available);
if (map != null)
{
if (mapAction.Value == MapAction.Delete)
{
Console.WriteLine(mapAction.Key + " was deleted");
map.Invalidate();
}
else
{
Console.WriteLine(mapAction.Key + " was updated");
map.Invalidate();
mapcache.LoadMap(mapAction.Key.Replace(package.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, map.Uid);
}
}
else
{
if (mapAction.Value != MapAction.Delete)
{
Console.WriteLine(mapAction.Key + " was added");
mapcache.LoadMap(mapAction.Key.Replace(package?.Name + Path.DirectorySeparatorChar, ""), package, classification, mapGrid, null);
}
}
}

mapActionQueue.Clear();
}
}

string RemoveSubDirs(string path)
{
var endPath = path.Replace(package.Name + Path.DirectorySeparatorChar, "");

// if file moved from out outside directory, ignore it
if (path == endPath)
return null;

return package.Name + Path.DirectorySeparatorChar + endPath.Split(Path.DirectorySeparatorChar)[0];
}
}
}
9 changes: 1 addition & 8 deletions OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs
@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
Expand Down Expand Up @@ -178,10 +178,6 @@ public SaveDirectory(Folder folder, string displayName, MapClassification classi

var combinedPath = Platform.ResolvePath(Path.Combine(selectedDirectory.Folder.Name, filename.Text + fileTypes[fileType].Extension));

// Invalidate the old map metadata
if (map.Uid != null && map.Package != null && map.Package.Name == combinedPath)
modData.MapCache[map.Uid].Invalidate();

try
{
if (!(map.Package is IReadWritePackage package) || package.Name != combinedPath)
Expand All @@ -195,9 +191,6 @@ public SaveDirectory(Folder folder, string displayName, MapClassification classi

map.Save(package);

// Update the map cache so it can be loaded without restarting the game
modData.MapCache[map.Uid].UpdateFromMap(map.Package, selectedDirectory.Folder, selectedDirectory.Classification, null, map.Grid.Type);

Console.WriteLine("Saved current map at {0}", combinedPath);
Ui.CloseWindow();

Expand Down