From 6f2ebe5a5a7439e69e9ba817df8f96edfc389f57 Mon Sep 17 00:00:00 2001 From: Liza Carvelli <liza@carvel.li> Date: Wed, 17 Jul 2024 15:05:24 +0200 Subject: [PATCH] Run JSON schema validation in separate thread --- Questionable/Controller/QuestRegistry.cs | 34 ++++---------- Questionable/QuestionablePlugin.cs | 41 +++++++++++++---- Questionable/Validation/IQuestValidator.cs | 4 ++ Questionable/Validation/QuestValidator.cs | 14 +++--- .../Validators/JsonSchemaValidator.cs | 44 +++++++++++++++++++ 5 files changed, 97 insertions(+), 40 deletions(-) create mode 100644 Questionable/Validation/Validators/JsonSchemaValidator.cs diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index 99cb4800..c8e67986 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -7,15 +7,14 @@ using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; -using System.Threading.Tasks; using Dalamud.Plugin; -using Json.Schema; using Microsoft.Extensions.Logging; using Questionable.Data; using Questionable.Model; using Questionable.Model.V1; using Questionable.QuestPaths; using Questionable.Validation; +using Questionable.Validation.Validators; namespace Questionable.Controller; @@ -25,18 +24,19 @@ internal sealed class QuestRegistry private readonly QuestData _questData; private readonly QuestValidator _questValidator; private readonly ILogger<QuestRegistry> _logger; - private readonly JsonSchema _questSchema; + private readonly JsonSchemaValidator _jsonSchemaValidator; private readonly Dictionary<ushort, Quest> _quests = new(); public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData, - QuestValidator questValidator, ILogger<QuestRegistry> logger) + QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator, + ILogger<QuestRegistry> logger) { _pluginInterface = pluginInterface; _questData = questData; _questValidator = questValidator; + _jsonSchemaValidator = jsonSchemaValidator; _logger = logger; - _questSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result; } public IEnumerable<Quest> AllQuests => _quests.Values; @@ -46,7 +46,7 @@ internal sealed class QuestRegistry public void Reload() { - _questValidator.ClearIssues(); + _questValidator.Reset(); _quests.Clear(); LoadQuestsFromAssembly(); @@ -130,26 +130,8 @@ internal sealed class QuestRegistry if (questId == null) return; - var questNode = JsonNode.Parse(stream); - Task.Run(() => - { - var evaluationResult = _questSchema.Evaluate(questNode, new EvaluationOptions - { - Culture = CultureInfo.InvariantCulture, - OutputFormat = OutputFormat.List - }); - if (!evaluationResult.IsValid) - { - _questValidator.AddIssue(new ValidationIssue - { - QuestId = questId.Value, - Sequence = null, - Step = null, - Severity = EIssueSeverity.Error, - Description = "JSON Validation failed" - }); - } - }); + var questNode = JsonNode.Parse(stream)!; + _jsonSchemaValidator.Enqueue(questId.Value, questNode); Quest quest = new Quest { diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 58f580e3..eca5656a 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -67,6 +67,23 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable))); serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration()); + AddBasicFunctionsAndData(serviceCollection); + AddTaskFactories(serviceCollection); + AddControllers(serviceCollection); + AddWindows(serviceCollection); + AddQuestValidators(serviceCollection); + + serviceCollection.AddSingleton<CommandHandler>(); + serviceCollection.AddSingleton<DalamudInitializer>(); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + _serviceProvider.GetRequiredService<QuestRegistry>().Reload(); + _serviceProvider.GetRequiredService<CommandHandler>(); + _serviceProvider.GetRequiredService<DalamudInitializer>(); + } + + private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton<GameFunctions>(); serviceCollection.AddSingleton<ChatFunctions>(); serviceCollection.AddSingleton<AetherCurrentData>(); @@ -76,12 +93,15 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton<NavmeshIpc>(); serviceCollection.AddSingleton<LifestreamIpc>(); serviceCollection.AddSingleton<YesAlreadyIpc>(); + } + private static void AddTaskFactories(ServiceCollection serviceCollection) + { // individual tasks serviceCollection.AddTransient<MountTask>(); serviceCollection.AddTransient<UnmountTask>(); - // tasks with factories + // task factories serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>(); serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>(); serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckTask>(); @@ -115,7 +135,10 @@ public sealed class QuestionablePlugin : IDalamudPlugin WaitAtEnd.WaitObjectAtPosition>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>(); + } + private static void AddControllers(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton<MovementController>(); serviceCollection.AddSingleton<MovementOverrideController>(); serviceCollection.AddSingleton<QuestRegistry>(); @@ -125,27 +148,27 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton<CombatController>(); serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>(); + } + private static void AddWindows(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton<QuestWindow>(); serviceCollection.AddSingleton<ConfigWindow>(); serviceCollection.AddSingleton<DebugOverlay>(); serviceCollection.AddSingleton<QuestSelectionWindow>(); serviceCollection.AddSingleton<QuestValidationWindow>(); + } + private static void AddQuestValidators(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton<QuestValidator>(); serviceCollection.AddSingleton<IQuestValidator, QuestDisabledValidator>(); serviceCollection.AddSingleton<IQuestValidator, BasicSequenceValidator>(); serviceCollection.AddSingleton<IQuestValidator, UniqueStartStopValidator>(); serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>(); serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>(); - - serviceCollection.AddSingleton<CommandHandler>(); - serviceCollection.AddSingleton<DalamudInitializer>(); - - _serviceProvider = serviceCollection.BuildServiceProvider(); - _serviceProvider.GetRequiredService<QuestRegistry>().Reload(); - _serviceProvider.GetRequiredService<CommandHandler>(); - _serviceProvider.GetRequiredService<DalamudInitializer>(); + serviceCollection.AddSingleton<JsonSchemaValidator>(); + serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>()); } public void Dispose() diff --git a/Questionable/Validation/IQuestValidator.cs b/Questionable/Validation/IQuestValidator.cs index 02a5f182..893b5d92 100644 --- a/Questionable/Validation/IQuestValidator.cs +++ b/Questionable/Validation/IQuestValidator.cs @@ -6,4 +6,8 @@ namespace Questionable.Validation; internal interface IQuestValidator { IEnumerable<ValidationIssue> Validate(Quest quest); + + void Reset() + { + } } diff --git a/Questionable/Validation/QuestValidator.cs b/Questionable/Validation/QuestValidator.cs index 1e6d6201..b5b7bd4f 100644 --- a/Questionable/Validation/QuestValidator.cs +++ b/Questionable/Validation/QuestValidator.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Questionable.Model; @@ -26,11 +27,16 @@ internal sealed class QuestValidator public int IssueCount => _validationIssues.Count; public int ErrorCount => _validationIssues.Count(x => x.Severity == EIssueSeverity.Error); - public void ClearIssues() => _validationIssues.Clear(); + public void Reset() + { + foreach (var validator in _validators) + validator.Reset(); + _validationIssues.Clear(); + } public void Validate(IEnumerable<Quest> quests) { - Task.Run(() => + Task.Factory.StartNew(() => { foreach (var quest in quests) { @@ -52,8 +58,6 @@ internal sealed class QuestValidator .ThenBy(x => x.Step) .ThenBy(x => x.Description) .ToList(); - }); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); } - - public void AddIssue(ValidationIssue issue) => _validationIssues.Add(issue); } diff --git a/Questionable/Validation/Validators/JsonSchemaValidator.cs b/Questionable/Validation/Validators/JsonSchemaValidator.cs new file mode 100644 index 00000000..04abc012 --- /dev/null +++ b/Questionable/Validation/Validators/JsonSchemaValidator.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; +using Json.Schema; +using Questionable.Model; +using Questionable.QuestPaths; + +namespace Questionable.Validation.Validators; + +internal sealed class JsonSchemaValidator : IQuestValidator +{ + private readonly Dictionary<ushort, JsonNode> _questNodes = new(); + private JsonSchema? _questSchema; + + public IEnumerable<ValidationIssue> Validate(Quest quest) + { + _questSchema ??= JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result; + + if (_questNodes.TryGetValue(quest.QuestId, out JsonNode? questNode)) + { + var evaluationResult = _questSchema.Evaluate(questNode, new EvaluationOptions + { + Culture = CultureInfo.InvariantCulture, + OutputFormat = OutputFormat.List + }); + if (!evaluationResult.IsValid) + { + yield return new ValidationIssue + { + QuestId = quest.QuestId, + Sequence = null, + Step = null, + Severity = EIssueSeverity.Error, + Description = "JSON Validation failed" + }; + } + } + + } + + public void Enqueue(ushort questId, JsonNode questNode) => _questNodes[questId] = questNode; + + public void Reset() => _questNodes.Clear(); +}