// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using EpicGames.Core; using EpicGames.UHT.Exporters.CodeGen; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; namespace EpicGames.UHT.Tables { /// /// Location where code will be injected /// public enum UhtCodeGeneratorInjectionLocation { /// /// Injection location will be in the generated.h after built-in code generation for the respective type /// Header, /// /// Injection location within the macros for the type. /// GeneratedMacro } /// /// Method attribute which defines a code generation injection behaviour. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class UhtCodeGeneratorInjectorAttribute : Attribute { /// /// The UhtType that the injector will be applied to. /// public required Type UhtType { get; set; } /// /// Specifies the injection location for the injector to run in. /// public required UhtCodeGeneratorInjectionLocation Location { get; set; } } internal record struct UhtCodeGeneratorInjectorDesire(Type UhtType, UhtCodeGeneratorInjectionLocation Location); /// /// Entrypoint for code generation injector /// internal delegate void UhtCodeGeneratorInjectorDelegate(StringBuilder builder, UhtType type, int leadingTabs, string eolSequence); /// /// Holds a set of injectors discovered via reflection /// public sealed class UhtCodeGeneratorInjectorTable { private readonly Dictionary?> _injectorsTable = new(); private readonly List _supportedLeafTypes; /// /// Constructs UhtCodeGeneratorInjectorTable /// public UhtCodeGeneratorInjectorTable() { _supportedLeafTypes = new List { typeof(UhtClass), typeof(UhtScriptStruct), }; } internal void Inject(StringBuilder builder, UhtType type, UhtCodeGeneratorInjectionLocation location) { string eolSequence; int leadingTabs; switch (location) { case UhtCodeGeneratorInjectionLocation.Header: eolSequence = "\r\n"; leadingTabs = 0; break; case UhtCodeGeneratorInjectionLocation.GeneratedMacro: eolSequence = " \\\r\n"; leadingTabs = 1; break; default: throw new ArgumentOutOfRangeException(nameof(location), location, "Unhandled injection location"); } UhtCodeGeneratorInjectorDesire desire = new(type.GetType(), location); if (_injectorsTable.TryGetValue(desire, out List? injectors)) { foreach (UhtCodeGeneratorInjectorDelegate injector in injectors!) { injector.Invoke(builder, type, leadingTabs, eolSequence); } } } internal void OnUhtCodeGeneratorInjectorAttribute(Type type, MethodInfo method, UhtCodeGeneratorInjectorAttribute attribute) { // Walk back from each support leaf types up to the declared type // If our leaf type IS of the declared type, then add a delegate for the handler of the leaf type // This allows for users to write injectors that pertain to multiple leaf types if required (ie. all UhtNumericProperty) foreach (Type supportedLeafType in _supportedLeafTypes) { if (attribute.UhtType.IsAssignableFrom(supportedLeafType)) { UhtCodeGeneratorInjectorDesire desire = new(supportedLeafType, attribute.Location); List? injectors; if (!_injectorsTable.TryGetValue(desire, out injectors)) { injectors = []; _injectorsTable.Add(desire, injectors); } injectors!.Add((UhtCodeGeneratorInjectorDelegate)Delegate.CreateDelegate(typeof(UhtCodeGeneratorInjectorDelegate), method)); } } } internal void Sort() { // Sort the delegates to ensure determinism across multiple runs foreach (List? injectors in _injectorsTable.Values) { if (injectors == null) { continue; } injectors.Sort((Lhs, Rhs) => { MethodInfo methodLhs = Lhs.Method; MethodInfo methodRhs = Rhs.Method; // Sort by module int moduleComparison = String.Compare(methodLhs.Module.Name, methodLhs.Module.Name, StringComparison.Ordinal); if (moduleComparison != 0) { return moduleComparison; } // Sort by declaring class int declaredClassComparison = String.Compare(methodLhs.DeclaringType!.Name, methodRhs.DeclaringType!.Name, StringComparison.Ordinal); if (declaredClassComparison != 0) { return declaredClassComparison; } // Sort by method name return String.Compare(methodLhs.Name, methodRhs.Name, StringComparison.Ordinal); }); } } } }