Run JSON schema validation in separate thread

arr-p5
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.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
{

View File

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

View File

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

View File

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

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