feat: add reusable node export validation
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Game.Godot.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thrown when a node's [Export]ed Node reference is unassigned.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MissingRequiredExportException(string nodeName, IReadOnlyList<string> missingExports)
|
||||||
|
: Exception(BuildMessage(nodeName, missingExports))
|
||||||
|
{
|
||||||
|
public string NodeName { get; } = nodeName;
|
||||||
|
public IReadOnlyList<string> MissingExports { get; } = missingExports;
|
||||||
|
|
||||||
|
private static string BuildMessage(string nodeName, IReadOnlyList<string> missingExports)
|
||||||
|
=> $"{nodeName}: missing required exports:\n- {string.Join("\n- ", missingExports)}\nAssign them in the editor Inspector.";
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://diqdaysdyp3jf
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Game.Godot.Exceptions;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Godot.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that a node's [Export] fields/properties pointing to other nodes are actually assigned.
|
||||||
|
/// </summary>
|
||||||
|
public static class NodeExportValidationExtensions
|
||||||
|
{
|
||||||
|
// Tells which members to include when scanning for [Export] fields/properties
|
||||||
|
private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
private static void DisableProcessing(Node node)
|
||||||
|
{
|
||||||
|
node.SetProcess(false);
|
||||||
|
node.SetPhysicsProcess(false);
|
||||||
|
node.SetProcessInput(false);
|
||||||
|
node.SetProcessUnhandledInput(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetMissingExportWarnings(Node node)
|
||||||
|
{
|
||||||
|
Type nodeType = node.GetType();
|
||||||
|
|
||||||
|
IEnumerable<string> missingFields = nodeType
|
||||||
|
.GetFields(MemberFlags)
|
||||||
|
.Where(field => field.GetCustomAttribute<ExportAttribute>() is not null)
|
||||||
|
.Where(field => typeof(Node).IsAssignableFrom(field.FieldType))
|
||||||
|
.Where(field => field.GetValue(node) is null)
|
||||||
|
.Select(field => $"{field.Name} ({field.FieldType.Name}) is not assigned.");
|
||||||
|
|
||||||
|
IEnumerable<string> missingProperties = nodeType
|
||||||
|
.GetProperties(MemberFlags)
|
||||||
|
.Where(property => property.GetCustomAttribute<ExportAttribute>() is not null)
|
||||||
|
.Where(property => typeof(Node).IsAssignableFrom(property.PropertyType))
|
||||||
|
.Where(property => property.GetValue(node) is null)
|
||||||
|
.Select(property => $"{property.Name} ({property.PropertyType.Name}) is not assigned.");
|
||||||
|
|
||||||
|
return missingFields.Concat(missingProperties).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws if any [Export]ed Node field on this node is unassigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to check.</param>
|
||||||
|
/// <exception cref="MissingRequiredExportException">One or more exports are missing.</exception>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// public override void _Ready()
|
||||||
|
/// {
|
||||||
|
/// this.AssertRequiredExports();
|
||||||
|
/// // rest of initialization
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public static void AssertRequiredExports(this Node node)
|
||||||
|
{
|
||||||
|
List<string> missing = GetMissingExportWarnings(node).ToList();
|
||||||
|
if (missing.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisableProcessing(node);
|
||||||
|
|
||||||
|
throw new MissingRequiredExportException(node.Name, missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d3twapljpp1ln
|
||||||
Reference in New Issue
Block a user