Skip to content

Commit

Permalink
Refactor SteamWorks code and add some async/awaitability to fix perfo…
Browse files Browse the repository at this point in the history
…rmance, "close on exit" setting and UI freezes

fixes X2CommunityCore#317
fixes X2CommunityCore#289
fixes X2CommunityCore#268
  • Loading branch information
remcoros committed Jul 31, 2023
1 parent d23f80a commit a6c77ea
Show file tree
Hide file tree
Showing 16 changed files with 274 additions and 408 deletions.
69 changes: 42 additions & 27 deletions xcom2-launcher/xcom2-launcher/Classes/Mod/ModList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ private void UpdateModsConflictState(IEnumerable<ModConflict> activeConflicts)
/// </summary>
/// <param name="mod"></param>
/// <returns></returns>
public void UpdatedModDependencyState(ModEntry mod)
public async Task UpdatedModDependencyStateAsync(ModEntry mod)
{
var requiredMods = GetRequiredMods(mod, true, true);
var requiredMods = await GetRequiredModsAsync(mod, true, true).ConfigureAwait(false);
var allRequiredModsAvailable = requiredMods.All(m => m.WorkshopID != 0 && m.isActive && !m.State.HasFlag(ModState.NotInstalled) && !m.State.HasFlag(ModState.NotLoaded));

if (allRequiredModsAvailable)
Expand Down Expand Up @@ -314,10 +314,13 @@ public async Task<List<ModEntry>> UpdateModAsync(ModEntry m, Settings settings)
steamModsCopy = steamModsCopy.Skip(Workshop.MAX_UGC_RESULTS).ToList();

Log.Debug($"Creating SteamUGCDetails_t batch request for {batchQueryModList.Count} mods.");

getDetailsTasks.Add(Task.Run(() =>

getDetailsTasks.Add(GetDetailsTask());
continue;

async Task<List<SteamUGCDetails_t>> GetDetailsTask()
{
var details = Workshop.GetDetails(batchQueryModList.ConvertAll(mod => (ulong) mod.WorkshopID), true);
var details = await Workshop.GetDetailsAsync(batchQueryModList.ConvertAll(mod => (ulong)mod.WorkshopID), true).ConfigureAwait(false);

if (details == null)
{
Expand All @@ -336,7 +339,10 @@ public async Task<List<ModEntry>> UpdateModAsync(ModEntry m, Settings settings)
return null;
}

updateTasks.Add(Task.Run(() =>
updateTasks.Add(UpdateTask());
continue;

async Task UpdateTask()
{
// A requested workshop detail may match more than one mod (having the same mod installed from Steam and locally for example).
var matchingMods = batchQueryModList.FindAll(mod => (ulong)mod.WorkshopID == workshopDetails.m_nPublishedFileId.m_PublishedFileId);
Expand All @@ -350,15 +356,12 @@ public async Task<List<ModEntry>> UpdateModAsync(ModEntry m, Settings settings)
return;
}

lock (_ModUpdateLock)
{
progress?.Report(new ModUpdateProgress($"Updating mods {steamProgress}/{totalModCount}...", steamProgress, totalModCount));
Interlocked.Increment(ref steamProgress);
}
progress?.Report(new ModUpdateProgress($"Updating mods {steamProgress}/{totalModCount}...", steamProgress, totalModCount));
Interlocked.Increment(ref steamProgress);

try
{
UpdateSteamMod(m, workshopDetails);
await UpdateSteamModAsync(m, workshopDetails).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand All @@ -367,14 +370,13 @@ public async Task<List<ModEntry>> UpdateModAsync(ModEntry m, Settings settings)
throw;
}
}

}, cancelToken));
}
}

try
{
Log.Debug($"Waiting for {updateTasks.Count} UpdateSteamMod tasks to complete.");
Task.WaitAll(updateTasks.ToArray(), cancelToken);
await Task.WhenAny(Task.WhenAll(updateTasks), Task.Delay(Timeout.Infinite, cancelToken)).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Expand All @@ -384,11 +386,11 @@ public async Task<List<ModEntry>> UpdateModAsync(ModEntry m, Settings settings)

Log.Debug("UpdateSteamMod tasks completed.");
return details;
}, cancelToken));
}
}

Log.Debug($"Waiting for {getDetailsTasks.Count} GetDetails tasks to complete.");
await Task.WhenAll(getDetailsTasks);
await Task.WhenAll(getDetailsTasks).ConfigureAwait(false);
Log.Debug("GetDetails tasks completed.");

var totalProgress = steamProgress;
Expand Down Expand Up @@ -481,7 +483,7 @@ void UpdateLocalMod(ModEntry m)
// Update directory size
// slow, but necessary ?
m.RealizeSize(Directory.EnumerateFiles(m.Path, "*", SearchOption.AllDirectories).Sum(fileName => new FileInfo(fileName).Length));

// Update Name and Description
// look for .XComMod file
try
Expand All @@ -503,7 +505,7 @@ void UpdateLocalMod(ModEntry m)

}

void UpdateSteamMod(ModEntry m, SteamUGCDetails_t workshopDetails)
async Task UpdateSteamModAsync(ModEntry m, SteamUGCDetails_t workshopDetails)
{
if (m == null || m.WorkshopID <= 0)
{
Expand Down Expand Up @@ -558,12 +560,12 @@ void UpdateSteamMod(ModEntry m, SteamUGCDetails_t workshopDetails)
// If the mod has dependencies, request the workshop id's of those mods.
if (workshopDetails.m_unNumChildren > 0)
{
var dependencies = Workshop.GetDependencies(workshopDetails);

var dependencies = await Workshop.GetDependenciesAsync(workshopDetails).ConfigureAwait(false);
if (dependencies != null)
{
m.Dependencies.Clear();
m.Dependencies.AddRange(dependencies.ConvertAll(val => (long) val));
m.Dependencies.AddRange(dependencies.Select(x => (long)x));
}
else
{
Expand All @@ -578,6 +580,8 @@ void UpdateSteamMod(ModEntry m, SteamUGCDetails_t workshopDetails)
m.AddState(ModState.UpdateAvailable);
}

await UpdatedModDependencyStateAsync(m).ConfigureAwait(false);

// Check if it is built for WOTC
try
{
Expand All @@ -590,7 +594,6 @@ void UpdateSteamMod(ModEntry m, SteamUGCDetails_t workshopDetails)
Log.Error("Failed parsing XComMod file for " + m.ID, ex);
Debug.Fail(ex.Message);
}

}

public string GetCategory(ModEntry mod)
Expand All @@ -604,10 +607,22 @@ public string GetCategory(ModEntry mod)
/// <param name="mod"></param>
/// <param name="compareModId">If set to false, dependencies are checked against the workshop ID. Otherwise, the mod ID is used which also matches for duplicates.</param>
/// <returns></returns>
public List<ModEntry> GetDependentMods(ModEntry mod, bool compareModId = true)
public async Task<List<ModEntry>> GetDependentModsAsync(ModEntry mod, bool compareModId = true)
{
var result = new List<ModEntry>();
if (compareModId)
return All.Where(m => GetRequiredMods(m).Select(requiredMod => requiredMod.ID).Contains(mod.ID)).ToList();
{
foreach (var modEntry in All)
{
var requiredMods = await GetRequiredModsAsync(modEntry).ConfigureAwait(false);
if (requiredMods.Any(x => x.ID == modEntry.ID))
{
result.Add(modEntry);
}
}

return result;
}

return All.Where(m => m.Dependencies.Contains(mod.WorkshopID)).ToList();
}
Expand All @@ -619,7 +634,7 @@ public List<ModEntry> GetDependentMods(ModEntry mod, bool compareModId = true)
/// <param name="substituteDuplicates">If set to true, the primary duplicate will be returned if the real dependency is a disabled duplicate.</param>
/// <param name="checkIgnoredDependencies">If set to true, dependencies that have been set to be ignored are not returned.</param>
/// <returns></returns>
public List<ModEntry> GetRequiredMods(ModEntry mod, bool substituteDuplicates = true, bool checkIgnoredDependencies = false)
public async Task<List<ModEntry>> GetRequiredModsAsync(ModEntry mod, bool substituteDuplicates = true, bool checkIgnoredDependencies = false)
{
List<ModEntry> requiredMods = new List<ModEntry>();
var installedSteamMods = All.Where(m => m.WorkshopID != 0).ToList();
Expand Down Expand Up @@ -663,7 +678,7 @@ public List<ModEntry> GetRequiredMods(ModEntry mod, bool substituteDuplicates =
}
else
{
var details = Workshop.GetDetails((ulong)id);
var details = await Workshop.GetDetailsAsync((ulong)id).ConfigureAwait(false);

if (details.m_eResult == EResult.k_EResultOK)
{
Expand Down
24 changes: 0 additions & 24 deletions xcom2-launcher/xcom2-launcher/Classes/Steam/DownloadItemRequest.cs

This file was deleted.

109 changes: 0 additions & 109 deletions xcom2-launcher/xcom2-launcher/Classes/Steam/ItemDetailsRequest.cs

This file was deleted.

Loading

0 comments on commit a6c77ea

Please sign in to comment.