[Milestone] AssemblyLoader completed.

Details:
- Assembly Mgmt Service for loading now a separate interface, not intended for normal use.
- Assembly Loader work; implemented custom dictionary key and table.
- Assembly loading work.
- EventService completed.
- Moved assembly extensions to ModUtils.cs
- Work to event service.
NetworkService work
- Added ImpromptuInterfaces package.
- Networking Service work to support NetVars
- Event Service
- Added assemblies references package for script compilation. Updated Roslyn version for compatibility.
- Package Loading work.
Swap Harmony to HarmonyX
- More refactor conversion to FluentResults.
- Updated StylesService to return Results.
- Refactor of PackageService partially complete.
- Made IService.Reset() required to return a Result.
- Moved plugin/assembly related code to their own folder (same namespace).
- Updated interfaces to reflect the use of Result<T>.
- Partial refactor, incomplete.
- Added 'FluentResults' so we can stop using cursed Exception-based flow control in loading code.
- Added 'OneOf' nuget package: https://github.com/mcintyre321/OneOf
for the implementation of the Optional<T> pattern and complex discrete return types instead of cursed enums (see current AssemblyManager.cs).
- Reapplied old branch changes.
This commit is contained in:
MapleWheels
2024-11-04 02:33:31 -05:00
committed by Maplewheels
parent 01cc1d331b
commit 6880e5e9ee
97 changed files with 4100 additions and 1512 deletions
@@ -10,6 +10,9 @@ using System.Threading;
using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Services.Processing;
using FluentResults;
using FluentResults.LuaCs;
using OneOf;
namespace Barotrauma.LuaCs.Services;
@@ -20,8 +23,7 @@ public partial class PackageService : IPackageService
// mod config / package scanners/parsers
private readonly Lazy<IXmlModConfigConverterService> _modConfigConverterService;
private readonly Lazy<ILegacyConfigService> _legacyConfigService;
private readonly Lazy<IModConfigParserService> _configParserService;
private readonly Lazy<ILuaScriptService> _luaScriptService;
private readonly Lazy<ILocalizationService> _localizationService;
private readonly Lazy<IPluginService> _pluginService;
@@ -35,31 +37,32 @@ public partial class PackageService : IPackageService
// state monitors
private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed;
private int _loadingOperationsRunning;
private int _isEnabledInModList;
public bool ConfigsLoaded
{
get => GetThreadSafeBool(ref _configsLoaded);
private set => SetThreadSafeBool(ref _configsLoaded, value);
get => ModUtils.Threading.GetBool(ref _configsLoaded);
private set => ModUtils.Threading.SetBool(ref _configsLoaded, value);
}
public bool LocalizationsLoaded
{
get => GetThreadSafeBool(ref _localizationsLoaded);
private set => SetThreadSafeBool(ref _localizationsLoaded, value);
get => ModUtils.Threading.GetBool(ref _localizationsLoaded);
private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value);
}
public bool LuaScriptsLoaded
{
get => GetThreadSafeBool(ref _luaScriptsLoaded);
private set => SetThreadSafeBool(ref _luaScriptsLoaded, value);
get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded);
private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value);
}
public bool PluginsLoaded
{
get => GetThreadSafeBool(ref _pluginsLoaded);
private set => SetThreadSafeBool(ref _pluginsLoaded, value);
get => ModUtils.Threading.GetBool(ref _pluginsLoaded);
private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value);
}
public bool IsDisposed
{
get => GetThreadSafeBool(ref _isDisposed);
private set => SetThreadSafeBool(ref _isDisposed, value);
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
private bool LoadingOperationsRunning
@@ -146,6 +149,12 @@ public partial class PackageService : IPackageService
}
}
public bool IsEnabledInModList
{
get => ModUtils.Threading.GetBool(ref _isEnabledInModList);
private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value);
}
#endregion
public ImmutableArray<CultureInfo> SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray<CultureInfo>.Empty;
@@ -159,43 +168,48 @@ public partial class PackageService : IPackageService
#region PublicAPI
public bool TryLoadResourcesInfo(ContentPackage package)
public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage)
{
if (cpackage.Package == null)
{
return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!")
.WithMetadata(MetadataType.ExceptionObject,this)
.WithMetadata(MetadataType.RootObject, cpackage));
}
ContentPackage package = cpackage.Package;
_operationsUsageLock.EnterWriteLock();
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
{
throw new ObjectDisposedException($"This package service instance is disposed!");
return FluentResults.Result.Fail(
new Error("Service is disposed.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package));
}
// try loading the ModConfig.xml. If it fails, use the Legacy loader to try and construct one from the package structure.
if (_storageService.TryLoadPackageXml(package, "ModConfig.xml", out var configXml)
&& configXml.Root is not null)
var res = _configParserService.Value.BuildConfigForPackage(package);
if (res.IsFailed)
{
if (_modConfigConverterService.Value.TryParseResource(configXml.Root, out IModConfigInfo configInfo))
{
ModConfigInfo = configInfo;
}
else
{
_loggerService.LogError(
$"Failed to parse ModConfig.xml for package {package.Name}, package mod content not loaded.");
return false;
}
}
else if (_legacyConfigService.Value.TryBuildModConfigFromLegacy(package, out var legacyConfig))
{
ModConfigInfo = legacyConfig;
}
else
{
// vanilla mod or broken
return false;
return FluentResults.Result.Fail(res.Errors)
.WithError(new Error("PackageService failed to load ModConfigInfo")
.WithMetadata(MetadataType.ExceptionObject, _configParserService)
.WithMetadata(MetadataType.RootObject, package));
}
return true;
this.ModConfigInfo = res.Value;
this.IsEnabledInModList = cpackage.IsEnabled;
return FluentResults.Result.Ok();
}
catch (Exception e)
{
return FluentResults.Result.Fail(new Error(e.Message)
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, package)
.WithMetadata(MetadataType.StackTrace, e.StackTrace));
}
finally
{
@@ -204,31 +218,19 @@ public partial class PackageService : IPackageService
}
}
public void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT0(assembliesInfo)) is { IsFailed: true } failed)
{
throw new ObjectDisposedException($"This package service instance is disposed!");
return failed;
}
SanitationChecksCore(assembliesInfo, "assemblies", nameof(LoadPlugins));
SanitationChecksEnumerable(assembliesInfo.Assemblies, "assemblies", nameof(LoadPlugins));
#if DEBUG
assembliesInfo.Assemblies.ForEach(ari =>
{
if (!this.Assemblies.Contains(ari))
{
throw new ArgumentException(
$"Package Service: tried to load the assembly resource {ari.InternalName} for package {this.Package.Name} but it is not in the list for this package.");
}
});
#endif
// Order these assemblies by internal dependencies
ImmutableArray<IAssemblyResourceInfo> resources;
if (ignoreDependencySorting)
@@ -243,12 +245,15 @@ public partial class PackageService : IPackageService
}
// Try loading them, throw on failure.
if (!_pluginService.Value.TryLoadAndInstanceTypes<IAssemblyPlugin>(resources, true, out var instancedTypes))
if (_pluginService.Value.LoadAndInstanceTypes<IAssemblyPlugin>(resources, true, out var instancedTypes) is { IsFailed: true} failed2)
{
throw new TypeLoadException($"PackageService: unable to load assemblies for package {this.Package.Name}! Aborting loading!");
return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, assembliesInfo));
}
PluginsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
@@ -257,37 +262,28 @@ public partial class PackageService : IPackageService
}
}
public void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT1(localizationsInfo)) is { IsFailed: true } failed)
{
throw new ObjectDisposedException($"This package service instance is disposed!");
return failed;
}
SanitationChecksCore(localizationsInfo, "localizations", nameof(LoadLocalizations));
SanitationChecksEnumerable(localizationsInfo.Localizations, "localizations", nameof(LoadLocalizations));
#if DEBUG
localizationsInfo.Localizations.ForEach(ri =>
if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2)
{
if (!this.Localizations.Contains(ri))
{
throw new ArgumentException(
$"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
}
});
#endif
if (!_localizationService.Value.TryLoadLocalizations(localizationsInfo.Localizations))
{
throw new FileLoadException($"Package Service: unable to load localizations for package {this.Package.Name}! Aborting!");
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, localizationsInfo));
}
LocalizationsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
@@ -296,38 +292,28 @@ public partial class PackageService : IPackageService
}
}
public void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo)
{
_operationsUsageLock.EnterReadLock();
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT4(luaScriptsInfo)) is { IsFailed: true } failed)
{
throw new ObjectDisposedException($"This package service instance is disposed!");
return failed;
}
SanitationChecksCore(luaScriptsInfo, "luaScripts", nameof(AddLuaScripts));
SanitationChecksEnumerable(luaScriptsInfo.LuaScripts, "luaScripts", nameof(AddLuaScripts));
#if DEBUG
luaScriptsInfo.LuaScripts.ForEach(ri =>
if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2)
{
if (!this.LuaScripts.Contains(ri))
{
throw new ArgumentException(
$"Package Service: tried to load the lua script resource for package {this.Package.Name} but it is not in the list for this package.");
}
});
#endif
if (!_luaScriptService.Value.TryAddScriptFiles(luaScriptsInfo.LuaScripts))
{
throw new ArgumentException(
$"Package Service: unable to add lua files for package {this.Package.Name}! Aborting!");
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, luaScriptsInfo));
}
LuaScriptsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
@@ -336,7 +322,7 @@ public partial class PackageService : IPackageService
}
}
public void LoadConfig(
public FluentResults.Result LoadConfig(
[NotNull]IConfigsResourcesInfo configsResourcesInfo,
[NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo)
{
@@ -344,48 +330,38 @@ public partial class PackageService : IPackageService
LoadingOperationsRunning = true;
try
{
if (IsDisposed)
// register configs
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT2(configsResourcesInfo)) is { IsFailed: true } failed)
{
throw new ObjectDisposedException($"This package service instance is disposed!");
return failed;
}
SanitationChecksCore(configsResourcesInfo, "config", nameof(LoadConfig));
SanitationChecksCore(configProfilesResourcesInfo, "config profiles", nameof(LoadConfig));
SanitationChecksEnumerable(configsResourcesInfo.Configs, "config", nameof(LoadConfig));
SanitationChecksEnumerable(configProfilesResourcesInfo.ConfigProfiles, "config profiles", nameof(LoadConfig));
#if DEBUG
configsResourcesInfo.Configs.ForEach(ri =>
if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2)
{
if (!this.Configs.Contains(ri))
{
throw new ArgumentException(
$"Package Service: tried to load the configs resource for package {this.Package.Name} but it is not in the list for this package.");
}
});
configProfilesResourcesInfo.ConfigProfiles.ForEach(ri =>
{
if (!this.ConfigProfiles.Contains(ri))
{
throw new ArgumentException(
$"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package.");
}
});
#endif
if (!_configService.Value.TryAddConfigs(configsResourcesInfo.Configs))
{
throw new ArgumentException(
$"Package Service: unable to add configs for package {this.Package.Name}! Aborting!");
return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, configsResourcesInfo));
}
if (!_configService.Value.TryAddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles))
// register config profiles
if (CheckResourceSanitation(OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo>
.FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3)
{
throw new ArgumentException(
$"Package Service: unable to add configs profiles for package {this.Package.Name}! Aborting!");
return failed3;
}
if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4)
{
return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo));
}
ConfigsLoaded = true;
return FluentResults.Result.Ok();
}
finally
{
@@ -463,22 +439,24 @@ public partial class PackageService : IPackageService
}
}
public void Reset()
public FluentResults.Result Reset()
{
_operationsUsageLock.EnterWriteLock();
try
{
if (this.Package is null)
{
_loggerService.LogError(
$"Package Service: cannot Dispose of service as ContentPackage and info is not set!");
return;
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!")
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
.WithMetadata(MetadataType.ExceptionObject, this));
}
if (this.ModConfigInfo is null)
{
_loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!");
return;
return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!")
.WithMetadata(MetadataType.ExceptionDetails, nameof(Reset))
.WithMetadata(MetadataType.ExceptionObject, this));
}
Interlocked.MemoryBarrier(); //ensure cache states
@@ -491,7 +469,7 @@ public partial class PackageService : IPackageService
_operationsUsageLock.EnterWriteLock();
if (timeoutLimit < DateTime.Now)
{
_loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing.");
_loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing.");
break;
}
}
@@ -520,6 +498,7 @@ public partial class PackageService : IPackageService
_localizationService.Value.Remove(this.Localizations);
LocalizationsLoaded = false;
}
return FluentResults.Result.Ok();
}
finally
{
@@ -531,96 +510,176 @@ public partial class PackageService : IPackageService
#region INTERNAL
private void SanitationChecksCore(object o, string resTypeInfoName, string callerName)
/// <summary>
/// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results.
/// NOTE: Requires that resource locks be set by the caller.
/// </summary>
/// <param name="resourcesInfos"></param>
/// <returns></returns>
private FluentResults.Result CheckResourceSanitation(
OneOf.OneOf<IAssembliesResourcesInfo, ILocalizationsResourcesInfo,
IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo> resourcesInfos)
{
if (o is null)
// execute checks based on known types
return resourcesInfos.Match<FluentResults.Result>(
ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins),
ass.Assemblies, this.Assemblies),
loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations),
loc.Localizations, this.Localizations),
cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig),
cfg.Configs, this.Configs),
cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig),
cfp.ConfigProfiles, this.ConfigProfiles),
lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts),
lua.LuaScripts, this.LuaScripts));
/*
* Helper functions
*/
FluentResults.Result ChecksDispatcher<T>(object obj, string resName, string callerName,
ImmutableArray<T> resList, ImmutableArray<T> compareList)
where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo
{
_loggerService.LogError($"Package Service: {resTypeInfoName} resources list is null!");
throw new NullReferenceException($"Package Service: {resTypeInfoName} resources list is null!");
string errMsg = $"{callerName}: Failed to load {resName}.";
if (DisposeCheck(obj) is { IsFailed: true } failed)
return failed;
if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1)
return failed1.WithError(new Error(errMsg));
if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2)
return failed2.WithError(new Error(errMsg));
if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3)
return failed3.WithError(new Error(errMsg));
return FluentResults.Result.Ok();
}
FluentResults.Result DisposeCheck(object obj)
{
if (IsDisposed)
{
return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, obj));
}
return FluentResults.Result.Ok();
}
FluentResults.Result DebugCheck<T>(ImmutableArray<T> resList, ImmutableArray<T> compareList, string resName)
where T : class, IPackageInfo
{
#if DEBUG
Stack<Error> errors = new();
resList.ForEach(res =>
{
if (!compareList.Contains(res))
{
errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}")
.WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, res));
}
});
if (errors.Count > 0)
{
return FluentResults.Result.Fail(errors).WithError(
new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, this.Package));
}
#endif
return FluentResults.Result.Ok();
}
}
private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName)
{
Error e = null;
if (obj is null)
{
e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!")
.WithMetadata(MetadataType.ExceptionDetails, "Object is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
}
if (this.Package is null)
{
_loggerService.LogError($"Package Service: package not set at {callerName}()!");
throw new NullReferenceException($"Package Service: package not set at {callerName}()!");
e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!"))
.WithMetadata(MetadataType.ExceptionDetails, "The Package is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.Sources, new List<string>() { resTypeInfoName, callerName });
}
return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e);
}
private void SanitationChecksEnumerable<T>(ImmutableArray<T> resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
private FluentResults.Result SanitationChecksEnumerable<T>(ImmutableArray<T> resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo
{
// Check if list is empty. Nothing more to do.
if (resourceInfos.IsDefaultOrEmpty)
return;
return FluentResults.Result.Ok();
Stack<Error> errors = new();
// Check if all resources in the list are registered to this package, throw if not.
foreach (var resourceInfo in resourceInfos)
{
// ownership checks
if (resourceInfo.OwnerPackage is null)
{
throw new ArgumentException($"Package Service: {resTypeInfoName} info for resource does not have a package name set! Run by {this.Package.Name}.");
{
errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
continue;
}
if (resourceInfo.OwnerPackage != this.Package)
{
throw new ArgumentException(
$"Package Service: {resTypeInfoName} info does not belong to this package! Owned by {resourceInfo.OwnerPackage.Name} but is run by {this.Package.Name}.");
errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
continue;
}
// Check if external dependencies are loaded and if current environment is supported, throw if not
if (resourceInfo.Dependencies.IsDefaultOrEmpty)
continue;
bool resourceMissing = false;
resourceInfo.Dependencies.ForEach(pdi =>
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (var pdi in resourceInfo.Dependencies)
{
// for clarification: assemblies passed to the function should always be loaded.
// optional assemblies should be filtered out before the list is sent.
// for clarification: all resources passed to the function should always be loaded.
// unneeded optional resources should be filtered out before the list is sent.
// left this as a reminder :)
/*if (pdi.Optional)
return;*/
if (!_packageManagementService.CheckDependencyLoaded(pdi))
{
resourceMissing = true;
_loggerService.LogError(
$"Package Service: the following dependency for package {resourceInfo.OwnerPackage.Name} is not loaded: {pdi.DependencyPackage?.Name ?? (pdi.PackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.PackageName)}");
errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}")
.WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
});
if (!resourceMissing)
{
throw new FileLoadException($"Package Service: dependencies for package {resourceInfo.OwnerPackage.Name} are not loaded.");
}
// check runtime platform
if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo))
{
throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported on this platform.");
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
// check local culture
if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo))
{
throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported in this culture.");
errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, resourceInfo));
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetThreadSafeBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) == 1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetThreadSafeBool(ref int var, bool value)
{
if (value)
{
Interlocked.CompareExchange(ref var, 1, 0);
}
else
{
Interlocked.CompareExchange(ref var, 0, 1);
}
return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok();
}
#endregion