From b89074fc2d83a61985660137178d9c048be720ee Mon Sep 17 00:00:00 2001 From: AnakinRaW Date: Sat, 8 Jun 2024 12:28:00 +0200 Subject: [PATCH] start parsing sfxevents and duplicate checker --- PetroglyphTools | 2 +- src/ModVerify/Steps/DuplicateFinderStep.cs | 49 +++++ ...sStep.cs => VerifyReferencedModelsStep.cs} | 8 +- src/ModVerify/VerificationProvider.cs | 1 + src/ModVerify/VerifyGamePipeline.cs | 1 - .../DataTypes/GameObject.cs | 61 +++--- .../DataTypes/SfxEvent.cs | 20 ++ .../DataTypes/XmlObject.cs | 19 ++ .../Database/GameDatabase.cs | 7 +- .../GameDatabaseService.cs | 9 +- .../Database/IGameDatabase.cs | 7 +- .../IGameDatabaseService.cs | 3 +- .../Database/IXmlDatabase.cs | 15 ++ .../Initialization}/CreateDatabaseStep.cs | 4 +- .../GameDatabaseCreationPipeline.cs | 80 +++++--- .../ParseXmlDatabaseFromContainerStep.cs | 26 +-- .../Initialization}/ParseXmlDatabaseStep.cs | 4 +- .../Database/XmlDatabase.cs | 27 +++ .../PetroglyphEngineServiceContribution.cs | 2 +- .../Repositories/GameRepository.cs | 1 + .../Xml/Parsers/Data/GameConstantsParser.cs | 21 ++ .../Parsers/{ => Data}/GameObjectParser.cs | 20 +- .../Xml/Parsers/Data/SfxEventParser.cs | 73 +++++++ .../Parsers/File/GameObjectFileFileParser.cs | 29 +++ .../Xml/Parsers/File/SfxEventFileParser.cs | 29 +++ .../Xml/Parsers/GameConstantsFileParser.cs | 14 -- .../Xml/Parsers/GameObjectFileFileParser.cs | 19 -- .../Xml/PetroglyphXmlParserFactory.cs | 10 +- .../Parsers/IPetroglyphXmlElementParser.cs | 7 +- .../Parsers/IPetroglyphXmlFileParser.cs | 5 +- .../Parsers/PetroglyphXmlElementParser.cs | 13 +- .../Parsers/PetroglyphXmlFileParser.cs | 30 ++- .../CommaSeparatedStringKeyValueListParser.cs | 6 + .../Primitives/PetroglyphXmlStringParser.cs | 6 + .../Primitives/XmlFileContainerParser.cs | 14 +- .../ValueListDictionary.cs | 189 +++++++++++++++--- 36 files changed, 647 insertions(+), 184 deletions(-) create mode 100644 src/ModVerify/Steps/DuplicateFinderStep.cs rename src/ModVerify/Steps/{VerifyModelsStep.cs => VerifyReferencedModelsStep.cs} (96%) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database}/GameDatabaseService.cs (68%) rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database}/IGameDatabaseService.cs (75%) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database/Initialization}/CreateDatabaseStep.cs (78%) rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database/Initialization}/GameDatabaseCreationPipeline.cs (58%) rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database/Initialization}/ParseXmlDatabaseFromContainerStep.cs (68%) rename src/PetroglyphTools/PG.StarWarsGame.Engine/{Pipeline => Database/Initialization}/ParseXmlDatabaseStep.cs (92%) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs rename src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/{ => Data}/GameObjectParser.cs (90%) create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs create mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs delete mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameConstantsFileParser.cs delete mode 100644 src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectFileFileParser.cs diff --git a/PetroglyphTools b/PetroglyphTools index 16461a0..adf607f 160000 --- a/PetroglyphTools +++ b/PetroglyphTools @@ -1 +1 @@ -Subproject commit 16461a02bb8f57a584cc6e9fff0f7a4d1c3901b5 +Subproject commit adf607fbe479c315ea142564f08b637e839914b1 diff --git a/src/ModVerify/Steps/DuplicateFinderStep.cs b/src/ModVerify/Steps/DuplicateFinderStep.cs new file mode 100644 index 0000000..f74721e --- /dev/null +++ b/src/ModVerify/Steps/DuplicateFinderStep.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Threading; +using AnakinRaW.CommonUtilities.Collections; +using PG.StarWarsGame.Engine.Database; +using PG.StarWarsGame.Engine.DataTypes; + +namespace AET.ModVerify.Steps; + +public sealed class DuplicateFinderStep( + IGameDatabase gameDatabase, + VerificationSettings settings, + IServiceProvider serviceProvider) + : GameVerificationStep(gameDatabase, settings, serviceProvider) +{ + public const string DuplicateFound = "DUP00"; + + protected override string LogFileName => "Duplicates"; + + public override string Name => "Duplicate Definitions"; + + protected override void RunVerification(CancellationToken token) + { + CheckDatabaseForDuplicates(Database.GameObjects, "GameObject"); + CheckDatabaseForDuplicates(Database.SfxEvents, "SFXEvent"); + } + + private void CheckDatabaseForDuplicates(IXmlDatabase database, string context) where T : XmlObject + { + foreach (var key in database.EntryKeys) + { + var entries = database.GetEntries(key); + if (entries.Count > 1) + AddError(VerificationError.Create(DuplicateFound, CreateDuplicateErrorMessage(context, entries))); + } + } + + private string CreateDuplicateErrorMessage(string context, ReadOnlyFrugalList entries) where T : XmlObject + { + var firstEntry = entries.First(); + + var message = $"{context} '{firstEntry.Name}' ({firstEntry.Crc32}) has duplicate definitions: "; + + foreach (var entry in entries) + message += $"['{entry.Name}' in {entry.Location.XmlFile}] "; + + return message.TrimEnd(); + } +} \ No newline at end of file diff --git a/src/ModVerify/Steps/VerifyModelsStep.cs b/src/ModVerify/Steps/VerifyReferencedModelsStep.cs similarity index 96% rename from src/ModVerify/Steps/VerifyModelsStep.cs rename to src/ModVerify/Steps/VerifyReferencedModelsStep.cs index b33b3d1..a43d244 100644 --- a/src/ModVerify/Steps/VerifyModelsStep.cs +++ b/src/ModVerify/Steps/VerifyReferencedModelsStep.cs @@ -16,7 +16,7 @@ namespace AET.ModVerify.Steps; -internal sealed class VerifyReferencedModelsStep( +public sealed class VerifyReferencedModelsStep( IGameDatabase database, VerificationSettings settings, IServiceProvider serviceProvider) @@ -34,13 +34,13 @@ internal sealed class VerifyReferencedModelsStep( private readonly IAloFileService _modelFileService = serviceProvider.GetRequiredService(); - protected override string LogFileName => "Model"; + protected override string LogFileName => "ModelRefs"; - public override string Name => "Model"; + public override string Name => "Referenced Models"; protected override void RunVerification(CancellationToken token) { - var aloQueue = new Queue(Database.GameObjects + var aloQueue = new Queue(Database.GameObjects.Entries .SelectMany(x => x.Models) .Concat(FocHardcodedConstants.HardcodedModels)); diff --git a/src/ModVerify/VerificationProvider.cs b/src/ModVerify/VerificationProvider.cs index 3a347c8..2a5c9d8 100644 --- a/src/ModVerify/VerificationProvider.cs +++ b/src/ModVerify/VerificationProvider.cs @@ -10,5 +10,6 @@ internal class VerificationProvider(IServiceProvider serviceProvider) : IVerific public IEnumerable GetAllDefaultVerifiers(IGameDatabase database, VerificationSettings settings) { yield return new VerifyReferencedModelsStep(database, settings, serviceProvider); + yield return new DuplicateFinderStep(database, settings, serviceProvider); } } \ No newline at end of file diff --git a/src/ModVerify/VerifyGamePipeline.cs b/src/ModVerify/VerifyGamePipeline.cs index cbf0bac..4d1d60a 100644 --- a/src/ModVerify/VerifyGamePipeline.cs +++ b/src/ModVerify/VerifyGamePipeline.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine; using PG.StarWarsGame.Engine.Database; -using PG.StarWarsGame.Engine.Pipeline; namespace AET.ModVerify; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs index 27bea09..320649f 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/GameObject.cs @@ -1,24 +1,33 @@ using System; using System.Collections.Generic; using System.Linq; -using PG.Commons.DataTypes; using PG.Commons.Hashing; using PG.StarWarsGame.Files.XML; namespace PG.StarWarsGame.Engine.DataTypes; -public sealed class GameObject(string type, string name, Crc32 nameCrc, GameObjectType estimatedType, ValueListDictionary properties, XmlLocationInfo location) - : IHasCrc32 +public sealed class GameObject : XmlObject { - public string Type { get; } = type ?? throw new ArgumentNullException(nameof(type)); + private readonly IReadOnlyValueListDictionary _properties; - public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); + internal GameObject( + string type, + string name, + Crc32 nameCrc, + GameObjectType estimatedType, + IReadOnlyValueListDictionary properties, + XmlLocationInfo location) + : base(name, nameCrc, location) + { + _properties = properties; + Type = type ?? throw new ArgumentNullException(nameof(type)); + EstimatedType = estimatedType; + } - public Crc32 Crc32 { get; } = nameCrc; + public string Type { get; } - public GameObjectType EstimatedType { get; } = estimatedType; - public XmlLocationInfo Location { get; } = location; + public GameObjectType EstimatedType { get; } /// /// Gets all model files (including particles) the game object references. @@ -27,22 +36,24 @@ public ISet Models { get { - var models = properties.AggregateValues(new HashSet - { - "Galactic_Model_Name", - "Destroyed_Galactic_Model_Name", - "Land_Model_Name", - "Space_Model_Name", - "Model_Name", - "Tactical_Model_Name", - "Galactic_Fleet_Override_Model_Name", - "GUI_Model_Name", - "GUI_Model", - // This can either be a model or a unit reference - "Land_Model_Anim_Override_Name", - "xxxSpace_Model_Name", - "Damaged_Smoke_Asset_Name" - }, v => v.EndsWith(".alo", StringComparison.OrdinalIgnoreCase)); + var models = _properties.AggregateValues + (new HashSet + { + "Galactic_Model_Name", + "Destroyed_Galactic_Model_Name", + "Land_Model_Name", + "Space_Model_Name", + "Model_Name", + "Tactical_Model_Name", + "Galactic_Fleet_Override_Model_Name", + "GUI_Model_Name", + "GUI_Model", + // This can either be a model or a unit reference + "Land_Model_Anim_Override_Name", + "xxxSpace_Model_Name", + "Damaged_Smoke_Asset_Name" + }, v => v.EndsWith(".alo", StringComparison.OrdinalIgnoreCase), + ValueListDictionaryExtensions.AggregateStrategy.LastValuePerKey); var terrainMappedModels = LandTerrainModelMapping?.Select(x => x.Model); if (terrainMappedModels is null) @@ -57,7 +68,7 @@ public ISet Models private T? GetLastPropertyOrDefault(string tagName, T? defaultValue = default) { - if (!properties.TryGetLastValue(tagName, out var value)) + if (!_properties.TryGetLastValue(tagName, out var value)) return defaultValue; return (T)value; } diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs new file mode 100644 index 0000000..8414df3 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/SfxEvent.cs @@ -0,0 +1,20 @@ +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.DataTypes; + +public sealed class SfxEvent : XmlObject +{ + private int _volumeValue; + + public bool IsPreset { get; } + + public SfxEvent? Preset { get; } + + public int Volume => Preset?.Volume ?? _volumeValue; + + internal SfxEvent(string name, Crc32 nameCrc, XmlLocationInfo location) + : base(name, nameCrc, location) + { + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs new file mode 100644 index 0000000..29914cb --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/DataTypes/XmlObject.cs @@ -0,0 +1,19 @@ +using System; +using PG.Commons.DataTypes; +using PG.Commons.Hashing; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.DataTypes; + +public abstract class XmlObject( + string name, + Crc32 nameCrc, + XmlLocationInfo location) + : IHasCrc32 +{ + public XmlLocationInfo Location { get; } = location; + + public Crc32 Crc32 { get; } = nameCrc; + + public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name)); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs index cae38db..c23903a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabase.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.DataTypes; using PG.StarWarsGame.Engine.Repositories; namespace PG.StarWarsGame.Engine.Database; @@ -10,5 +9,7 @@ internal class GameDatabase : IGameDatabase public required GameConstants GameConstants { get; init; } - public required IList GameObjects { get; init; } + public required IXmlDatabase GameObjects { get; init; } + + public required IXmlDatabase SfxEvents { get; init; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs similarity index 68% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseService.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs index 6465a45..9386d0c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/GameDatabaseService.cs @@ -2,14 +2,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Engine.Database; +using PG.StarWarsGame.Engine.Database.Initialization; using PG.StarWarsGame.Engine.Repositories; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database; internal class GameDatabaseService(IServiceProvider serviceProvider) : IGameDatabaseService { - public async Task CreateDatabaseAsync(GameEngineType targetEngineType, GameLocations locations, CancellationToken cancellationToken = default) + public async Task CreateDatabaseAsync( + GameEngineType targetEngineType, + GameLocations locations, + CancellationToken cancellationToken = default) { var repoFactory = serviceProvider.GetRequiredService(); var repository = repoFactory.Create(targetEngineType, locations); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs index d228af7..7716027 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabase.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.DataTypes; using PG.StarWarsGame.Engine.Repositories; namespace PG.StarWarsGame.Engine.Database; @@ -10,5 +9,7 @@ public interface IGameDatabase public GameConstants GameConstants { get; } - public IList GameObjects { get; } + public IXmlDatabase GameObjects { get; } + + public IXmlDatabase SfxEvents { get; } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/IGameDatabaseService.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs similarity index 75% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/IGameDatabaseService.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs index 9492017..21b5971 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/IGameDatabaseService.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IGameDatabaseService.cs @@ -1,8 +1,7 @@ using System.Threading; using System.Threading.Tasks; -using PG.StarWarsGame.Engine.Database; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database; public interface IGameDatabaseService { diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs new file mode 100644 index 0000000..c2edb8e --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/IXmlDatabase.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using AnakinRaW.CommonUtilities.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; + +namespace PG.StarWarsGame.Engine.Database; + +public interface IXmlDatabase where T : XmlObject +{ + ICollection Entries { get; } + + ICollection EntryKeys { get; } + + ReadOnlyFrugalList GetEntries(Crc32 key); +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs similarity index 78% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateDatabaseStep.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs index bb94640..2c1f20e 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/CreateDatabaseStep.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/CreateDatabaseStep.cs @@ -4,9 +4,9 @@ using Microsoft.Extensions.Logging; using PG.StarWarsGame.Engine.Repositories; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database.Initialization; -public abstract class CreateDatabaseStep(IGameRepository repository, IServiceProvider serviceProvider) +internal abstract class CreateDatabaseStep(IGameRepository repository, IServiceProvider serviceProvider) : PipelineStep(serviceProvider) where T : class { public T Database { get; private set; } = null!; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseCreationPipeline.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs similarity index 58% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseCreationPipeline.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs index 4928e33..642461e 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/GameDatabaseCreationPipeline.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/GameDatabaseCreationPipeline.cs @@ -4,24 +4,45 @@ using System.Threading; using System.Threading.Tasks; using AnakinRaW.CommonUtilities.SimplePipeline; +using AnakinRaW.CommonUtilities.SimplePipeline.Runners; +using AnakinRaW.CommonUtilities.SimplePipeline.Steps; using Microsoft.Extensions.Logging; -using PG.StarWarsGame.Engine.Database; using PG.StarWarsGame.Engine.DataTypes; using PG.StarWarsGame.Engine.Repositories; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database.Initialization; -internal class GameDatabaseCreationPipeline(GameRepository repository, IServiceProvider serviceProvider) : ParallelPipeline(serviceProvider) +internal class GameDatabaseCreationPipeline(GameRepository repository, IServiceProvider serviceProvider) + : Pipeline(serviceProvider) { private ParseSingletonXmlStep _parseGameConstants = null!; - private ParseFromContainerStep _parseGameObjects = null!; + private ParseXmlDatabaseFromContainerStep _parseGameObjects = null!; + private ParseXmlDatabaseFromContainerStep _parseSfxEvents = null!; + + private ParallelRunner _parseXmlRunner = null!; public GameDatabase GameDatabase { get; private set; } = null!; - - protected override Task> BuildSteps() + + + protected override Task PrepareCoreAsync() + { + _parseXmlRunner = new ParallelRunner(4, ServiceProvider); + foreach (var xmlParserStep in CreateXmlParserSteps()) _parseXmlRunner.AddStep(xmlParserStep); + + + return Task.FromResult(true); + } + + private IEnumerable CreateXmlParserSteps() { - _parseGameConstants = new ParseSingletonXmlStep("GameConstants", "DATA\\XML\\GAMECONSTANTS.XML", repository, ServiceProvider); - _parseGameObjects = new ParseFromContainerStep("GameObjects", "DATA\\XML\\GAMEOBJECTFILES.XML", repository, ServiceProvider); + yield return _parseGameConstants = new ParseSingletonXmlStep("GameConstants", + "DATA\\XML\\GAMECONSTANTS.XML", repository, ServiceProvider); + + yield return _parseGameObjects = new ParseXmlDatabaseFromContainerStep("GameObjects", + "DATA\\XML\\GAMEOBJECTFILES.XML", repository, ServiceProvider); + + yield return _parseSfxEvents = new ParseXmlDatabaseFromContainerStep("SFXEvents", + "DATA\\XML\\SFXEventFiles.XML", repository, ServiceProvider); // GUIDialogs.xml // LensFlares.xml @@ -54,7 +75,6 @@ protected override Task> BuildSteps() // CONTAINER FILES: // GameObjectFiles.xml - // SFXEventFiles.xml // CommandBarComponentFiles.xml // TradeRouteFiles.xml // HardPointDataFiles.xml @@ -62,12 +82,6 @@ protected override Task> BuildSteps() // FactionFiles.xml // TargetingPrioritySetFiles.xml // MousePointerFiles.xml - - return Task.FromResult>(new List - { - _parseGameConstants, - _parseGameObjects - }); } protected override async Task RunCoreAsync(CancellationToken token) @@ -76,7 +90,22 @@ protected override async Task RunCoreAsync(CancellationToken token) try { - await base.RunCoreAsync(token); + try + { + Logger?.LogInformation("Parsing XML Files..."); + _parseXmlRunner.Error += OnError; + await _parseXmlRunner.RunAsync(token); + } + finally + { + Logger?.LogInformation("Finished parsing XML Files..."); + _parseXmlRunner.Error -= OnError; + } + + ThrowIfAnyStepsFailed(_parseXmlRunner.Steps); + + token.ThrowIfCancellationRequested(); + repository.Seal(); @@ -84,7 +113,8 @@ protected override async Task RunCoreAsync(CancellationToken token) { GameRepository = repository, GameConstants = _parseGameConstants.Database, - GameObjects = _parseGameObjects.Database + GameObjects = _parseGameObjects.Database, + SfxEvents = _parseSfxEvents.Database, }; } finally @@ -106,20 +136,4 @@ protected override T CreateDatabase(IList parsedDatabaseEntries) return parsedDatabaseEntries.First(); } } - - - private sealed class ParseFromContainerStep( - string name, - string xmlFile, - IGameRepository repository, - IServiceProvider serviceProvider) - : ParseXmlDatabaseFromContainerStep>(xmlFile, repository, serviceProvider) - { - protected override string Name => name; - - protected override IList CreateDatabase(IList> parsedDatabaseEntries) - { - return parsedDatabaseEntries.SelectMany(x => x).ToList(); - } - } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseFromContainerStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs similarity index 68% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseFromContainerStep.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs index 00235e7..a29c226 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseFromContainerStep.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseFromContainerStep.cs @@ -1,27 +1,31 @@ using System; -using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; using PG.StarWarsGame.Engine.Repositories; using PG.StarWarsGame.Engine.Xml; using PG.StarWarsGame.Files.XML; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database.Initialization; -public abstract class ParseXmlDatabaseFromContainerStep( +internal class ParseXmlDatabaseFromContainerStep( + string name, string xmlFile, IGameRepository repository, IServiceProvider serviceProvider) - : CreateDatabaseStep(repository, serviceProvider) - where T : class + : CreateDatabaseStep>(repository, serviceProvider) + where T : XmlObject { private readonly IFileSystem _fileSystem = serviceProvider.GetRequiredService(); protected readonly IPetroglyphXmlFileParserFactory FileParserFactory = serviceProvider.GetRequiredService(); - protected sealed override T CreateDatabase() + protected override string Name => name; + + protected sealed override IXmlDatabase CreateDatabase() { using var containerStream = GameRepository.OpenFile(xmlFile); var containerParser = FileParserFactory.GetFileParser(); @@ -30,19 +34,17 @@ protected sealed override T CreateDatabase() var xmlFiles = container.Files.Select(x => _fileSystem.Path.Combine("DATA\\XML", x)).ToList(); + var parsedEntries = new ValueListDictionary(); - var parsedDatabaseEntries = new List(); foreach (var file in xmlFiles) { using var fileStream = GameRepository.OpenFile(file); var parser = FileParserFactory.GetFileParser(); Logger?.LogDebug($"Parsing File '{file}'"); - var parsedData = parser.ParseFile(fileStream); - parsedDatabaseEntries.Add(parsedData); + parser.ParseFile(fileStream, parsedEntries); } - return CreateDatabase(parsedDatabaseEntries); - } - protected abstract T CreateDatabase(IList files); + return new XmlDatabase(parsedEntries, Services); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseStep.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs similarity index 92% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseStep.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs index 2fb1c9d..4bed127 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Pipeline/ParseXmlDatabaseStep.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/Initialization/ParseXmlDatabaseStep.cs @@ -5,9 +5,9 @@ using PG.StarWarsGame.Engine.Repositories; using PG.StarWarsGame.Engine.Xml; -namespace PG.StarWarsGame.Engine.Pipeline; +namespace PG.StarWarsGame.Engine.Database.Initialization; -public abstract class ParseXmlDatabaseStep( +internal abstract class ParseXmlDatabaseStep( IList xmlFiles, IGameRepository repository, IServiceProvider serviceProvider) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs new file mode 100644 index 0000000..38d0a78 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Database/XmlDatabase.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using AnakinRaW.CommonUtilities.Collections; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Files.XML; + +namespace PG.StarWarsGame.Engine.Database; + +internal class XmlDatabase(IReadOnlyValueListDictionary parsedObjects, IServiceProvider serviceProvider) : IXmlDatabase + where T : XmlObject +{ + + private readonly IServiceProvider _serviceProvider = + serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + private readonly IReadOnlyValueListDictionary _parsedObjects = parsedObjects ?? throw new ArgumentNullException(nameof(parsedObjects)); + + public ICollection Entries => _parsedObjects.Values; + + public ICollection EntryKeys => _parsedObjects.Keys; + + public ReadOnlyFrugalList GetEntries(Crc32 key) + { + return _parsedObjects.GetValues(key); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs index 36f02b4..4b02a65 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/PetroglyphEngineServiceContribution.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; +using PG.StarWarsGame.Engine.Database; using PG.StarWarsGame.Engine.Language; -using PG.StarWarsGame.Engine.Pipeline; using PG.StarWarsGame.Engine.Repositories; using PG.StarWarsGame.Engine.Xml; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs index 32dcedd..74c21df 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Repositories/GameRepository.cs @@ -249,6 +249,7 @@ internal void Seal() protected MegDataEntryReference? FindFileInMasterMeg(string filePath) { + // TODO To Span, as we don't use the name elsewhere var normalizedPath = _megPathNormalizer.Normalize(filePath); var crc = _crc32HashingService.GetCrc32(normalizedPath, PGConstants.PGCrc32Encoding); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs new file mode 100644 index 0000000..23c5917 --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameConstantsParser.cs @@ -0,0 +1,21 @@ +using System; +using System.Xml.Linq; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; + +internal class GameConstantsParser(IServiceProvider serviceProvider) : PetroglyphXmlFileParser(serviceProvider) +{ + public override GameConstants Parse(XElement element) + { + return new GameConstants(); + } + + protected override void Parse(XElement element, IValueListDictionary parsedElements) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs similarity index 90% rename from src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectParser.cs rename to src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs index d3039f5..5e07b24 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/GameObjectParser.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using PG.Commons.Hashing; @@ -8,7 +7,7 @@ using PG.StarWarsGame.Files.XML.Parsers; using PG.StarWarsGame.Files.XML.Parsers.Primitives; -namespace PG.StarWarsGame.Engine.Xml.Parsers; +namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; public sealed class GameObjectParser(IServiceProvider serviceProvider) : PetroglyphXmlElementParser(serviceProvider) { @@ -39,14 +38,18 @@ public sealed class GameObjectParser(IServiceProvider serviceProvider) : Petrogl } public override GameObject Parse(XElement element) + { + throw new NotSupportedException(); + } + + public override GameObject Parse(XElement element, IReadOnlyValueListDictionary parsedElements, out Crc32 nameCrc) { var properties = ToKeyValuePairList(element); var name = GetNameAttributeValue(element); - var nameCrc = _crc32Hashing.GetCrc32(name, PGConstants.PGCrc32Encoding); + nameCrc = _crc32Hashing.GetCrc32Upper(name.AsSpan(), PGConstants.PGCrc32Encoding); var type = GetTagName(element); var objectType = EstimateType(type); - var location = XmlLocationInfo.FromElement(element); - var gameObject = new GameObject(type, name, nameCrc, objectType, properties, location); + var gameObject = new GameObject(type, name, nameCrc, objectType, properties, XmlLocationInfo.FromElement(element)); return gameObject; } @@ -101,13 +104,6 @@ private static GameObjectType EstimateType(string tagName) }; } - public string GetNameAttributeValue(XElement element) - { - var nameAttribute = element.Attributes() - .FirstOrDefault(a => a.Name.LocalName == "Name"); - return nameAttribute is null ? string.Empty : nameAttribute.Value; - } - public string GetTagName(XElement element) { return element.Name.LocalName; diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs new file mode 100644 index 0000000..4ec2f0d --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/Data/SfxEventParser.cs @@ -0,0 +1,73 @@ +using System; +using System.Xml.Linq; +using Microsoft.Extensions.DependencyInjection; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.Data; + +public sealed class SfxEventParser(IServiceProvider serviceProvider) : PetroglyphXmlElementParser(serviceProvider) +{ + private readonly ICrc32HashingService _hashingService = serviceProvider.GetRequiredService(); + + protected override IPetroglyphXmlElementParser? GetParser(string tag) + { + return null; + //switch (tag) + //{ + // case "Land_Terrain_Model_Mapping": + // return new CommaSeparatedStringKeyValueListParser(ServiceProvider); + // case "Galactic_Model_Name": + // case "Damaged_Smoke_Asset_Name": + // return PetroglyphXmlStringParser.Instance; + // default: + // return null; + //} + } + + public override SfxEvent Parse( + XElement element, + IReadOnlyValueListDictionary parsedElements, + out Crc32 nameCrc) + { + var name = GetNameAttributeValue(element); + nameCrc = _hashingService.GetCrc32Upper(name.AsSpan(), PGConstants.PGCrc32Encoding); + + var valueList = new ValueListDictionary(); + + foreach (var child in element.Elements()) + { + var tagName = child.Name.LocalName; + var parser = GetParser(tagName); + if (parser is null) + continue; + + if (tagName.Equals("Use_Preset")) + { + var presetName = parser.Parse(child) as string; + var presetNameCrc = _hashingService.GetCrc32Upper(presetName.AsSpan(), PGConstants.PGCrc32Encoding); + if (presetNameCrc == default || !parsedElements.TryGetFirstValue(presetNameCrc, out var preset)) + { + // Unable to find Preset + continue; + } + } + else + { + valueList.Add(tagName, parser.Parse(child)); + } + } + + var sfxEvent = new SfxEvent(name, nameCrc, XmlLocationInfo.FromElement(element)); + + return sfxEvent; + } + + public override SfxEvent Parse(XElement element) + { + throw new NotSupportedException(); + } + +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs new file mode 100644 index 0000000..e02a86d --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/GameObjectFileFileParser.cs @@ -0,0 +1,29 @@ +using System; +using System.Xml.Linq; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.Xml.Parsers.Data; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.File; + +internal class GameObjectFileFileParser(IServiceProvider serviceProvider) + : PetroglyphXmlFileParser(serviceProvider) +{ + protected override void Parse(XElement element, IValueListDictionary parsedElements) + { + var parser = new GameObjectParser(ServiceProvider); + + foreach (var xElement in element.Elements()) + { + var sfxEvent = parser.Parse(xElement, parsedElements, out var nameCrc); + parsedElements.Add(nameCrc, sfxEvent); + } + } + + public override GameObject Parse(XElement element) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs new file mode 100644 index 0000000..c02b6bb --- /dev/null +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/File/SfxEventFileParser.cs @@ -0,0 +1,29 @@ +using System; +using System.Xml.Linq; +using PG.Commons.Hashing; +using PG.StarWarsGame.Engine.DataTypes; +using PG.StarWarsGame.Engine.Xml.Parsers.Data; +using PG.StarWarsGame.Files.XML; +using PG.StarWarsGame.Files.XML.Parsers; + +namespace PG.StarWarsGame.Engine.Xml.Parsers.File; + +internal class SfxEventFileParser(IServiceProvider serviceProvider) + : PetroglyphXmlFileParser(serviceProvider) +{ + protected override void Parse(XElement element, IValueListDictionary parsedElements) + { + var parser = new SfxEventParser(ServiceProvider); + var parsedObjects = new ValueListDictionary(); + foreach (var xElement in element.Elements()) + { + var sfxEvent = parser.Parse(xElement, parsedObjects, out var nameCrc); + parsedObjects.Add(nameCrc, sfxEvent); + } + } + + public override SfxEvent Parse(XElement element) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameConstantsFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameConstantsFileParser.cs deleted file mode 100644 index ac20c1c..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameConstantsFileParser.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Xml.Linq; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Engine.Xml.Parsers; - -public class GameConstantsFileParser(IServiceProvider serviceProvider) : PetroglyphXmlFileParser(serviceProvider) -{ - public override GameConstants Parse(XElement element) - { - return new GameConstants(); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectFileFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectFileFileParser.cs deleted file mode 100644 index 3bb7e0a..0000000 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/Parsers/GameObjectFileFileParser.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Files.XML.Parsers; - -namespace PG.StarWarsGame.Engine.Xml.Parsers; - -public class GameObjectFileFileParser(IServiceProvider serviceProvider) : PetroglyphXmlFileParser>(serviceProvider) -{ - protected override bool LoadLineInfo => true; - - public override IList Parse(XElement element) - { - var parser = new GameObjectParser(ServiceProvider); - return element.Elements().Select(parser.Parse).ToList(); - } -} \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs index a29f79a..10af5a0 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Engine/Xml/PetroglyphXmlParserFactory.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using PG.StarWarsGame.Engine.DataTypes; -using PG.StarWarsGame.Engine.Xml.Parsers; +using PG.StarWarsGame.Engine.Xml.Parsers.Data; +using PG.StarWarsGame.Engine.Xml.Parsers.File; using PG.StarWarsGame.Files.XML; using PG.StarWarsGame.Files.XML.Parsers; using PG.StarWarsGame.Files.XML.Parsers.Primitives; @@ -21,11 +22,14 @@ public IPetroglyphXmlFileParser GetFileParser(Type type) return new XmlFileContainerParser(serviceProvider); if (type == typeof(GameConstants)) - return new GameConstantsFileParser(serviceProvider); + return new GameConstantsParser(serviceProvider); - if (type == typeof(IList)) + if (type == typeof(GameObject)) return new GameObjectFileFileParser(serviceProvider); + if (type == typeof(SfxEvent)) + return new SfxEventFileParser(serviceProvider); + throw new NotImplementedException($"The parser for the type {type} is not yet implemented."); } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs index a26a06b..8fb31c6 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlElementParser.cs @@ -1,13 +1,16 @@ using System.Xml.Linq; +using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers; public interface IPetroglyphXmlElementParser { - public object? Parse(XElement element); + public object Parse(XElement element); } -public interface IPetroglyphXmlElementParser : IPetroglyphXmlElementParser +public interface IPetroglyphXmlElementParser : IPetroglyphXmlElementParser { public new T Parse(XElement element); + + public T Parse(XElement element, IReadOnlyValueListDictionary parsedElements, out Crc32 nameCrc); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs index b958e1f..817a13c 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/IPetroglyphXmlFileParser.cs @@ -1,4 +1,5 @@ using System.IO; +using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers; @@ -7,7 +8,9 @@ public interface IPetroglyphXmlFileParser : IPetroglyphXmlElementParser public object? ParseFile(Stream stream); } -public interface IPetroglyphXmlFileParser : IPetroglyphXmlElementParser, IPetroglyphXmlFileParser +public interface IPetroglyphXmlFileParser : IPetroglyphXmlElementParser, IPetroglyphXmlFileParser { public new T ParseFile(Stream stream); + + public void ParseFile(Stream stream, IValueListDictionary parsedEntries); } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs index 850bf26..b67fc88 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlElementParser.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using System.Xml.Linq; +using PG.Commons.Hashing; using PG.StarWarsGame.Files.XML.Parsers.Primitives; namespace PG.StarWarsGame.Files.XML.Parsers; @@ -8,6 +10,7 @@ public abstract class PetroglyphXmlElementParser(IServiceProvider serviceProv { protected IServiceProvider ServiceProvider { get; } = serviceProvider; + protected virtual IPetroglyphXmlElementParser? GetParser(string tag) { return PetroglyphXmlStringParser.Instance; @@ -15,6 +18,8 @@ public abstract class PetroglyphXmlElementParser(IServiceProvider serviceProv public abstract T Parse(XElement element); + public abstract T Parse(XElement element, IReadOnlyValueListDictionary parsedElements, out Crc32 nameCrc); + public ValueListDictionary ToKeyValuePairList(XElement element) { var keyValuePairList = new ValueListDictionary(); @@ -30,10 +35,16 @@ public ValueListDictionary ToKeyValuePairList(XElement element) keyValuePairList.Add(tagName, value); } } - return keyValuePairList; } + protected string GetNameAttributeValue(XElement element) + { + var nameAttribute = element.Attributes() + .FirstOrDefault(a => a.Name.LocalName == "Name"); + return nameAttribute is null ? string.Empty : nameAttribute.Value; + } + object? IPetroglyphXmlElementParser.Parse(XElement element) { return Parse(element); diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs index 16e5778..c721f5a 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/PetroglyphXmlFileParser.cs @@ -2,14 +2,37 @@ using System.IO; using System.Xml; using System.Xml.Linq; +using PG.Commons.Hashing; using PG.Commons.Utilities; namespace PG.StarWarsGame.Files.XML.Parsers; -public abstract class PetroglyphXmlFileParser(IServiceProvider serviceProvider) : PetroglyphXmlElementParser(serviceProvider), IPetroglyphXmlFileParser +public abstract class PetroglyphXmlFileParser(IServiceProvider serviceProvider) + : PetroglyphXmlElementParser(serviceProvider), IPetroglyphXmlFileParser { protected virtual bool LoadLineInfo => false; + public T ParseFile(Stream xmlStream) + { + var root = GetRootElement(xmlStream); + return root is null ? default! : Parse(root); + } + + public void ParseFile(Stream xmlStream, IValueListDictionary parsedEntries) + { + var root = GetRootElement(xmlStream); + if (root is not null) + Parse(root, parsedEntries); + } + + public sealed override T Parse(XElement element, IReadOnlyValueListDictionary parsedElements, out Crc32 nameCrc) + { + throw new NotSupportedException(); + } + + protected abstract void Parse(XElement element, IValueListDictionary parsedElements); + + private XElement? GetRootElement(Stream xmlStream) { var fileName = xmlStream.GetFilePath(); var xmlReader = XmlReader.Create(xmlStream, new XmlReaderSettings(), fileName); @@ -19,10 +42,7 @@ public T ParseFile(Stream xmlStream) options |= LoadOptions.SetLineInfo; var doc = XDocument.Load(xmlReader, options); - var root = doc.Root; - if (root is null) - return default!; - return Parse(root); + return doc.Root; } object? IPetroglyphXmlFileParser.ParseFile(Stream stream) diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs index ee1929d..e0de592 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/CommaSeparatedStringKeyValueListParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; +using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; @@ -34,4 +35,9 @@ public sealed class CommaSeparatedStringKeyValueListParser(IServiceProvider serv return keyValueList; } + + public override IList<(string key, string value)> Parse(XElement element, IReadOnlyValueListDictionary> parsedElements, out Crc32 nameCrc) + { + throw new NotSupportedException(); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs index 66253ae..5f53e49 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/PetroglyphXmlStringParser.cs @@ -1,5 +1,6 @@ using System; using System.Xml.Linq; +using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; @@ -16,4 +17,9 @@ public override string Parse(XElement element) { return element.Value.Trim(); } + + public override string Parse(XElement element, IReadOnlyValueListDictionary parsedElements, out Crc32 nameCrc) + { + throw new NotSupportedException(); + } } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs index 490ce83..b2954ab 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/Parsers/Primitives/XmlFileContainerParser.cs @@ -1,16 +1,17 @@ using System; using System.Linq; using System.Xml.Linq; +using PG.Commons.Hashing; namespace PG.StarWarsGame.Files.XML.Parsers.Primitives; public class XmlFileContainerParser(IServiceProvider serviceProvider) : PetroglyphXmlFileParser(serviceProvider) { - protected override IPetroglyphXmlElementParser? GetParser(string tag) + protected override bool LoadLineInfo => false; + + protected override void Parse(XElement element, IValueListDictionary parsedElements) { - if (tag == "File") - return PetroglyphXmlStringParser.Instance; - return null; + throw new NotSupportedException(); } public override XmlFileContainer Parse(XElement element) @@ -21,4 +22,9 @@ public override XmlFileContainer Parse(XElement element) ? new XmlFileContainer(files.OfType().ToList()) : new XmlFileContainer([]); } + protected override IPetroglyphXmlElementParser? GetParser(string tag) + { + return tag == "File" ? PetroglyphXmlStringParser.Instance : null; + } + } \ No newline at end of file diff --git a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs index 19cd60a..5da2eb7 100644 --- a/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs +++ b/src/PetroglyphTools/PG.StarWarsGame.Files.XML/ValueListDictionary.cs @@ -1,26 +1,57 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using AnakinRaW.CommonUtilities.Collections; namespace PG.StarWarsGame.Files.XML; + +public interface IReadOnlyValueListDictionary : IEnumerable> where TKey : notnull +{ + ICollection Values { get; } + ICollection Keys { get; } + + bool ContainsKey(TKey key); + + ReadOnlyFrugalList GetValues(TKey key); + + TValue GetLastValue(TKey key); + + TValue GetFirstValue(TKey key); + + bool TryGetFirstValue(TKey key, [NotNullWhen(true)] out TValue value); + + bool TryGetLastValue(TKey key, [NotNullWhen(true)] out TValue value); + + bool TryGetValues(TKey key, out ReadOnlyFrugalList values); +} + +public interface IValueListDictionary : IReadOnlyValueListDictionary where TKey : notnull +{ + bool Add(TKey key, TValue value); +} + // NOT THREAD-SAFE! -public class ValueListDictionary where TKey : notnull +public class ValueListDictionary : IValueListDictionary where TKey : notnull { - private readonly Dictionary _singleValueDictionary = new (); - private readonly Dictionary> _multiValueDictionary = new(); + private readonly Dictionary _singleValueDictionary = new (); + private readonly Dictionary> _multiValueDictionary = new(); + public ICollection Keys => _singleValueDictionary.Keys.Concat(_multiValueDictionary.Keys).ToList(); + + public ICollection Values => this.Select(x => x.Value).ToList(); public bool ContainsKey(TKey key) { return _singleValueDictionary.ContainsKey(key) || _multiValueDictionary.ContainsKey(key); } - - public bool Add(TKey key, TValue? value) + + public bool Add(TKey key, TValue value) { - if (key == null) + if (key is null) throw new ArgumentNullException(nameof(key)); if (!_singleValueDictionary.ContainsKey(key)) @@ -48,7 +79,7 @@ public bool Add(TKey key, TValue? value) return true; } - public TValue? GetLastValue(TKey key) + public TValue GetLastValue(TKey key) { if (_singleValueDictionary.TryGetValue(key, out var value)) return value; @@ -59,66 +90,153 @@ public bool Add(TKey key, TValue? value) throw new KeyNotFoundException($"The key '{key}' was not found."); } - public T? GetLastValue(TKey key) where T : TValue + public TValue GetFirstValue(TKey key) { - var value = GetLastValue(key); - if (value is null) - return default; - return (T)value; + if (_singleValueDictionary.TryGetValue(key, out var value)) + return value; + + if (_multiValueDictionary.TryGetValue(key, out var valueList)) + return valueList.First(); + + throw new KeyNotFoundException($"The key '{key}' was not found."); } + + public ReadOnlyFrugalList GetValues(TKey key) + { + if (TryGetValues(key, out var values)) + return values; + throw new KeyNotFoundException($"The key '{key}' was not found."); - public IList GetValues(TKey key) + } + + public bool TryGetFirstValue(TKey key, [NotNullWhen(true)] out TValue value) { - if (!TryGetValues(key, out var values)) - throw new KeyNotFoundException($"The key '{key}' was not found."); - return values; + if (_singleValueDictionary.TryGetValue(key, out value!)) + return true; + + if (_multiValueDictionary.TryGetValue(key, out var valueList)) + { + value = valueList.First()!; + return true; + } + + return false; } - public bool TryGetLastValue(TKey key, [NotNullWhen(true)] out TValue? value) + public bool TryGetLastValue(TKey key, [NotNullWhen(true)] out TValue value) { if (_singleValueDictionary.TryGetValue(key, out value!)) return true; if (_multiValueDictionary.TryGetValue(key, out var valueList)) { - value = valueList.Last()!; + value = valueList.Last()!; return true; } return false; } - public bool TryGetValues(TKey key, [NotNullWhen(true)] out IList? values) + public bool TryGetValues(TKey key, out ReadOnlyFrugalList values) { if (_singleValueDictionary.TryGetValue(key, out var value)) { - values = new List(1) - { - value - }; + values = new ReadOnlyFrugalList(value); return true; } if (_multiValueDictionary.TryGetValue(key, out var valueList)) { - values = valueList; + values = new ReadOnlyFrugalList(valueList); return true; } - values = null; + values = ReadOnlyFrugalList.Empty; return false; } - public IEnumerable AggregateValues(ISet keys, Predicate filter, bool multipleValuesPerKey = false) where T : TValue + public IEnumerator> GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public struct Enumerator : IEnumerator> + { + private Dictionary.Enumerator _singleEnumerator; + private Dictionary>.Enumerator _multiEnumerator; + private List.Enumerator _currentListEnumerator = default; + private bool _isMultiEnumeratorActive = false; + + internal Enumerator(ValueListDictionary valueListDictionary) + { + _singleEnumerator = valueListDictionary._singleValueDictionary.GetEnumerator(); + _multiEnumerator = valueListDictionary._multiValueDictionary.GetEnumerator(); + } + + public KeyValuePair Current => + _isMultiEnumeratorActive + ? new KeyValuePair(_multiEnumerator.Current.Key, _currentListEnumerator.Current) + : _singleEnumerator.Current; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (_singleEnumerator.MoveNext()) + return true; + + if (_isMultiEnumeratorActive) + { + if (_currentListEnumerator.MoveNext()) + return true; + _isMultiEnumeratorActive = false; + } + + if (_multiEnumerator.MoveNext()) + { + _currentListEnumerator = _multiEnumerator.Current.Value.GetEnumerator(); + _isMultiEnumeratorActive = true; + return _currentListEnumerator.MoveNext(); + } + + return false; + } + + public void Reset() + { + throw new NotSupportedException(); + } + + public void Dispose() + { + _singleEnumerator.Dispose(); + _multiEnumerator.Dispose(); + } + } +} + +public static class ValueListDictionaryExtensions +{ + public static IEnumerable AggregateValues( + this IReadOnlyValueListDictionary valueListDictionary, + ISet keys, Predicate filter, + AggregateStrategy aggregateStrategy) + where TKey : notnull + where T : TValue { foreach (var key in keys) { - if (!ContainsKey(key)) + if (!valueListDictionary.ContainsKey(key)) continue; - if (multipleValuesPerKey) + if (aggregateStrategy == AggregateStrategy.MultipleValuesPerKey) { - foreach (var value in GetValues(key)) + foreach (var value in valueListDictionary.GetValues(key)) { if (value is not null) { @@ -126,12 +244,14 @@ public IEnumerable AggregateValues(ISet keys, Predicate filter, b if (filter(typedValue)) yield return typedValue; } - + } } else { - var value = GetLastValue(key); + var value = aggregateStrategy == AggregateStrategy.FirstValuePerKey + ? valueListDictionary.GetFirstValue(key) + : valueListDictionary.GetLastValue(key); if (value is not null) { var typedValue = (T)value; @@ -141,4 +261,11 @@ public IEnumerable AggregateValues(ISet keys, Predicate filter, b } } } + + public enum AggregateStrategy + { + FirstValuePerKey, + LastValuePerKey, + MultipleValuesPerKey, + } } \ No newline at end of file