Load replays asynchronously and in parallel.
This prevents the UI blocking, and also speeds up loading time for getting all the replays displayed.
This commit is contained in:
@@ -9,9 +9,12 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
using OpenRA.Mods.Common.Widgets;
|
using OpenRA.Mods.Common.Widgets;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
@@ -23,16 +26,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
static Filter filter = new Filter();
|
static Filter filter = new Filter();
|
||||||
|
|
||||||
Widget panel;
|
readonly Widget panel;
|
||||||
ScrollPanelWidget replayList, playerList;
|
readonly ScrollPanelWidget replayList, playerList;
|
||||||
ScrollItemWidget playerTemplate, playerHeader;
|
readonly ScrollItemWidget playerTemplate, playerHeader;
|
||||||
List<ReplayMetadata> replays;
|
readonly List<ReplayMetadata> replays = new List<ReplayMetadata>();
|
||||||
Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
|
readonly Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
|
||||||
|
readonly Action onStart;
|
||||||
|
|
||||||
Dictionary<CPos, SpawnOccupant> selectedSpawns;
|
Dictionary<CPos, SpawnOccupant> selectedSpawns;
|
||||||
ReplayMetadata selectedReplay;
|
ReplayMetadata selectedReplay;
|
||||||
|
|
||||||
Action onStart;
|
volatile bool cancelLoadingReplays;
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
public ReplayBrowserLogic(Widget widget, Action onExit, Action onStart)
|
public ReplayBrowserLogic(Widget widget, Action onExit, Action onStart)
|
||||||
@@ -46,7 +50,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
playerTemplate = playerList.Get<ScrollItemWidget>("TEMPLATE");
|
playerTemplate = playerList.Get<ScrollItemWidget>("TEMPLATE");
|
||||||
playerList.RemoveChildren();
|
playerList.RemoveChildren();
|
||||||
|
|
||||||
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
|
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { cancelLoadingReplays = true; Ui.CloseWindow(); onExit(); };
|
||||||
|
|
||||||
replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
|
replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
|
||||||
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
|
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
|
||||||
@@ -54,26 +58,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var mod = Game.ModData.Manifest.Mod;
|
var mod = Game.ModData.Manifest.Mod;
|
||||||
var dir = Platform.ResolvePath("^", "Replays", mod.Id, mod.Version);
|
var dir = Platform.ResolvePath("^", "Replays", mod.Id, mod.Version);
|
||||||
|
|
||||||
replayList.RemoveChildren();
|
|
||||||
if (Directory.Exists(dir))
|
if (Directory.Exists(dir))
|
||||||
{
|
ThreadPool.QueueUserWorkItem(_ => LoadReplays(dir, template));
|
||||||
using (new Support.PerfTimer("Load replays"))
|
|
||||||
{
|
|
||||||
replays = Directory
|
|
||||||
.GetFiles(dir, "*.orarep")
|
|
||||||
.Select(ReplayMetadata.Read)
|
|
||||||
.Where(r => r != null)
|
|
||||||
.OrderByDescending(r => r.GameInfo.StartTimeUtc)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var replay in replays)
|
|
||||||
AddReplay(replay, template);
|
|
||||||
|
|
||||||
ApplyFilter();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
replays = new List<ReplayMetadata>();
|
|
||||||
|
|
||||||
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
|
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
|
||||||
watch.IsDisabled = () => selectedReplay == null || selectedReplay.GameInfo.MapPreview.Status != MapStatus.Available;
|
watch.IsDisabled = () => selectedReplay == null || selectedReplay.GameInfo.MapPreview.Status != MapStatus.Available;
|
||||||
@@ -99,6 +85,40 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
SetupManagement();
|
SetupManagement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadReplays(string dir, ScrollItemWidget template)
|
||||||
|
{
|
||||||
|
using (new Support.PerfTimer("Load replays"))
|
||||||
|
{
|
||||||
|
var loadedReplays = new ConcurrentBag<ReplayMetadata>();
|
||||||
|
Parallel.ForEach(Directory.GetFiles(dir, "*.orarep"), (fileName, pls) =>
|
||||||
|
{
|
||||||
|
if (cancelLoadingReplays)
|
||||||
|
{
|
||||||
|
pls.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var replay = ReplayMetadata.Read(fileName);
|
||||||
|
if (replay != null)
|
||||||
|
loadedReplays.Add(replay);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cancelLoadingReplays)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sortedReplays = loadedReplays.OrderByDescending(replay => replay.GameInfo.StartTimeUtc).ToList();
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
|
{
|
||||||
|
replayList.RemoveChildren();
|
||||||
|
foreach (var replay in sortedReplays)
|
||||||
|
AddReplay(replay, template);
|
||||||
|
|
||||||
|
SetupReplayDependentFilters();
|
||||||
|
ApplyFilter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SetupFilters()
|
void SetupFilters()
|
||||||
{
|
{
|
||||||
// Game type
|
// Game type
|
||||||
@@ -202,6 +222,50 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outcome (depends on Player)
|
||||||
|
{
|
||||||
|
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
|
||||||
|
if (ddb != null)
|
||||||
|
{
|
||||||
|
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
|
||||||
|
|
||||||
|
// Using list to maintain the order
|
||||||
|
var options = new List<Pair<WinState, string>>
|
||||||
|
{
|
||||||
|
Pair.New(WinState.Undefined, ddb.GetText()),
|
||||||
|
Pair.New(WinState.Lost, "Defeat"),
|
||||||
|
Pair.New(WinState.Won, "Victory")
|
||||||
|
};
|
||||||
|
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
||||||
|
|
||||||
|
ddb.GetText = () => lookup[filter.Outcome];
|
||||||
|
ddb.OnMouseDown = _ =>
|
||||||
|
{
|
||||||
|
Func<Pair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||||
|
{
|
||||||
|
var item = ScrollItemWidget.Setup(
|
||||||
|
tpl,
|
||||||
|
() => filter.Outcome == option.First,
|
||||||
|
() => { filter.Outcome = option.First; ApplyFilter(); });
|
||||||
|
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset button
|
||||||
|
{
|
||||||
|
var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON");
|
||||||
|
button.IsDisabled = () => filter.IsEmpty;
|
||||||
|
button.OnClick = () => { filter = new Filter(); ApplyFilter(); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupReplayDependentFilters()
|
||||||
|
{
|
||||||
// Map
|
// Map
|
||||||
{
|
{
|
||||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON");
|
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON");
|
||||||
@@ -258,40 +322,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outcome (depends on Player)
|
|
||||||
{
|
|
||||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
|
|
||||||
if (ddb != null)
|
|
||||||
{
|
|
||||||
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
|
|
||||||
|
|
||||||
// Using list to maintain the order
|
|
||||||
var options = new List<Pair<WinState, string>>
|
|
||||||
{
|
|
||||||
Pair.New(WinState.Undefined, ddb.GetText()),
|
|
||||||
Pair.New(WinState.Lost, "Defeat"),
|
|
||||||
Pair.New(WinState.Won, "Victory")
|
|
||||||
};
|
|
||||||
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
|
||||||
|
|
||||||
ddb.GetText = () => lookup[filter.Outcome];
|
|
||||||
ddb.OnMouseDown = _ =>
|
|
||||||
{
|
|
||||||
Func<Pair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
|
||||||
{
|
|
||||||
var item = ScrollItemWidget.Setup(
|
|
||||||
tpl,
|
|
||||||
() => filter.Outcome == option.First,
|
|
||||||
() => { filter.Outcome = option.First; ApplyFilter(); });
|
|
||||||
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
|
||||||
return item;
|
|
||||||
};
|
|
||||||
|
|
||||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faction (depends on Player)
|
// Faction (depends on Player)
|
||||||
{
|
{
|
||||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
|
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
|
||||||
@@ -323,13 +353,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset button
|
|
||||||
{
|
|
||||||
var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON");
|
|
||||||
button.IsDisabled = () => filter.IsEmpty;
|
|
||||||
button.OnClick = () => { filter = new Filter(); ApplyFilter(); };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupManagement()
|
void SetupManagement()
|
||||||
@@ -632,6 +655,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
Action startReplay = () =>
|
Action startReplay = () =>
|
||||||
{
|
{
|
||||||
|
cancelLoadingReplays = true;
|
||||||
Game.JoinReplay(selectedReplay.FilePath);
|
Game.JoinReplay(selectedReplay.FilePath);
|
||||||
Ui.CloseWindow();
|
Ui.CloseWindow();
|
||||||
onStart();
|
onStart();
|
||||||
@@ -643,6 +667,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
|
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
|
||||||
{
|
{
|
||||||
|
replays.Add(replay);
|
||||||
|
|
||||||
var item = ScrollItemWidget.Setup(template,
|
var item = ScrollItemWidget.Setup(template,
|
||||||
() => selectedReplay == replay,
|
() => selectedReplay == replay,
|
||||||
() => SelectReplay(replay),
|
() => SelectReplay(replay),
|
||||||
|
|||||||
Reference in New Issue
Block a user