From 6f2ebe5a5a7439e69e9ba817df8f96edfc389f57 Mon Sep 17 00:00:00 2001 From: Liza Carvelli 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 99cb48008..c8e67986e 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 _logger; - private readonly JsonSchema _questSchema; + private readonly JsonSchemaValidator _jsonSchemaValidator; private readonly Dictionary _quests = new(); public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData, - QuestValidator questValidator, ILogger logger) + QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator, + ILogger logger) { _pluginInterface = pluginInterface; _questData = questData; _questValidator = questValidator; + _jsonSchemaValidator = jsonSchemaValidator; _logger = logger; - _questSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result; } public IEnumerable 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 58f580e3d..eca5656a8 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(); + serviceCollection.AddSingleton(); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + _serviceProvider.GetRequiredService().Reload(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + } + + private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -76,12 +93,15 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + } + private static void AddTaskFactories(ServiceCollection serviceCollection) + { // individual tasks serviceCollection.AddTransient(); serviceCollection.AddTransient(); - // tasks with factories + // task factories serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); @@ -115,7 +135,10 @@ public sealed class QuestionablePlugin : IDalamudPlugin WaitAtEnd.WaitObjectAtPosition>(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + } + private static void AddControllers(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -125,27 +148,27 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + } + private static void AddWindows(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + } + private static void AddQuestValidators(ServiceCollection serviceCollection) + { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - - _serviceProvider = serviceCollection.BuildServiceProvider(); - _serviceProvider.GetRequiredService().Reload(); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(sp => sp.GetRequiredService()); } public void Dispose() diff --git a/Questionable/Validation/IQuestValidator.cs b/Questionable/Validation/IQuestValidator.cs index 02a5f182d..893b5d92d 100644 --- a/Questionable/Validation/IQuestValidator.cs +++ b/Questionable/Validation/IQuestValidator.cs @@ -6,4 +6,8 @@ namespace Questionable.Validation; internal interface IQuestValidator { IEnumerable Validate(Quest quest); + + void Reset() + { + } } diff --git a/Questionable/Validation/QuestValidator.cs b/Questionable/Validation/QuestValidator.cs index 1e6d62010..b5b7bd4f6 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 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 000000000..04abc0127 --- /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 _questNodes = new(); + private JsonSchema? _questSchema; + + public IEnumerable 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(); +}