[Save/Sync] In-Progress ModConfigXml loading rewrite.
+ Fixed async operations lock for Dispose() pattern in working files. + Rewrote StorageService.cs: --- Now uses ContentPath instead of raw strings where possible. --- Now throws exceptions for developer errors and critical program states. + Rewrote ModConfigService.cs: --- All functions are now completely async. + Removed ConfigProfilesResources completely as they exist in common Config xml files. + Somewhat simplified package data and processes.
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Processing;
|
||||
|
||||
public sealed class ConfigFileParserService :
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>
|
||||
{
|
||||
private IStorageService _storageService;
|
||||
private readonly AsyncReaderWriterLock _operationsLock = new();
|
||||
|
||||
public ConfigFileParserService(IStorageService storageService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
#region Dispose
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
return;
|
||||
try
|
||||
{
|
||||
_storageService.Dispose();
|
||||
this._storageService = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// --- Assemblies
|
||||
async Task<Result<IAssemblyResourceInfo>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".dll");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new AssemblyResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible,
|
||||
// Type Specific
|
||||
FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty),
|
||||
IsScript = src.Element.GetAttributeBool("IsScript", false),
|
||||
};
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<IAssemblyResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IAssemblyResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Config
|
||||
|
||||
async Task<Result<IConfigResourceInfo>> IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Config") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".xml");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new ConfigResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible
|
||||
};
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<IConfigResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IConfigResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Lua Scripts
|
||||
async Task<Result<ILuaScriptResourceInfo>> IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Lua") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".lua");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new LuaScriptsResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible,
|
||||
// Type Specific
|
||||
IsAutorun = src.Element.GetAttributeBool("RunFile", false)
|
||||
};
|
||||
}
|
||||
|
||||
private FluentResults.Result CheckThrowNullRefs(ResourceParserInfo src, string elementName)
|
||||
{
|
||||
Guard.IsNotNull(src, nameof(src));
|
||||
Guard.IsNotNull(src.Owner, nameof(src.Owner));
|
||||
Guard.IsNotNull(src.Element, nameof(src.Element));
|
||||
|
||||
if (src.Element.Name != elementName)
|
||||
{
|
||||
return FluentResults.Result.Fail($"Element name '{elementName}' is incorrect");
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<ILuaScriptResourceInfo>>> IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<ILuaScriptResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Helpers
|
||||
private async Task<Result<ImmutableArray<ContentPath>>> GetCheckedFiles(XElement srcElement, ContentPackage srcOwner, string fileExtension)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var builder = ImmutableArray.CreateBuilder<ContentPath>();
|
||||
var filePath = srcElement.GetAttributeString("File", string.Empty);
|
||||
var folderPath = srcElement.GetAttributeString("Folder", string.Empty);
|
||||
|
||||
if (!filePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var cp = ContentPath.FromRaw(srcOwner, filePath);
|
||||
if (_storageService.FileExists(cp.FullPath) is { IsSuccess: true, Value: true })
|
||||
{
|
||||
builder.Add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
if (!folderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var cp = ContentPath.FromRaw(srcOwner, folderPath);
|
||||
if (_storageService.DirectoryExists(cp.FullPath) is { IsSuccess: true, Value: true })
|
||||
{
|
||||
var files = _storageService.FindFilesInPackage(cp.ContentPackage, cp.Value, fileExtension, true);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
private (Platform Platform, Target Target) GetRuntimeEnvironment(XElement element)
|
||||
{
|
||||
return (
|
||||
Platform: element.GetAttributeEnum("Platform", Platform.Windows | Platform.Linux | Platform.OSX),
|
||||
Target: element.GetAttributeEnum("Target", Target.Client | Target.Server));
|
||||
}
|
||||
|
||||
private async Task<ImmutableArray<Result<T>>> TryParseGenericResourcesAsync<T>(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
Guard.IsNotNull(sources, nameof(IParserServiceAsync<ResourceParserInfo, T>.TryParseResourcesAsync));
|
||||
var builder = ImmutableArray.CreateBuilder<Result<T>>();
|
||||
foreach (var info in sources)
|
||||
{
|
||||
builder.Add(await Unsafe.As<IParserServiceAsync<ResourceParserInfo, T>>(this).TryParseResourceAsync(info));
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
}
|
||||
+134
-34
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -17,29 +18,51 @@ namespace Barotrauma.LuaCs.Services.Processing;
|
||||
public sealed class ModConfigService : IModConfigService
|
||||
{
|
||||
private IStorageService _storageService;
|
||||
private ILoggerService _logger;
|
||||
private IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> _assemblyParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> _luaScriptParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> _configParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, IConfigProfileResourceInfo> _configProfileParserService;
|
||||
private readonly AsyncReaderWriterLock _operationsLock = new();
|
||||
|
||||
public ModConfigService(IStorageService storageService,
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> assemblyParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> luaScriptParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> configParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigProfileResourceInfo> configProfileParserService)
|
||||
ILoggerService logger)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_assemblyParserService = assemblyParserService;
|
||||
_luaScriptParserService = luaScriptParserService;
|
||||
_configParserService = configParserService;
|
||||
_configProfileParserService = configProfileParserService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region Dispose
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_storageService.Dispose();
|
||||
_logger.Dispose();
|
||||
_assemblyParserService.Dispose();
|
||||
_luaScriptParserService.Dispose();
|
||||
_configParserService.Dispose();
|
||||
|
||||
_storageService = null;
|
||||
_logger = null;
|
||||
_assemblyParserService = null;
|
||||
_luaScriptParserService = null;
|
||||
_configParserService = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
@@ -54,6 +77,8 @@ public sealed class ModConfigService : IModConfigService
|
||||
public async Task<Result<IModConfigInfo>> CreateConfigAsync(ContentPackage src)
|
||||
{
|
||||
Guard.IsNotNull(src, nameof(src));
|
||||
using var lck = await _operationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config })
|
||||
{
|
||||
@@ -65,6 +90,11 @@ public sealed class ModConfigService : IModConfigService
|
||||
|
||||
public async Task<ImmutableArray<(ContentPackage Source, Result<IModConfigInfo> Config)>> CreateConfigsAsync(ImmutableArray<ContentPackage> src)
|
||||
{
|
||||
if (src.IsDefaultOrEmpty)
|
||||
ThrowHelper.ThrowArgumentNullException($"{nameof(CreateConfigsAsync)}: The supplied array is default or empty!");
|
||||
using var lck = await _operationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var builder = new ConcurrentQueue<(ContentPackage Source, Result<IModConfigInfo> Config)>();
|
||||
|
||||
await src.ParallelForEachAsync(async package =>
|
||||
@@ -79,55 +109,125 @@ public sealed class ModConfigService : IModConfigService
|
||||
//--- Helpers
|
||||
private async Task<Result<XElement>> TryGetModConfigXmlAsync(ContentPackage src)
|
||||
{
|
||||
return await _storageService.LoadPackageXmlAsync(src, "ModConfig.xml") is { IsSuccess: true, Value: { Root: {} config} }
|
||||
return await _storageService.LoadPackageXmlAsync(ContentPath.FromRaw(src, "%ModDir%/ModConfig.xml")) is { IsSuccess: true, Value: { Root: {} config} }
|
||||
? FluentResults.Result.Ok(config)
|
||||
: FluentResults.Result.Fail<XElement>("ModConfig.xml not found");
|
||||
}
|
||||
|
||||
private async Task<Result<IModConfigInfo>> CreateFromConfigXmlAsync(ContentPackage owner, XElement src)
|
||||
{
|
||||
/*var cfg = src.GetChildElements("Config");
|
||||
var modConfig = new ModConfigInfo()
|
||||
ImmutableArray<IAssemblyResourceInfo> assemblyResources = default;
|
||||
ImmutableArray<IConfigResourceInfo> configResources = default;
|
||||
ImmutableArray<ILuaScriptResourceInfo> luaResources = default;
|
||||
|
||||
var res = await Task.WhenAll(new[]
|
||||
{
|
||||
new Task<Task>(async () => assemblyResources = await GetAssembliesFromXml(owner, src)),
|
||||
new Task<Task>(async () => configResources = await GetConfigsFromXml(owner, src)),
|
||||
new Task<Task>(async () => luaResources = await GetLuaScriptsFromXml(owner, src)),
|
||||
});
|
||||
|
||||
bool isFaulted = false;
|
||||
foreach (var task in res)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
_logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: {task.Exception?.ToString()}");
|
||||
isFaulted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFaulted)
|
||||
{
|
||||
_logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}");
|
||||
return FluentResults.Result.Fail($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}");
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok<IModConfigInfo>(new ModConfigInfo()
|
||||
{
|
||||
Package = owner,
|
||||
Assemblies = src.GetChildElements("Assembly") is {} asm ? GetAssembliesFromXml(owner, asm)
|
||||
: ImmutableArray<IAssemblyResourceInfo>.Empty,
|
||||
Configs = cfg is {} ? GetConfigsFromXml(owner, cfg) : ImmutableArray<IConfigResourceInfo>.Empty,
|
||||
ConfigProfiles = cfg is {} ? GetConfigProfilesFromXml(owner, cfg) : ImmutableArray<IConfigProfileResourceInfo>.Empty,
|
||||
LuaScripts = src.GetChildElements("Lua") is {} lua ? GetLuaScriptsFromXml(owner, lua)
|
||||
: ImmutableArray<ILuaScriptResourceInfo>.Empty
|
||||
};*/
|
||||
Assemblies = assemblyResources,
|
||||
Configs = configResources,
|
||||
LuaScripts = luaResources
|
||||
});
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<ILuaScriptResourceInfo>>> GetLuaScriptsFromXml(ContentPackage contentPackage,
|
||||
async Task<ImmutableArray<ILuaScriptResourceInfo>> GetLuaScriptsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
var luaElems = cfgElement.GetChildElements("Lua").ToImmutableArray();
|
||||
if (cfgElement.GetChildElements("FileGroup").ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroup
|
||||
&& fileGroup.SelectMany(fg => fg.GetChildElements()))
|
||||
return await GetResourceFromXml<ILuaScriptResourceInfo>(contentPackage, cfgElement, "Lua", "FileGroup", _luaScriptParserService);
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<IConfigResourceInfo>> GetConfigsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
return await GetResourceFromXml<IConfigResourceInfo>(contentPackage, cfgElement, "Config", "FileGroup", _configParserService);
|
||||
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<IAssemblyResourceInfo>> GetAssembliesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
return await GetResourceFromXml<IAssemblyResourceInfo>(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService);
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<T>> GetResourceFromXml<T>(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync<ResourceParserInfo, T> resourceService)
|
||||
{
|
||||
var elems = GetResourceElementsWithName(owner, cfgElement, elemName, fileGroupName);
|
||||
if (elems.IsDefaultOrEmpty)
|
||||
return ImmutableArray<T>.Empty;
|
||||
|
||||
var results = await resourceService.TryParseResourcesAsync(elems);
|
||||
Guard.IsNotEmpty((IReadOnlyCollection<Result<T>>)results, nameof(results));
|
||||
|
||||
var resources = ImmutableArray.CreateBuilder<T>();
|
||||
foreach (var result in results)
|
||||
{
|
||||
|
||||
if (result.Errors.Count > 0)
|
||||
{
|
||||
_logger.LogResults(result.ToResult());
|
||||
continue;
|
||||
}
|
||||
resources.Add(result.Value);
|
||||
}
|
||||
return resources.MoveToImmutable();
|
||||
}
|
||||
|
||||
ImmutableArray<ResourceParserInfo> GetResourceElementsWithName(ContentPackage package, XElement root, string elemName, string groupName)
|
||||
{
|
||||
var elems = ImmutableArray.CreateBuilder<ResourceParserInfo>();
|
||||
|
||||
elems.AddRange(root.GetChildElements(elemName)
|
||||
.Select(e => new ResourceParserInfo(package, e, ImmutableArray<Identifier>.Empty, ImmutableArray<Identifier>.Empty))
|
||||
.ToImmutableArray());
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
if (root.GetChildElements(groupName).ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroups)
|
||||
{
|
||||
foreach (var fileGroup in fileGroups)
|
||||
{
|
||||
if (fileGroup.GetChildElements(elemName).ToImmutableArray() is { IsDefaultOrEmpty: false } subLuaElems)
|
||||
{
|
||||
var cond = GetDependencyIdentifiers(fileGroup, true);
|
||||
var negCond = GetDependencyIdentifiers(fileGroup, false);
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IConfigProfileResourceInfo>>> GetConfigProfilesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
foreach (var element in subLuaElems)
|
||||
{
|
||||
elems.Add(new ResourceParserInfo(package, element, cond, negCond));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IConfigResourceInfo>>> GetConfigsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return elems.MoveToImmutable();
|
||||
}
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>>> GetAssembliesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
|
||||
ImmutableArray<Identifier> GetDependencyIdentifiers(XElement fg, bool depsLoadedSetting)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return fg.GetChildElements("Conditional")
|
||||
.Where(cElem => bool.TryParse(cElem.GetAttribute("IsLoaded").Value, out bool isLoaded) && isLoaded == depsLoadedSetting)
|
||||
.SelectMany(cElem2 => cElem2.GetAttributeString("Dependencies", String.Empty)
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(ident => new Identifier(ident)))
|
||||
.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user