// 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 class to build linked lists of objects which are defined in different scopes.
/// e.g. properties of a class, where some properties are only present when WITH_EDITORONLY_DATA is defined
///
///
public class UhtDefineScopeListBuilder : IDisposable
where T: UhtType
{
///
/// Initialize the builder with an empty 'head' link.
///
public UhtDefineScopeListBuilder(UhtDefineScope scope)
{
Head = new UhtDefineScopeLink(scope);
_pending.Add(Head);
}
///
/// Link pointing to the first object in the list for each relevant define scope.
///
public UhtDefineScopeLink Head { get; }
///
/// Add the given object as the next object after any preceding object which does not yet have a following object
/// in the scope(s) the new object is defined in.
///
/// Object to append to the list
public UhtDefineScopeLink Add(T inNext)
{
foreach (UhtDefineScopeLink link in _pending)
{
link.Add(inNext);
}
_pending.RemoveAll(x => x.IsComplete);
UhtDefineScopeLink newLink = new(inNext.DefineScope);
_pending.Add(newLink);
return newLink;
}
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Explicitly terminate each pending link with null
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (UhtDefineScopeLink link in _pending)
{
link.Terminate();
}
}
}
private readonly List> _pending = [];
}
///
/// This class can be used as a field in UhtTypes to perform as an intrusive linked list 'next' pointer to
/// another object of the same type, resolving to a different object depending on which preprocessor definitions
/// are set.
///
///
public class UhtDefineScopeLink where T: UhtType
{
///
/// Initialize this link with the declaration scope of the object holding the link.
///
///
public UhtDefineScopeLink(UhtDefineScope thisScope)
{
_thisScope = thisScope;
_completionMask = ToCompletionMask(thisScope);
// A next node in scope 'None' always completes this link regardless of _thisScope
_completionMask |= ToCompletionMask(UhtDefineScope.None);
}
///
/// Try to add a new element following this one in the linked list.
/// If there is already a next element for every combination of scopes for which this and the candidate are defined,
/// the new element will not be added.
/// If, after adding the new element, there is a next element for every combination of scopes in which this is defined,
/// this link will return true from IsComplete.
///
///
public void Add(T inNext)
{
uint addedMask = ToAddedMask(inNext.DefineScope);
if ((addedMask & _addedScopes) == 0)
{
_addedScopes |= addedMask;
_next.Add(inNext);
uint completionCandidateMask = ToCompletionMask(inNext.DefineScope);
if ((completionCandidateMask & _completionMask) != 0)
{
IsComplete = true;
}
}
}
///
/// Must be called on an incomplete link. Terminates the link explicitly with nullptr to handle the case where there
/// is a next link for some scopes, but none for all scopes the link is defined in.
///
public void Terminate()
{
if (IsComplete)
{
throw new InvalidOperationException("Attemping to terminate an already-complete link. Such links should be removed from the pending list by the builder.");
}
_next.Add(null);
}
///
/// Returns true if there is a next element for every combination of scopes which this is defined.
///
public bool IsComplete { get; private set; }
///
/// Returns the scope of the first object in the list, or UhtDefineScope.None.
///
public UhtDefineScope InitialScope => _next.First()?.DefineScope ?? UhtDefineScope.None;
///
/// Returns all objects which follow this object in some relevant scope.
/// Only the last element may be null.
///
public IReadOnlyList Next => _next;
// Ordered list of next objects with differing scopes
private readonly List _next = [];
// The scope in which this link is defined
private readonly UhtDefineScope _thisScope;
// When any the bits in this are matched by the bits in the next added item, this link is complete
private readonly uint _completionMask;
// Tracks which scopes have been added with Add
private uint _addedScopes = 0;
// One of two masks used by this class, this is used for _completionMask to track when this link
// has a next node for every situation in which it is defined
private static uint ToCompletionMask(UhtDefineScope scope)
{
// Shift other scopes by 1 so that 'None' is assigned a bit as well
return scope == UhtDefineScope.None ? 1 : (uint)scope << 1;
}
// The second mask used by this class, this is used to track when a next node is already present for
// a given scope, so a candidate does not need to be added
private uint ToAddedMask(UhtDefineScope scope)
{
// Add the scope if this link to the mask because so that e.g.
// thisScope == UhtDefineScope.EditorOnly
// inNext.DefineScope = UhtDefineScope.VerseVM
// if the next link to be added is UhtDefineScope.EditorOnly|UhtDefineScope.VerseVM, we do not need to add it
// because under the preprocessor state UhtDefineScope.EditorOnly|UhtDefineScope.VerseVM, a next node is defined already.
return (uint)1 << (int)(scope | _thisScope);
}
}
///
/// 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 an array view construction expression for the given instances
///
/// 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 AppendArrayView(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("{}")
.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("MakeConstArrayView(")
.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(MakeConstArrayView(")
.AppendCppName(staticsName, arrayName)
.Append("), {})")
.Append(endl);
break;
case UhtDefineScopeNames.WithEditor:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITOR(MakeConstArrayView(")
.AppendCppName(staticsName, arrayName)
.Append("), {})")
.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.Append("\r\n");
builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append("MakeArrayView(")
.AppendCppName(staticsName, arrayName)
.Append(')')
.Append(endl);
builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append("{}")
.Append(endl);
builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames);
}
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;
}
///
/// Append a link which resolves to a different object under different preprocessor definitions
///
///
///
///
///
///
///
///
///
public static StringBuilder AppendScopeLink(this StringBuilder builder, UhtDefineScopeLink? link, UhtDefineScopeNames defineScopeNames, IUhtPropertyMemberContext context, int tabs, string? prefix, string? endl)
{
// No link or link contains nothing
if (link is null)
{
return builder.AppendTabs(tabs).Append(prefix).Append("nullptr").Append(endl);
}
using (UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, link.InitialScope))
{
foreach (UhtProperty? next in link.Next)
{
blockEmitter.Set(next?.DefineScope ?? UhtDefineScope.None);
builder.AppendTabs(tabs).Append(prefix);
builder.AppendConstInitMemberPtr(next, context, next?.EngineName, "", 0, endl);
}
}
return builder;
}
///
/// Append a link which resolves to a different object under different preprocessor definitions
///
///
///
///
///
///
///
///
///
public static StringBuilder AppendScopeLink(this StringBuilder builder, UhtDefineScopeLink? link, UhtDefineScopeNames defineScopeNames, IUhtPropertyMemberContext context, int tabs, string? prefix, string? endl)
{
// No link or link contains nothing
if (link is null)
{
return builder.AppendTabs(tabs).Append(prefix).Append("nullptr").Append(endl);
}
using (UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, link.InitialScope))
{
foreach (UhtFunction? next in link.Next)
{
blockEmitter.Set(next?.DefineScope ?? UhtDefineScope.None);
builder.AppendTabs(tabs).Append(prefix);
builder.AppendConstInitSingletonRef(context, next).Append(endl);
}
}
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);
}
}
}