Skip to content

Commit

Permalink
Make decompress queued
Browse files Browse the repository at this point in the history
  • Loading branch information
DiFFoZ committed Apr 4, 2024
1 parent aad0fd1 commit 88b13e3
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 20 deletions.
30 changes: 28 additions & 2 deletions BepInExFasterLoadAssetBundles/Helpers/AsyncHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using static BepInExFasterLoadAssetBundles.Helpers.AsyncOperationHelper;

namespace BepInExFasterLoadAssetBundles.Helpers;
internal static class AsyncHelper
Expand Down Expand Up @@ -33,14 +32,14 @@ public static void Schedule(Func<Task> func)
}

public static SwitchToMainThreadAwaiter SwitchToMainThread() => new();
public static SwitchToThreadPoolAwaiter SwitchToThreadPool() => new();

public readonly struct SwitchToMainThreadAwaiter : ICriticalNotifyCompletion
{
private static readonly SendOrPostCallback s_OnPostAction = OnPost;

public readonly SwitchToMainThreadAwaiter GetAwaiter() => this;
public readonly bool IsCompleted => Thread.CurrentThread.ManagedThreadId == s_MainThreadId;

public void GetResult()
{ }

Expand All @@ -60,4 +59,31 @@ private static void OnPost(object state)
action?.Invoke();
}
}

public readonly struct SwitchToThreadPoolAwaiter : ICriticalNotifyCompletion
{
private static readonly WaitCallback s_OnPostAction = OnPost;

public readonly SwitchToThreadPoolAwaiter GetAwaiter() => this;
public readonly bool IsCompleted => false;

public void GetResult()
{ }

public readonly void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(s_OnPostAction, continuation);
}

public readonly void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(s_OnPostAction, continuation);
}

private static void OnPost(object state)
{
var action = state as Action;
action?.Invoke();
}
}
}
9 changes: 0 additions & 9 deletions BepInExFasterLoadAssetBundles/Helpers/AsyncOperationHelper.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;

namespace BepInExFasterLoadAssetBundles.Helpers;
internal static class AsyncOperationHelper
{
public static void WaitUntilOperationComplete<T>(T op) where T : AsyncOperation
{
while (!op.isDone)
{
Thread.Sleep(100);
}
}

public static AsyncOperationAwaiter WaitCompletionAsync<T>(this T op) where T : AsyncOperation
{
return new AsyncOperationAwaiter(op);
Expand Down
79 changes: 70 additions & 9 deletions BepInExFasterLoadAssetBundles/Managers/AssetBundleManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BepInExFasterLoadAssetBundles.Helpers;
using BepInExFasterLoadAssetBundles.Models;
Expand All @@ -10,6 +9,10 @@
namespace BepInExFasterLoadAssetBundles.Managers;
internal class AssetBundleManager
{
private readonly ConcurrentQueue<WorkAsset> m_WorkAssets = new();
private readonly object m_Lock = new();
private bool m_IsProcessingQueue;

public string CachePath { get; }

public AssetBundleManager(string cachePath)
Expand All @@ -28,17 +31,24 @@ public AssetBundleManager(string cachePath)
private void DeleteTempFiles()
{
// unity creates tmp files when decompress
var count = 0;
try
{
foreach (var tempFile in Directory.EnumerateFiles(CachePath, "*.tmp"))
{
File.Delete(tempFile);
count++;
}
}
catch (Exception ex)
{
Patcher.Logger.LogError($"Failed to delete temp files\n{ex}");
}

if (count > 0)
{
Patcher.Logger.LogWarning($"Deleted {count} temp files");
}
}

public bool TryRecompressAssetBundle(ref string path)
Expand Down Expand Up @@ -84,8 +94,8 @@ public bool TryRecompressAssetBundleInternal(ref string path, byte[] hash)

if (DriveHelper.HasDriveSpaceOnPath(CachePath, 10))
{
var nonRefPath = path;
AsyncHelper.Schedule(() => DecompressAssetBundleAsync(nonRefPath, hash));
m_WorkAssets.Enqueue(new(path, hash));
StartRunner();
}
else
{
Expand All @@ -104,6 +114,47 @@ public void DeleteCachedAssetBundle(string path)
}
}

private void StartRunner()
{
if (m_IsProcessingQueue)
{
return;
}

lock (m_Lock)
{
if (m_IsProcessingQueue)
{
return;
}

m_IsProcessingQueue = true;
}

AsyncHelper.Schedule(ProcessQueue);
}

private async Task ProcessQueue()
{
try
{
while (m_WorkAssets.TryDequeue(out var work))
{
await DecompressAssetBundleAsync(work.Path, work.Hash);
}
}
finally
{
lock (m_Lock)
{
if (m_IsProcessingQueue)
{
m_IsProcessingQueue = false;
}
}
}
}

private async Task DecompressAssetBundleAsync(string path, byte[] hash)
{
var metadata = new Metadata()
Expand All @@ -117,22 +168,20 @@ private async Task DecompressAssetBundleAsync(string path, byte[] hash)

// when loading assetbundle async via stream, the file can be still in use. Wait a bit for that
await FileHelper.RetryUntilFileIsClosedAsync(path, 5);

await AsyncHelper.SwitchToMainThread();

var op = AssetBundle.RecompressAssetBundleAsync(path, outputPath,
BuildCompression.UncompressedRuntime, 0, ThreadPriority.Normal);
BuildCompression.UncompressedRuntime, 0, UnityEngine.ThreadPriority.Normal);

await op.WaitCompletionAsync();
await AsyncHelper.SwitchToThreadPool();

if (op.result is not AssetBundleLoadResult.Success)
if (op.result is not AssetBundleLoadResult.Success || !op.success)
{
Patcher.Logger.LogWarning($"Failed to decompress a assetbundle at \"{path}\"\n{op.humanReadableResult}");
Patcher.Logger.LogWarning($"Failed to decompress a assetbundle at \"{path}\"\nResult: {op.result}, {op.humanReadableResult}");
return;
}

await Task.Yield();

// check if unity returned the same assetbundle (means that assetbundle is already decompressed)
if (hash.AsSpan().SequenceEqual(HashingHelper.HashFile(outputPath)))
{
Expand All @@ -148,4 +197,16 @@ private async Task DecompressAssetBundleAsync(string path, byte[] hash)
metadata.UncompressedAssetBundleName = outputName;
Patcher.MetadataManager.SaveMetadata(metadata);
}

private struct WorkAsset
{
public WorkAsset(string path, byte[] hash)
{
Path = path;
Hash = hash;
}

public string Path { get; set; }
public byte[] Hash { get; set; }
}
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] 2024-04-04
### Added
- Check of drive space before trying to decompress.
### Changed
- Moved cache folder to the game installation.
- The old cache folder will be deleted.
- Switching to main thread when decompress the bundle.
- Loading of uncompressed bundle to make them load faster.
### Fixed
- Exception that happens if mod trying to load non exists bundle.

## [0.3.1] 2024-03-28
### Fixed
- Exception that prevents to decompress bundle.

## [0.3.0] 2024-03-28
### Changed
- Cache folder is now global (`%userprofile%\AppData\LocalLow\<companyname>\<productname>`).
Expand Down

0 comments on commit 88b13e3

Please sign in to comment.