Run JSON schema validation in separate thread

This commit is contained in:
Liza 2024-07-17 15:05:24 +02:00
parent aa73231f38
commit 6f2ebe5a5a
Signed by: liza
GPG Key ID: 7199F8D727D55F67
5 changed files with 97 additions and 40 deletions

View File

@ -7,15 +7,14 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Dalamud.Plugin; using Dalamud.Plugin;
using Json.Schema;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Data; using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using Questionable.QuestPaths; using Questionable.QuestPaths;
using Questionable.Validation; using Questionable.Validation;
using Questionable.Validation.Validators;
namespace Questionable.Controller; namespace Questionable.Controller;
@ -25,18 +24,19 @@ internal sealed class QuestRegistry
private readonly QuestData _questData; private readonly QuestData _questData;
private readonly QuestValidator _questValidator; private readonly QuestValidator _questValidator;
private readonly ILogger<QuestRegistry> _logger; private readonly ILogger<QuestRegistry> _logger;
private readonly JsonSchema _questSchema; private readonly JsonSchemaValidator _jsonSchemaValidator;
private readonly Dictionary<ushort, Quest> _quests = new(); private readonly Dictionary<ushort, Quest> _quests = new();
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData, public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
QuestValidator questValidator, ILogger<QuestRegistry> logger) QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
ILogger<QuestRegistry> logger)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_questData = questData; _questData = questData;
_questValidator = questValidator; _questValidator = questValidator;
_jsonSchemaValidator = jsonSchemaValidator;
_logger = logger; _logger = logger;
_questSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result;
} }
public IEnumerable<Quest> AllQuests => _quests.Values; public IEnumerable<Quest> AllQuests => _quests.Values;
@ -46,7 +46,7 @@ internal sealed class QuestRegistry
public void Reload() public void Reload()
{ {
_questValidator.ClearIssues(); _questValidator.Reset();
_quests.Clear(); _quests.Clear();
LoadQuestsFromAssembly(); LoadQuestsFromAssembly();
@ -130,26 +130,8 @@ internal sealed class QuestRegistry
if (questId == null) if (questId == null)
return; return;
var questNode = JsonNode.Parse(stream); var questNode = JsonNode.Parse(stream)!;
Task.Run(() => _jsonSchemaValidator.Enqueue(questId.Value, questNode);
{
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"
});
}
});
Quest quest = new Quest Quest quest = new Quest
{ {

View File

@ -67,6 +67,23 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable))); serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration()); 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<GameFunctions>();
serviceCollection.AddSingleton<ChatFunctions>(); serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<AetherCurrentData>(); serviceCollection.AddSingleton<AetherCurrentData>();
@ -76,12 +93,15 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<NavmeshIpc>(); serviceCollection.AddSingleton<NavmeshIpc>();
serviceCollection.AddSingleton<LifestreamIpc>(); serviceCollection.AddSingleton<LifestreamIpc>();
serviceCollection.AddSingleton<YesAlreadyIpc>(); serviceCollection.AddSingleton<YesAlreadyIpc>();
}
private static void AddTaskFactories(ServiceCollection serviceCollection)
{
// individual tasks // individual tasks
serviceCollection.AddTransient<MountTask>(); serviceCollection.AddTransient<MountTask>();
serviceCollection.AddTransient<UnmountTask>(); serviceCollection.AddTransient<UnmountTask>();
// tasks with factories // task factories
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>(); serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>(); serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>();
serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckTask>(); serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckTask>();
@ -115,7 +135,10 @@ public sealed class QuestionablePlugin : IDalamudPlugin
WaitAtEnd.WaitObjectAtPosition>(); WaitAtEnd.WaitObjectAtPosition>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
}
private static void AddControllers(ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<MovementController>(); serviceCollection.AddSingleton<MovementController>();
serviceCollection.AddSingleton<MovementOverrideController>(); serviceCollection.AddSingleton<MovementOverrideController>();
serviceCollection.AddSingleton<QuestRegistry>(); serviceCollection.AddSingleton<QuestRegistry>();
@ -125,27 +148,27 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<CombatController>(); serviceCollection.AddSingleton<CombatController>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>(); serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
}
private static void AddWindows(ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<QuestWindow>(); serviceCollection.AddSingleton<QuestWindow>();
serviceCollection.AddSingleton<ConfigWindow>(); serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DebugOverlay>(); serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<QuestSelectionWindow>(); serviceCollection.AddSingleton<QuestSelectionWindow>();
serviceCollection.AddSingleton<QuestValidationWindow>(); serviceCollection.AddSingleton<QuestValidationWindow>();
}
private static void AddQuestValidators(ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<QuestValidator>(); serviceCollection.AddSingleton<QuestValidator>();
serviceCollection.AddSingleton<IQuestValidator, QuestDisabledValidator>(); serviceCollection.AddSingleton<IQuestValidator, QuestDisabledValidator>();
serviceCollection.AddSingleton<IQuestValidator, BasicSequenceValidator>(); serviceCollection.AddSingleton<IQuestValidator, BasicSequenceValidator>();
serviceCollection.AddSingleton<IQuestValidator, UniqueStartStopValidator>(); serviceCollection.AddSingleton<IQuestValidator, UniqueStartStopValidator>();
serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>(); serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>(); serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
serviceCollection.AddSingleton<JsonSchemaValidator>();
serviceCollection.AddSingleton<CommandHandler>(); serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
serviceCollection.AddSingleton<DalamudInitializer>();
_serviceProvider = serviceCollection.BuildServiceProvider();
_serviceProvider.GetRequiredService<QuestRegistry>().Reload();
_serviceProvider.GetRequiredService<CommandHandler>();
_serviceProvider.GetRequiredService<DalamudInitializer>();
} }
public void Dispose() public void Dispose()

View File

@ -6,4 +6,8 @@ namespace Questionable.Validation;
internal interface IQuestValidator internal interface IQuestValidator
{ {
IEnumerable<ValidationIssue> Validate(Quest quest); IEnumerable<ValidationIssue> Validate(Quest quest);
void Reset()
{
}
} }

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model; using Questionable.Model;
@ -26,11 +27,16 @@ internal sealed class QuestValidator
public int IssueCount => _validationIssues.Count; public int IssueCount => _validationIssues.Count;
public int ErrorCount => _validationIssues.Count(x => x.Severity == EIssueSeverity.Error); 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) public void Validate(IEnumerable<Quest> quests)
{ {
Task.Run(() => Task.Factory.StartNew(() =>
{ {
foreach (var quest in quests) foreach (var quest in quests)
{ {
@ -52,8 +58,6 @@ internal sealed class QuestValidator
.ThenBy(x => x.Step) .ThenBy(x => x.Step)
.ThenBy(x => x.Description) .ThenBy(x => x.Description)
.ToList(); .ToList();
}); }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} }
public void AddIssue(ValidationIssue issue) => _validationIssues.Add(issue);
} }

View File

@ -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();
}