Handle race conditions and manage package ownership

This commit is contained in:
Gustas
2025-12-01 18:32:28 +02:00
committed by Gustas Kažukauskas
parent 9e926b0f53
commit 027e38ee69
2 changed files with 65 additions and 41 deletions

View File

@@ -151,22 +151,29 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var approving = new Action(() =>
{
// CloseWindow will dispose this logic, so take ownership of the package.
var package = generatedMapPackage;
generatedMapPackage = null;
Ui.CloseWindow();
if (currentTab == MapClassification.Generated && generatedMapArgs != null)
{
// PERF: Add the map directly into the map cache to allow an instant map switch for the local player
var p = modData.MapCache[generatedMapArgs.Uid];
if (p.Status != MapStatus.Available && generatedMapPackage is ZipFileLoader.ReadWriteZipFile zipPackage)
if (p.Status != MapStatus.Available && package is ZipFileLoader.ReadWriteZipFile zipPackage)
{
// The original package will be disposed, so take a deep copy
var package = ZipFileLoader.ReadWriteZipFile.FromBase64String(zipPackage.ToBase64String());
p.UpdateFromMap(package, MapClassification.Generated);
p.UpdateFromMap(zipPackage, MapClassification.Generated);
// UpdateFromMap took ownership of the package.
package = null;
}
onSelectGenerated?.Invoke(generatedMapArgs);
}
else
onSelect?.Invoke(selectedUid);
package?.Dispose();
});
var canceling = new Action(() => { Ui.CloseWindow(); onExit(); });
@@ -411,6 +418,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
"onGenerate", (Action<MapGenerationArgs, IReadWritePackage>)((args, package) =>
{
generatedMapArgs = args;
generatedMapPackage?.Dispose();
generatedMapPackage = package;
})
}
@@ -640,6 +648,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
protected override void Dispose(bool disposing)
{
disposed = true;
generatedMapPackage?.Dispose();
generatedMapPackage = null;
base.Dispose(disposing);
}
}

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileSystem;
using OpenRA.Mods.Common.MapGenerator;
@@ -77,23 +78,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Widget tilesetSetting;
readonly Widget sizeSetting;
readonly Widget parentWidget;
readonly ZipFileLoader.ReadWriteZipFile package;
ITerrainInfo selectedTerrain;
string selectedSize;
Size size;
volatile bool generating;
volatile bool failed;
bool initialGenerationDone;
volatile bool failed;
volatile uint generationCounter = 0;
volatile uint lastGeneration = 0;
bool IsGenerating => lastGeneration != generationCounter;
[ObjectCreator.UseCtor]
internal MapGeneratorLogic(Widget widget, ModData modData, MapGenerationArgs initialSettings, Action<MapGenerationArgs, IReadWritePackage> onGenerate)
{
this.modData = modData;
this.onGenerate = onGenerate;
parentWidget = widget.Parent;
package = new ZipFileLoader.ReadWriteZipFile();
generator = modData.DefaultRules.Actors[SystemActors.EditorWorld].TraitInfos<IEditorMapGeneratorInfo>().First();
settings = generator.GetSettings();
@@ -103,7 +105,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var title = new CachedTransform<string, string>(id => FluentProvider.GetMessage(id));
var previewTitleLabel = widget.Get<LabelWidget>("TITLE");
previewTitleLabel.GetText = () => title.Update(generating ? Generating : failed ? GenerationFailed : RandomMap);
previewTitleLabel.GetText = () => title.Update(IsGenerating ? Generating : failed ? GenerationFailed : RandomMap);
var previewDetailsLabel = widget.GetOrNull<LabelWidget>("DETAILS");
if (previewDetailsLabel != null)
@@ -196,7 +198,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
};
var generateButton = widget.Get<ButtonWidget>("BUTTON_GENERATE");
generateButton.IsDisabled = () => generating;
generateButton.IsDisabled = () => IsGenerating;
generateButton.OnClick = () =>
{
settings.Randomize(Game.CosmeticRandom);
@@ -235,7 +237,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public override void Tick()
{
if (!initialGenerationDone && !generating && parentWidget.IsVisible())
if (!initialGenerationDone && !IsGenerating && parentWidget.IsVisible())
{
initialGenerationDone = true;
GenerateMap();
@@ -402,46 +404,56 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void GenerateMap()
{
generating = true;
onGenerate(null, null);
var currentGeneration = Interlocked.Increment(ref generationCounter);
failed = false;
onGenerate(null, null);
preview.Clear();
Task.Run(() =>
{
// Tasks don't run in parallel, so we may be able to cancel some outdated requests here.
if (currentGeneration != generationCounter)
return;
MapGenerationArgs args;
Map map;
try
{
var args = settings.Compile(selectedTerrain, size);
var map = generator.Generate(modData, args);
// Map UID and preview image are generated on save
map.Save(package);
args.Uid = map.Uid;
Game.RunAfterTick(() =>
{
preview.Update(map);
onGenerate(args, package);
generating = false;
});
args = settings.Compile(selectedTerrain, size);
map = generator.Generate(modData, args);
}
catch (MapGenerationException)
{
failed = true;
generating = false;
// We are the lastest generation request, mark as failed.
if (currentGeneration == generationCounter)
{
lastGeneration = currentGeneration;
failed = true;
}
return;
}
// Need to invoke widgets from the main thread.
Game.RunAfterTick(() =>
{
// A newer generation will be set after us, discard.
if (currentGeneration == generationCounter)
{
var package = new ZipFileLoader.ReadWriteZipFile();
map.Save(package);
args.Uid = map.Uid;
preview.Update(map);
lastGeneration = currentGeneration;
// `onGenerate` assumed to take ownership of package here.
onGenerate(args, package);
}
});
});
}
bool disposed;
protected override void Dispose(bool disposing)
{
if (disposing && !disposed)
{
disposed = true;
package.Dispose();
}
base.Dispose(disposing);
}
}
}