Merge pull request #397 from sp-tarkov/lazyload-transformer-handling
Add Transformer to Lazyload, get rid of event
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services.Cache;
|
||||
|
||||
[Injectable]
|
||||
public class BundleHashCacheService(
|
||||
ISptLogger<BundleHashCacheService> _logger,
|
||||
HashUtil _hashUtil,
|
||||
JsonUtil _jsonUtil,
|
||||
FileUtil _fileUtil
|
||||
)
|
||||
{
|
||||
protected static readonly string _bundleHashCachePath = "./user/cache/bundleHashCache.json";
|
||||
protected Dictionary<string, string> _bundleHashes = new();
|
||||
|
||||
public string GetStoredValue(string key)
|
||||
{
|
||||
_bundleHashes.TryGetValue(key, out var value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void StoreValue(string key, string value)
|
||||
{
|
||||
_bundleHashes.Add(key, value);
|
||||
|
||||
_fileUtil.WriteFile(_bundleHashCachePath, _jsonUtil.Serialize(_bundleHashes));
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Bundle {key} hash stored in {_bundleHashCachePath}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchWithStoredHash(string bundlePath, string hash)
|
||||
{
|
||||
return GetStoredValue(bundlePath) == hash;
|
||||
}
|
||||
|
||||
public bool CalculateAndMatchHash(string bundlePath)
|
||||
{
|
||||
var fileContents = _fileUtil.ReadFile(bundlePath);
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
|
||||
|
||||
return MatchWithStoredHash(bundlePath, generatedHash);
|
||||
}
|
||||
|
||||
public void CalculateAndStoreHash(string bundlePath)
|
||||
{
|
||||
var fileContents = _fileUtil.ReadFile(bundlePath);
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
|
||||
|
||||
StoreValue(bundlePath, generatedHash);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services.Cache;
|
||||
|
||||
[Injectable]
|
||||
public class ModHashCacheService(
|
||||
ISptLogger<ModHashCacheService> _logger,
|
||||
JsonUtil _jsonUtil,
|
||||
HashUtil _hashUtil,
|
||||
FileUtil _fileUtil
|
||||
)
|
||||
{
|
||||
protected readonly string _modCachePath = "./user/cache/modCache.json";
|
||||
protected readonly Dictionary<string, string> _modHashes = new();
|
||||
|
||||
public string? GetStoredValue(string key)
|
||||
{
|
||||
_modHashes.TryGetValue(key, out var value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void StoreValue(string key, string value)
|
||||
{
|
||||
_modHashes.TryAdd(key, value);
|
||||
|
||||
_fileUtil.WriteFile(_modCachePath, _jsonUtil.Serialize(_modHashes));
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Mod {key} hash stored in: {_modCachePath}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchWithStoredHash(string modName, string hash)
|
||||
{
|
||||
return GetStoredValue(modName) == hash;
|
||||
}
|
||||
|
||||
public bool CalculateAndCompareHash(string modName, string modContent)
|
||||
{
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
|
||||
|
||||
return MatchWithStoredHash(modName, generatedHash);
|
||||
}
|
||||
|
||||
public void CalculateAndStoreHash(string modName, string modContent)
|
||||
{
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
|
||||
|
||||
StoreValue(modName, generatedHash);
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,10 @@ public class LocaleService(
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
// we have to LazyLoad the data from the database and then combine it with the custom data before returning it
|
||||
protected readonly LocaleConfig _localeConfig = _configServer.GetConfig<LocaleConfig>();
|
||||
protected readonly Dictionary<string, Dictionary<string, string>> customClientLocales = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get the eft globals db file based on the configured locale in config/locale.json, if not found, fall back to 'en'
|
||||
/// This will contain Custom locales added by mods
|
||||
/// </summary>
|
||||
/// <returns> Dictionary of locales for desired language - en/fr/cn </returns>
|
||||
public Dictionary<string, string> GetLocaleDb(string? language = null)
|
||||
@@ -29,23 +26,21 @@ public class LocaleService(
|
||||
: language;
|
||||
|
||||
// if it can't get locales for language provided, default to en
|
||||
if (TryGetLocaleDbWithCustomLocales(languageToUse, out var localeToReturn) ||
|
||||
TryGetLocaleDbWithCustomLocales("en", out localeToReturn))
|
||||
if (TryGetLocaleDb(languageToUse, out var localeToReturn) || TryGetLocaleDb("en", out localeToReturn))
|
||||
{
|
||||
// TODO: need to see if this needs to be cloned
|
||||
return RemovePraporTestMessage(localeToReturn);
|
||||
return localeToReturn;
|
||||
}
|
||||
|
||||
throw new Exception($"unable to get locales from either {languageToUse} or en");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the locale database for the specified language key, including custom locales if available.
|
||||
/// Attempts to retrieve the locale database for the specified language key
|
||||
/// </summary>
|
||||
/// <param name="languageKey">The language key for which the locale database should be retrieved.</param>
|
||||
/// <param name="localeToReturn">The resulting locale database as a dictionary, or null if the operation fails.</param>
|
||||
/// <returns>True if the locale database was successfully retrieved, otherwise false.</returns>
|
||||
protected bool TryGetLocaleDbWithCustomLocales(string languageKey, out Dictionary<string, string>? localeToReturn)
|
||||
protected bool TryGetLocaleDb(string languageKey, out Dictionary<string, string>? localeToReturn)
|
||||
{
|
||||
localeToReturn = null;
|
||||
if (!_databaseServer.GetTables().Locales.Global.TryGetValue(languageKey, out var keyedLocales))
|
||||
@@ -55,42 +50,9 @@ public class LocaleService(
|
||||
|
||||
localeToReturn = keyedLocales.Value;
|
||||
|
||||
if (customClientLocales.TryGetValue(languageKey, out var customClientLocale))
|
||||
{
|
||||
// there were custom locales for this language
|
||||
localeToReturn = CombineDbWithCustomLocales(localeToReturn, customClientLocale);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Combines the provided database locales with custom locales, ensuring that all entries are merged into a single dictionary.
|
||||
/// Custom locale entries will overwrite existing keys from the database locales if conflicts occur.
|
||||
/// </summary>
|
||||
/// <param name="dbLocales">The dictionary containing locale entries from the database.</param>
|
||||
/// <param name="customLocales">The dictionary containing custom locale entries to be merged.</param>
|
||||
/// <returns>A dictionary representing the merged result of database and custom locales.</returns>
|
||||
protected Dictionary<string, string> CombineDbWithCustomLocales(Dictionary<string, string> dbLocales, Dictionary<string, string> customLocales)
|
||||
{
|
||||
try
|
||||
{
|
||||
return dbLocales
|
||||
.Concat(customLocales)
|
||||
.GroupBy(kvp => kvp.Key)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.Last().Value
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game locale key from the locale.json file,
|
||||
/// if value is 'system' get system-configured locale
|
||||
@@ -239,33 +201,6 @@ public class LocaleService(
|
||||
return GetLocaleDb().Keys.Where(x => x.StartsWith(partialKey)).ToList();
|
||||
}
|
||||
|
||||
public void AddCustomClientLocale(string locale, string localeKey, string localeValue)
|
||||
{
|
||||
AddToDictionary(locale, localeKey, localeValue, customClientLocales);
|
||||
}
|
||||
|
||||
public void RemoveCustomClientLocale(string locale, string localeKey)
|
||||
{
|
||||
customClientLocales.Remove(localeKey);
|
||||
}
|
||||
|
||||
private void AddToDictionary(string locale, string localeKey, string localeValue,
|
||||
Dictionary<string, Dictionary<string, string>> dictionaryToAddTo)
|
||||
{
|
||||
dictionaryToAddTo.TryAdd(locale, new Dictionary<string, string>());
|
||||
if (!dictionaryToAddTo.TryGetValue(locale, out var localeDictToAddTo))
|
||||
{
|
||||
_logger.Error($"Unable to get custom locale dictionary keyed by: {locale}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localeDictToAddTo.TryAdd(localeKey, localeValue))
|
||||
{
|
||||
localeDictToAddTo[localeKey] = localeValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blank out the "test" mail message from prapor
|
||||
/// </summary>
|
||||
|
||||
@@ -243,9 +243,17 @@ public class CustomItemService(
|
||||
|
||||
newLocaleDetails ??= localeDetails[localeDetails.Keys.FirstOrDefault()];
|
||||
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Name", newLocaleDetails.Name);
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} ShortName", newLocaleDetails.ShortName);
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Description", newLocaleDetails.Description);
|
||||
if (databaseService.GetLocales().Global.TryGetValue(shortNameKey.Key, out var lazyLoad))
|
||||
{
|
||||
lazyLoad.AddTransformer(localeData =>
|
||||
{
|
||||
localeData.Add($"{newItemId} Name", newLocaleDetails.Name);
|
||||
localeData.Add($"{newItemId} ShortName", newLocaleDetails.ShortName);
|
||||
localeData.Add($"{newItemId} Description", newLocaleDetails.Description);
|
||||
|
||||
return localeData;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,8 @@ public class PostDbLoadService(
|
||||
|
||||
RemoveNewBeginningRequirementFromPrestige();
|
||||
|
||||
RemovePraporTestMessage();
|
||||
|
||||
ValidateQuestAssortUnlocksExist();
|
||||
|
||||
if (_seasonalEventService.IsAutomaticEventDetectionEnabled())
|
||||
@@ -191,6 +193,19 @@ public class PostDbLoadService(
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePraporTestMessage()
|
||||
{
|
||||
foreach((var locale, var lazyLoad) in _databaseService.GetLocales().Global)
|
||||
{
|
||||
lazyLoad.AddTransformer(lazyloadedData =>
|
||||
{
|
||||
lazyloadedData["61687e2c3e526901fa76baf9"] = "";
|
||||
|
||||
return lazyloadedData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void CloneExistingCraftsAndAddNew()
|
||||
{
|
||||
var hideoutCraftDb = _databaseService.GetHideout().Production;
|
||||
@@ -264,34 +279,38 @@ public class PostDbLoadService(
|
||||
continue;
|
||||
}
|
||||
|
||||
var mapLooseLoot = _databaseService.GetLocation(mapId).LooseLoot.Value;
|
||||
if (mapLooseLoot is null)
|
||||
_databaseService.GetLocation(mapId).LooseLoot.AddTransformer(looselootData =>
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var positionToAdd in positionsToAdd)
|
||||
{
|
||||
// Exists already, add new items to existing positions pool
|
||||
var existingLootPosition = mapLooseLoot.Spawnpoints.FirstOrDefault(x =>
|
||||
x.Template.Id == positionToAdd.Template.Id
|
||||
);
|
||||
|
||||
if (existingLootPosition is not null)
|
||||
if (looselootData is null)
|
||||
{
|
||||
existingLootPosition.Template.Items.AddRange(positionToAdd.Template.Items);
|
||||
existingLootPosition.ItemDistribution.AddRange(positionToAdd.ItemDistribution);
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
return looselootData;
|
||||
}
|
||||
|
||||
// New position, add entire object
|
||||
mapLooseLoot.Spawnpoints.Add(positionToAdd);
|
||||
}
|
||||
foreach (var positionToAdd in positionsToAdd)
|
||||
{
|
||||
// Exists already, add new items to existing positions pool
|
||||
var existingLootPosition = looselootData.Spawnpoints.FirstOrDefault(x =>
|
||||
x.Template.Id == positionToAdd.Template.Id
|
||||
);
|
||||
|
||||
if (existingLootPosition is not null)
|
||||
{
|
||||
existingLootPosition.Template.Items.AddRange(positionToAdd.Template.Items);
|
||||
existingLootPosition.ItemDistribution.AddRange(positionToAdd.ItemDistribution);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// New position, add entire object
|
||||
looselootData.Spawnpoints.Add(positionToAdd);
|
||||
}
|
||||
|
||||
return looselootData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,35 +415,39 @@ public class PostDbLoadService(
|
||||
|
||||
foreach (var (mapId, mapAdjustments) in _lootConfig.LooseLootSpawnPointAdjustments)
|
||||
{
|
||||
var mapLooseLootData = _databaseService.GetLocation(mapId).LooseLoot.Value;
|
||||
if (mapLooseLootData is null)
|
||||
_databaseService.GetLocation(mapId).LooseLoot.AddTransformer(looselootData =>
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (lootKey, newChanceValue) in mapAdjustments)
|
||||
{
|
||||
var lootPostionToAdjust = mapLooseLootData.Spawnpoints.FirstOrDefault(spawnPoint =>
|
||||
spawnPoint.Template.Id == lootKey
|
||||
);
|
||||
if (lootPostionToAdjust is null)
|
||||
if (looselootData is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_adjust_loot_position_on_map",
|
||||
new { lootKey, mapId }
|
||||
)
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
return looselootData;
|
||||
}
|
||||
|
||||
lootPostionToAdjust.Probability = newChanceValue;
|
||||
}
|
||||
foreach (var (lootKey, newChanceValue) in mapAdjustments)
|
||||
{
|
||||
var lootPostionToAdjust = looselootData.Spawnpoints.FirstOrDefault(spawnPoint =>
|
||||
spawnPoint.Template.Id == lootKey
|
||||
);
|
||||
if (lootPostionToAdjust is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_adjust_loot_position_on_map",
|
||||
new { lootKey, mapId }
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
lootPostionToAdjust.Probability = newChanceValue;
|
||||
}
|
||||
|
||||
return looselootData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,11 +541,11 @@ public class PostDbLoadService(
|
||||
}
|
||||
|
||||
foreach (var area in _databaseService.GetHideout().Areas)
|
||||
foreach (var (_, stage) in area.Stages)
|
||||
// Only adjust crafts ABOVE the override
|
||||
{
|
||||
stage.ConstructionTime = Math.Min(stage.ConstructionTime.Value, overrideSeconds);
|
||||
}
|
||||
foreach (var (_, stage) in area.Stages)
|
||||
// Only adjust crafts ABOVE the override
|
||||
{
|
||||
stage.ConstructionTime = Math.Min(stage.ConstructionTime.Value, overrideSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UnlockHideoutLootCrateCrafts()
|
||||
|
||||
@@ -1030,8 +1030,16 @@ public class SeasonalEventService(
|
||||
|
||||
protected void RenameBitcoin()
|
||||
{
|
||||
_localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name", "Physical SPT Coin");
|
||||
_localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName", "0.2SPT");
|
||||
if(_databaseService.GetLocales().Global.TryGetValue("en", out var lazyLoad))
|
||||
{
|
||||
lazyLoad.AddTransformer(localeData =>
|
||||
{
|
||||
localeData[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name"] = "Physical SPT Coin";
|
||||
localeData[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName"] = "0.2SPT";
|
||||
|
||||
return localeData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
public class LazyLoad<T>(Func<T> deserialize)
|
||||
{
|
||||
private readonly List<Func<T?, T?>> _lazyLoadTransformers = [];
|
||||
private readonly ReaderWriterLockSlim _lazyLoadTransformersLock = new();
|
||||
private static readonly TimeSpan _autoCleanerTimeout = TimeSpan.FromSeconds(30);
|
||||
private bool _isLoaded;
|
||||
private T? _result;
|
||||
@@ -9,10 +11,23 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
private Timer? autoCleanerTimeout;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="OnLazyLoad" /> can be subscribed to for mods to modify. It is fired right after lazy loading is complete
|
||||
/// and any modification passed to <see cref="OnLazyLoadEventArgs.Value" /> will stay for the duration of this <see cref="LazyLoad{T}"/>'s lifecycle
|
||||
/// Adds a transformer to modify the value during lazy loading. Transformers execute
|
||||
/// in registration order and the final result is cached until auto-cleanup.
|
||||
/// </summary>
|
||||
public event EventHandler<OnLazyLoadEventArgs<T>>? OnLazyLoad;
|
||||
/// <param name="transformer">Function that transforms the value</param>
|
||||
public void AddTransformer(Func<T?, T?> transformer)
|
||||
{
|
||||
_lazyLoadTransformersLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
_lazyLoadTransformers.Add(transformer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lazyLoadTransformersLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public T? Value
|
||||
{
|
||||
@@ -23,12 +38,21 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
_result = deserialize();
|
||||
_isLoaded = true;
|
||||
|
||||
OnLazyLoadEventArgs<T> args = new(_result);
|
||||
OnLazyLoad?.Invoke(this, args);
|
||||
|
||||
if (args.Value != null)
|
||||
_lazyLoadTransformersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
_result = args.Value;
|
||||
foreach (var transform in _lazyLoadTransformers)
|
||||
{
|
||||
_result = transform(_result);
|
||||
}
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lazyLoadTransformersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
autoCleanerTimeout = new Timer(
|
||||
@@ -43,15 +67,10 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
_autoCleanerTimeout,
|
||||
Timeout.InfiniteTimeSpan
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
autoCleanerTimeout?.Change(_autoCleanerTimeout, Timeout.InfiniteTimeSpan);
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OnLazyLoadEventArgs<T>(T value) : EventArgs
|
||||
{
|
||||
public T Value { get; set; } = value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user