Bundle loader refactor (#502)

* Bundle loader refactor

- Made async
- Validate if bundle actually exists, if not throw warning into the console
- Updated mod example

* Cleanup unused var
This commit is contained in:
Jesse
2025-07-22 13:54:06 +02:00
committed by GitHub
parent 7d8c3d041e
commit c852debf2b
4 changed files with 81 additions and 70 deletions
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SPTarkov.Server.Core.Models.Spt.Mod;
namespace SPTarkov.Server.Core.Extensions
{
public static class SptModExtensions
{
public static string GetModPath(this SptMod sptMod)
{
var relativeModPath = Path.GetRelativePath(
Directory.GetCurrentDirectory(),
sptMod.Directory
)
.Replace('\\', '/');
return relativeModPath;
}
}
}
@@ -1,9 +1,10 @@
using System.Text.Json.Serialization;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Spt.Mod;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Loaders;
@@ -18,40 +19,66 @@ namespace SPTarkov.Server.Core.Loaders;
"Crc" : 1030040371,
"Dependencies" : [ ]
} */
public class BundleInfo
public class BundleInfo(string modPath, BundleManifestEntry bundle, uint bundleHash)
{
public BundleInfo() { }
public string ModPath { get; private set; } = modPath;
public BundleInfo(string modPath, BundleManifestEntry bundle, uint bundleHash)
{
ModPath = modPath;
FileName = bundle.Key;
Bundle = bundle;
Crc = bundleHash;
Dependencies = bundle?.DependencyKeys ?? [];
}
public string FileName { get; private set; } = bundle.Key;
public string? ModPath { get; set; }
public BundleManifestEntry Bundle { get; private set; } = bundle;
public string FileName { get; set; }
public uint Crc { get; private set; } = bundleHash;
public BundleManifestEntry Bundle { get; set; }
public uint Crc { get; set; }
public List<string> Dependencies { get; set; }
public List<string> Dependencies { get; private set; } = bundle?.DependencyKeys ?? [];
}
[Injectable(InjectionType.Singleton)]
public class BundleLoader(
ISptLogger<BundleLoader> logger,
JsonUtil jsonUtil,
FileUtil fileUtil,
BundleHashCacheService bundleHashCacheService,
ICloner cloner
BundleHashCacheService bundleHashCacheService
)
{
private readonly Dictionary<string, BundleInfo> _bundles = new();
private readonly Dictionary<string, BundleInfo> _bundles = [];
public async Task LoadBundlesAsync(SptMod mod)
{
var modPath = mod.GetModPath();
var modBundles = await jsonUtil.DeserializeFromFileAsync<BundleManifest>(
Path.Join(Directory.GetCurrentDirectory(), modPath, "bundles.json")
);
var bundleManifests = modBundles?.Manifest ?? [];
foreach (var bundleManifest in bundleManifests)
{
var relativeModPath = modPath.Replace('\\', '/');
var bundleLocalPath = Path.Join(relativeModPath, "bundles", bundleManifest.Key)
.Replace('\\', '/');
if (!File.Exists(bundleLocalPath))
{
logger.Warning(
$"Could not find bundle {bundleManifest.Key} for mod {mod.ModMetadata.Name}"
);
continue;
}
if (!bundleHashCacheService.CalculateAndMatchHash(bundleLocalPath))
{
await bundleHashCacheService.CalculateAndStoreHash(bundleLocalPath);
}
var bundleHash = bundleHashCacheService.GetStoredValue(bundleLocalPath);
AddBundle(
bundleManifest.Key,
new BundleInfo(relativeModPath, bundleManifest, bundleHash)
);
}
}
/// <summary>
/// Handle singleplayer/bundles
@@ -71,39 +98,7 @@ public class BundleLoader(
public BundleInfo? GetBundle(string bundleKey)
{
return cloner.Clone(_bundles.GetValueOrDefault(bundleKey));
}
public void AddBundles(string modPath)
{
// modPath should be relative to the server exe - ./user/mods/Mod3
// TODO: make sure the mod is passing a path that is relative from the server exe
var modBundlesJson = fileUtil.ReadFile(
Path.Join(Directory.GetCurrentDirectory(), modPath, "bundles.json")
);
var modBundles = jsonUtil.Deserialize<BundleManifest>(modBundlesJson);
var bundleManifestArr = modBundles?.Manifest;
foreach (var bundleManifest in bundleManifestArr)
{
var relativeModPath = modPath.Replace('\\', '/');
var bundleLocalPath = Path.Join(relativeModPath, "bundles", bundleManifest.Key)
.Replace('\\', '/');
if (!bundleHashCacheService.CalculateAndMatchHash(bundleLocalPath))
{
bundleHashCacheService.CalculateAndStoreHash(bundleLocalPath);
}
var bundleHash = bundleHashCacheService.GetStoredValue(bundleLocalPath);
AddBundle(
bundleManifest.Key,
new BundleInfo(relativeModPath, bundleManifest, bundleHash)
);
}
return _bundles.GetValueOrDefault(bundleKey);
}
public void AddBundle(string key, BundleInfo bundle)
@@ -119,13 +114,13 @@ public class BundleLoader(
public record BundleManifest
{
[JsonPropertyName("manifest")]
public List<BundleManifestEntry> Manifest { get; set; }
public List<BundleManifestEntry>? Manifest { get; set; }
}
public record BundleManifestEntry
{
[JsonPropertyName("key")]
public string Key { get; set; }
public required string Key { get; set; }
[JsonPropertyName("dependencyKeys")]
public List<string>? DependencyKeys { get; set; }
@@ -14,7 +14,7 @@ public class BundleHashCacheService(
{
protected const string _bundleHashCachePath = "./user/cache/";
protected const string _cacheName = "bundleHashCache.json";
protected readonly Dictionary<string, uint> _bundleHashes = new();
protected readonly Dictionary<string, uint> _bundleHashes = [];
public uint GetStoredValue(string key)
{
@@ -26,7 +26,7 @@ public class BundleHashCacheService(
return value;
}
public void StoreValue(string bundlePath, uint hash)
public async Task StoreValue(string bundlePath, uint hash)
{
_bundleHashes.Add(bundlePath, hash);
@@ -35,7 +35,7 @@ public class BundleHashCacheService(
Directory.CreateDirectory(_bundleHashCachePath);
}
fileUtil.WriteFile(
await fileUtil.WriteFileAsync(
Path.Join(_bundleHashCachePath, _cacheName),
jsonUtil.Serialize(_bundleHashes)
);
@@ -48,9 +48,9 @@ public class BundleHashCacheService(
return MatchWithStoredHash(BundlePath, CalculateHash(BundlePath));
}
public void CalculateAndStoreHash(string BundlePath)
public async Task CalculateAndStoreHash(string BundlePath)
{
StoreValue(BundlePath, CalculateHash(BundlePath));
await StoreValue(BundlePath, CalculateHash(BundlePath));
}
public uint CalculateHash(string BundlePath)
@@ -19,14 +19,7 @@ public class SptServerBackgroundService(
{
if (mod.ModMetadata?.IsBundleMod == true)
{
// Convert to relative path
var relativeModPath = Path.GetRelativePath(
Directory.GetCurrentDirectory(),
mod.Directory
)
.Replace('\\', '/');
bundleLoader.AddBundles(relativeModPath);
await bundleLoader.LoadBundlesAsync(mod);
}
}
}