// Copyright © Gamesmiths Guild. using System; using System.Linq; using Gamesmiths.Forge.Core; using Gamesmiths.Forge.Statescript; using Gamesmiths.Forge.Statescript.Properties; using Godot; using ForgeNode = Gamesmiths.Forge.Statescript.Node; namespace Gamesmiths.Forge.Godot.Resources.Statescript; /// /// Base resource for all Statescript property resolvers. Each resolver type derives from this and implements the /// binding methods to wire serialized editor data into the core runtime graph. /// /// /// Subclasses must override to return a unique string that matches the corresponding /// editor's ResolverTypeId. This enables automatic discovery and matching without hardcoded registrations. /// [Tool] [GlobalClass] public partial class StatescriptResolverResource : Resource { /// /// Gets the unique type identifier for this resolver, used to match serialized resources to their corresponding /// editor. Subclasses must override this to return a non-empty string that matches their editor's /// ResolverTypeId. /// public virtual string ResolverTypeId => string.Empty; /// /// Binds this resolver as an input property on a runtime node. Implementations should register any necessary /// variable or property definitions on the graph and call with the appropriate /// name. /// /// The runtime graph being built. /// The runtime node to bind the input on. /// The serialized node identifier, used for generating unique property names. /// The zero-based index of the input property to bind. public virtual void BindInput(Graph graph, ForgeNode runtimeNode, string nodeId, byte index) { } /// /// Binds this resolver as an output variable on a runtime node. Implementations should call /// with the appropriate variable name. /// /// The runtime node to bind the output on. /// The zero-based index of the output variable to bind. public virtual void BindOutput(ForgeNode runtimeNode, byte index) { } /// /// Creates an from this resolver resource. Used when this resolver appears as a /// nested operand inside another resolver (e.g., left/right side of a comparison). /// /// The runtime graph being built, used for looking up variable type information. /// The property resolver instance, or a default zero-value resolver if the resource is not configured. /// public virtual IPropertyResolver BuildResolver(Graph graph) { return new VariantResolver(default, typeof(int)); } protected static void DefineAndBindInputProperty( Graph graph, ForgeNode runtimeNode, string propertyName, byte index, IPropertyResolver resolver) { var propertyKey = new StringKey(propertyName); graph.VariableDefinitions.DefineProperty( propertyKey, AdaptResolverForExpectedInput(runtimeNode, index, resolver)); runtimeNode.BindInput(index, propertyKey); } protected static bool NeedsNumericInputAdaptation( ForgeNode runtimeNode, byte index, Type actualType) { if (index >= runtimeNode.InputProperties.Length) { return false; } Type expectedType = runtimeNode.InputProperties[index].ExpectedType; return expectedType != typeof(Variant128) && expectedType != actualType && StatescriptNumericCompatibility.CanCoerce(actualType, expectedType); } protected static IPropertyResolver AdaptResolverForExpectedType( IPropertyResolver resolver, Type expectedType) { if (expectedType == typeof(Variant128) || expectedType == resolver.ValueType) { return resolver; } if (StatescriptNumericCompatibility.CanCoerce(resolver.ValueType, expectedType)) { return new NumericCoercionResolver(resolver, expectedType); } return resolver; } protected static IPropertyResolver PromoteIntegralResolverToFloatingPoint( IPropertyResolver resolver, Type preferredFloatingType) { if (!StatescriptNumericCompatibility.IsNumericType(resolver.ValueType) || IsFloatingPointType(resolver.ValueType)) { return resolver; } return AdaptResolverForExpectedType(resolver, preferredFloatingType); } protected static Type GetPreferredFloatingPointType(params IPropertyResolver[] resolvers) { foreach (Type resolverType in resolvers.Select(resolver => resolver.ValueType)) { if (resolverType == typeof(double) || resolverType == typeof(decimal)) { return typeof(double); } } foreach (IPropertyResolver resolver in resolvers) { if (resolver.ValueType == typeof(float)) { return typeof(float); } } return typeof(double); } protected static bool IsVectorOrQuaternionType(Type type) { return type == typeof(System.Numerics.Vector2) || type == typeof(System.Numerics.Vector3) || type == typeof(System.Numerics.Vector4) || type == typeof(System.Numerics.Quaternion); } private static IPropertyResolver AdaptResolverForExpectedInput( ForgeNode runtimeNode, byte index, IPropertyResolver resolver) { if (index >= runtimeNode.InputProperties.Length) { return resolver; } Type expectedType = runtimeNode.InputProperties[index].ExpectedType; return AdaptResolverForExpectedType(resolver, expectedType); } private static bool IsFloatingPointType(Type type) { return type == typeof(float) || type == typeof(double) || type == typeof(decimal); } }