// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using EpicGames.Core; using EpicGames.UHT.Parsers; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; namespace EpicGames.UHT.Exporters.CodeGen { /// /// Helper class for collection where types exist in different define scope blocks /// /// public class UhtUsedDefineScopes where T : UhtType { private bool _first = true; private UhtDefineScope _soleScope = UhtDefineScope.Invalid; private UhtDefineScope _allScopes = UhtDefineScope.None; private uint _present = 0; /// /// Collection of instances /// public List Instances { get; } = new(); /// /// If true, there are no instances /// public bool IsEmpty => Instances.Count == 0; /// /// If true, at least one instance had no scope /// public bool HasNoneScope => HasScope(UhtDefineScope.None); /// /// If all types share the same scope, then the sole scope is that scope. The value will be Invalid if the /// instances have different scopes. /// public UhtDefineScope SoleScope => _soleScope; /// /// Collection of all scopes found in the types. If /// public UhtDefineScope AllScopes => _allScopes; /// /// If any instance has no scope, then None is returned. Otherwise AllScopes is returned. /// public UhtDefineScope NoneAwareScopes => HasNoneScope ? UhtDefineScope.None : AllScopes; /// /// Constructor with no initial instances /// public UhtUsedDefineScopes() { } /// /// Constructor with initial range of types /// /// Instances to initially add public UhtUsedDefineScopes(IEnumerable instances) { AddRange(instances); } /// /// Add an instance to the collection /// /// Instance to be added public void Add(T instance) { UhtDefineScope defineScope = instance.DefineScope; if (_first) { _soleScope = defineScope; _first = false; } else if (_soleScope != UhtDefineScope.Invalid && _soleScope != defineScope) { _soleScope = UhtDefineScope.Invalid; } _allScopes |= defineScope; _present |= ScopeToMask(defineScope); Instances.Add(instance); } /// /// Add a range of instances to the collection /// /// Collection of instances public void AddRange(IEnumerable instances) { foreach (T instance in instances) { Add(instance); } } /// /// Check to see if the given scope has instances /// /// Scope to test /// True if the scope has elements public bool HasScope(UhtDefineScope defineScope) { return (_present & ScopeToMask(defineScope)) != 0; } /// /// Update the list to by ordered by the define scope /// public void OrderByDefineScope() { Instances.SortBy(x => x.DefineScope); } /// /// Enumerate all of the used defined scopes /// /// Enumeration public IEnumerable EnumerateDefinedScopes() { ulong present = _present; for (int index = 0; present != 0; ++index, present >>= 1) { if ((present & 1) != 0) { yield return (UhtDefineScope)index; } } } /// /// Enumerate all of the used defined scopes /// /// Enumeration public IEnumerable EnumerateDefinedScopesNoneAtEnd() { ulong present = _present >> 1; for (int index = 1; present != 0; ++index, present >>= 1) { if ((present & 1) != 0) { yield return (UhtDefineScope)index; } } // At this time, we alway return None yield return UhtDefineScope.None; } private static uint ScopeToMask(UhtDefineScope defineScope) { return (uint)1 << (int)defineScope; } } /// /// Helper extensions when working with defined scope collections. /// /// This class helps with producing consistent and correct code that works with the DefineScope element in UhtType. /// /// In generated header files, it supports two flavors of macro definitions used to generate the body macro. /// /// The multi macro style will generate one macro for each unique combination of DefineScope found. Only the instances /// that match the DefineScope will be placed in that macro. The macros will be generated in such a way that they /// will always be present and each much be included in the generated body macro. This style is used to generate /// declarations as needed. /// /// The single macro style will generate a single macro, but that macro will appear multiple times depending on /// each combination of the DefineScope combinations required. Each macro will have a complete set of instances /// filtered by the DefineScope. This style is used to populate such things as enum definitions needed by the engine. /// /// In generated cpp files, there is support for enumerating through all the instances and emitting the appropriate /// #if block to include instances based on their DefineScope. /// internal static class UhtUsedDefineScopesExtensions { /// /// Append a macro scoped macro /// /// Destination builder /// Which set of scope names will be used /// Specified scope /// Action to invoke to append an instance /// String builder public static StringBuilder AppendScoped(this StringBuilder builder, UhtDefineScopeNames defineScopeNames, UhtDefineScope defineScope, Action appendAction) { using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, defineScope); appendAction(builder); return builder; } /// /// Append multiple for the given collection of scopes /// /// Destination builder /// Defined scope instances /// Which set of scope names will be used /// Action to invoke to append an instance /// String builder public static StringBuilder AppendMultiScoped(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action> appendAction) where T : UhtType { foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes()) { AppendScoped(builder, defineScopeNames, defineScope, builder => appendAction(builder, instances.Instances.Where(x => x.DefineScope == defineScope))); } return builder; } /// /// Append a macro scoped macro /// /// Destination builder /// Which set of scope names will be used /// Specified scope /// Header code generator /// Output type owning the instances /// Macro being created /// If true, include such things as _EOD onto the macro name /// Action to invoke to append an instance /// String builder public static StringBuilder AppendScopedMacro(this StringBuilder builder, UhtDefineScopeNames defineScopeNames, UhtDefineScope defineScope, UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, bool includeSuffix, Action appendAction) { using (UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, defineScope)) { using (UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, defineScope, includeSuffix)) { appendAction(builder); } // We can skip writing the macros if there are no properties to declare, as the 'if' and 'else' would be the same if (defineScope != UhtDefineScope.None) { // Trim the extra newlines added after the macro generator if (builder.Length > 4 && builder[^4] == '\r' && builder[^3] == '\n' && builder[^2] == '\r' && builder[^1] == '\n') { builder.Length -= 4; } builder.AppendElsePreprocessor(defineScope, defineScopeNames); using UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, defineScope, includeSuffix); // Empty macro } } if (defineScope != UhtDefineScope.None) { builder.Append("\r\n\r\n"); } return builder; } /// /// Append multi macros for the given collection of scopes /// /// Destination builder /// Defined scope instances /// Which set of scope names will be used /// Header code generator /// Output type owning the instances /// Macro being created /// Action to invoke to append an instance /// String builder public static StringBuilder AppendMultiScopedMacros(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, Action> appendAction) where T : UhtType { foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes()) { AppendScopedMacro(builder, defineScopeNames, defineScope, generator, outerType, macroSuffix, true, builder => appendAction(builder, instances.Instances.Where(x => x.DefineScope == defineScope))); } return builder; } /// /// Append the macro definitions requested /// /// Destination builder /// Defined scope instances /// Header code generator /// Output type owning the instances /// Macro being created /// String builder public static StringBuilder AppendMultiMacroRefs(this StringBuilder builder, UhtUsedDefineScopes instances, UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix) where T : UhtType { foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes()) { builder.Append('\t').AppendMacroName(generator, outerType, macroSuffix, defineScope).Append(" \\\r\n"); } return builder; } /// /// Append a single macro, where the macro -same- definition can exist inside of define scopes where /// the instances contained are all instances that would be satisfied by a scope. /// /// Destination builder /// Defined scope instances /// Which set of scope names will be used /// Header code generator /// Output type owning the instances /// Macro being created /// Action to invoke to append an instance /// String builder public static StringBuilder AppendSingleMacro(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, Action> appendAction) where T : UhtType { if (instances.SoleScope == UhtDefineScope.None) { using UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, UhtDefineScope.None); appendAction(builder, instances.Instances); } else { bool first = true; foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopesNoneAtEnd()) { if (first) { builder.AppendIfPreprocessor(defineScope, defineScopeNames); first = false; } else if (defineScope != UhtDefineScope.None) { builder.AppendElseIfPreprocessor(defineScope, defineScopeNames); } else { builder.Append("#else\r\n"); } using (UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, UhtDefineScope.None)) { appendAction(builder, instances.Instances.Where(x => (x.DefineScope & ~defineScope) == 0)); } if (builder.Length > 4 && builder[^4] == '\r' && builder[^3] == '\n' && builder[^2] == '\r' && builder[^1] == '\n') { builder.Length -= 4; } } builder.Append("#endif\r\n\r\n\r\n"); } return builder; } /// /// Invoke the append action if any types are present. If all types are from the same define scope, then /// it will be wrapped with and #if block. /// /// Destination builder /// Defined scope instances /// Names to use when outputting the scope /// Action to invoke /// String builder public static StringBuilder AppendIfInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action appendAction) where T : UhtType { if (!instances.IsEmpty) { using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, instances.NoneAwareScopes); appendAction(builder); } return builder; } /// /// Append each instance to the builder /// /// Destination builder /// Defined scope instances /// Names to use when outputting the scope /// Action to invoke /// String builder public static StringBuilder AppendInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action appendAction) where T : UhtType { return builder.AppendInstances(instances, defineScopeNames, null, appendAction, null); } /// /// Append each instance to the builder /// /// Destination builder /// Defined scope instances /// Names to use when outputting the scope /// Action to invoke prior to first instance /// Action to invoke /// Action to invoke following all instances /// String builder public static StringBuilder AppendInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action? preambleAction, Action appendAction, Action? postambleAction) where T : UhtType { if (!instances.IsEmpty) { using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, instances.SoleScope); preambleAction?.Invoke(builder); foreach (T instance in instances.Instances) { blockEmitter.Set(instance.DefineScope); appendAction(builder, instance); } // Make sure the postable run with the proper initial scope blockEmitter.Set(instances.SoleScope); postambleAction?.Invoke(builder); } return builder; } /// /// Append the given array list and count as arguments to a structure constructor /// /// Destination builder /// Collected instances /// Names to use when outputting the scope /// Name of the statics section /// The name of the arrray /// Number of tabs to start the line /// Text to end the line /// Destination builder public static StringBuilder AppendArrayPtrAndCountLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType { UhtDefineScope noneAwareScopes = instances.NoneAwareScopes; // We have no instances in the collection, the list will always be empty if (instances.IsEmpty) { builder .AppendTabs(tabs) .Append("nullptr, 0") .Append(endl); } // If we have any instances that have no scope, then we can always just reference the array else if (noneAwareScopes == UhtDefineScope.None) { builder .AppendTabs(tabs) .AppendCppName(staticsName, arrayName) .Append(", UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append(')') .Append(endl); } // If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros else if (noneAwareScopes == UhtDefineScope.EditorOnlyData) { switch (defineScopeNames) { case UhtDefineScopeNames.Standard: builder .AppendTabs(tabs) .Append("IF_WITH_EDITORONLY_DATA(") .AppendCppName(staticsName, arrayName) .Append(", nullptr), IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append("), 0)") .Append(endl); break; case UhtDefineScopeNames.WithEditor: builder .AppendTabs(tabs) .Append("IF_WITH_EDITOR(") .AppendCppName(staticsName, arrayName) .Append(", nullptr), IF_WITH_EDITOR(UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append("), 0)") .Append(endl); break; default: throw new UhtIceException("Unexpected define scope names value"); } } // Otherwise we have many different scopes but no none scope, must generate #if block else { builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .AppendCppName(staticsName, arrayName) .Append(", UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append(')') .Append(endl); builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .Append("nullptr, 0") .Append(endl); builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames); } return builder; } /// /// Append the given array list as arguments to a structure constructor /// /// Destination builder /// Collected instances /// Names to use when outputting the scope /// Name of the statics section /// The name of the arrray /// Number of tabs to start the line /// Text to end the line /// Destination builder public static StringBuilder AppendArrayPtrLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType { UhtDefineScope noneAwareScopes = instances.NoneAwareScopes; // We have no instances in the collection, the list will always be empty if (instances.IsEmpty) { builder .AppendTabs(tabs) .Append("nullptr") .Append(endl); } // If we have any instances that have no scope, then we can always just reference the array else if (noneAwareScopes == UhtDefineScope.None) { builder .AppendTabs(tabs) .AppendCppName(staticsName, arrayName) .Append(endl); } // If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros else if (noneAwareScopes == UhtDefineScope.EditorOnlyData) { switch (defineScopeNames) { case UhtDefineScopeNames.Standard: builder .AppendTabs(tabs) .Append("IF_WITH_EDITORONLY_DATA(") .AppendCppName(staticsName, arrayName) .Append(", nullptr)") .Append(endl); break; case UhtDefineScopeNames.WithEditor: builder .AppendTabs(tabs) .Append("IF_WITH_EDITOR(") .AppendCppName(staticsName, arrayName) .Append(", nullptr)") .Append(endl); break; default: throw new UhtIceException("Unexpected define scope names value"); } } // Otherwise we have many different scopes but no none scope, must generate #if block else { builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .AppendCppName(staticsName, arrayName) .Append(endl); builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .Append("nullptr") .Append(endl); builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames); } return builder; } /// /// Append the given array count as arguments to a structure constructor /// /// Destination builder /// Collected instances /// Names to use when outputting the scope /// Name of the statics section /// The name of the arrray /// Number of tabs to start the line /// Text to end the line /// Destination builder public static StringBuilder AppendArrayCountLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType { UhtDefineScope noneAwareScopes = instances.NoneAwareScopes; // We have no instances in the collection, the list will always be empty if (instances.IsEmpty) { builder .AppendTabs(tabs) .Append('0') .Append(endl); } // If we have any instances that have no scope, then we can always just reference the array else if (noneAwareScopes == UhtDefineScope.None) { builder .AppendTabs(tabs) .Append("UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append(')') .Append(endl); } // If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros else if (noneAwareScopes == UhtDefineScope.EditorOnlyData) { switch (defineScopeNames) { case UhtDefineScopeNames.Standard: builder .AppendTabs(tabs) .Append("IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append("), 0)") .Append(endl); break; case UhtDefineScopeNames.WithEditor: builder .AppendTabs(tabs) .Append("IF_WITH_EDITOR(UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append("), 0)") .Append(endl); break; default: throw new UhtIceException("Unexpected define scope names value"); } } // Otherwise we have many different scopes but no none scope, must generate #if block else { builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .Append("UE_ARRAY_COUNT(") .AppendCppName(staticsName, arrayName) .Append(')') .Append(endl); builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames); builder .AppendTabs(tabs) .Append('0') .Append(endl); builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames); } return builder; } /// /// Start an #if block with the given scope /// /// String builder /// Scope /// Which set of scope names will be used /// String builder public static StringBuilder AppendIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard) { if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid) { builder.Append("#if ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n"); } return builder; } /// /// Start an #else block with the given scope /// /// String builder /// Scope /// Which set of scope names will be used /// String builder public static StringBuilder AppendElsePreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard) { if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid) { builder.Append("#else // ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n"); } return builder; } /// /// Start an #elif block with the given scope /// /// String builder /// Scope /// Which set of scope names will be used /// String builder public static StringBuilder AppendElseIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard) { if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid) { builder.Append("#elif ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n"); } return builder; } /// /// Start an #endif block with the given scope /// /// String builder /// Scope /// Which set of scope names will be used /// String builder public static StringBuilder AppendEndIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard) { if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid) { builder.Append("#endif // ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n"); } return builder; } /// /// Append scope expression (i.e. WITH_X && WITH_Y && ...) /// /// String builder /// Scope /// Which set of scope names will be used /// String builder public static StringBuilder AppendScopeExpression(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames) { if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid) { int scopes = (int)defineScope; bool needAnd = false; for (int mask = 1; mask <= scopes; mask <<= 1) { if ((mask & scopes) != 0) { if (needAnd) { builder.Append(" && "); } builder.Append(((UhtDefineScope)(mask & scopes)).GetCompilerDirective(defineScopeNames).GetCompilerDirectiveText()); needAnd = true; } } } return builder; } private static StringBuilder AppendCppName(this StringBuilder builder, string? staticsName, string arrayName) { if (!String.IsNullOrEmpty(staticsName)) { builder.Append(staticsName).Append("::"); } return builder.Append(arrayName); } } }