// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using EpicGames.Core;
using EpicGames.UHT.Exporters.CodeGen;
using EpicGames.UHT.Types;
namespace EpicGames.UHT.Utils
{
///
/// Interface used to collect all the objects referenced by a given type.
/// Not all types such as UhtPackage and UhtHeaderFile support collecting
/// references due to assorted reasons.
///
public interface IUhtReferenceCollector
{
///
/// Add a cross module reference to a given object type.
///
/// Object/type being referenced
/// Type of reference required
void AddCrossModuleReference(UhtObject? obj, UhtSingletonType type);
///
/// Add an object declaration
///
/// Object in question
/// Type of reference required
void AddDeclaration(UhtObject obj, UhtSingletonType type);
///
/// Add a field as a singleton for exporting
///
/// Field to be added
void AddSingleton(UhtField field);
///
/// Add a field as a type being exported
///
/// Field to be added
void AddExportType(UhtField field);
///
/// Add a forward declaration. This is the preferred way of adding a forward declaration since the exporter will
/// handle all the formatting requirements.
///
///
void AddForwardDeclaration(UhtField field);
///
/// Add a forward declaration. The string can contain multiple declarations but must only exist on one line.
///
/// The namespace to place the string
/// The declarations to add
void AddForwardDeclaration(UhtNamespace? namespaceObj, string? declaration);
}
///
/// Maintains a list of referenced object indices.
///
public class UhtUniqueReferenceCollection
{
private const int Shift = 2;
private const int Mask = 0x3;
///
/// Returns whether there is anything in this collection
///
public bool IsEmpty => Uniques.Count == 0;
///
/// List of all unique reference keys. Use UngetKey to get the object index and the flag.
///
private HashSet Uniques { get; } = new HashSet();
///
/// Return an encoded key that represents the object and type of reference.
/// If the object has the alternate object set (i.e. native interfaces), then
/// that object's index is used to generate the key.
///
/// Object being referenced
/// The type of reference required for linking.
/// Integer key value.
public static int GetKey(UhtObject obj, UhtSingletonType type)
{
return obj.AlternateObject != null ? GetKey(obj.AlternateObject, type) : (obj.ObjectTypeIndex << Shift) | ((int)type);
}
///
/// Given a key, return the object index and registered flag
///
/// The key in question
public static (int objectIndex, UhtSingletonType type) UngetKey(int key)
{
return (key >> Shift, (UhtSingletonType)(key & Mask));
}
///
/// Add the given object to the references
///
/// Object to be added
/// Type of reference required
public void Add(UhtObject? obj, UhtSingletonType type)
{
if (obj != null)
{
Uniques.Add(GetKey(obj, type));
}
}
///
/// Return the collection of references sorted by the API string returned by the delegate.
///
/// Function to invoke to return the requested object API string, taking the object index and the UhtSingletonType.
/// Read only memory region of all the string.
public IReadOnlyList GetSortedReferences(Func referenceToString)
{
// Collect the unsorted array
List sorted = new(Uniques.Count);
foreach(int key in Uniques)
{
(int objectIndex, UhtSingletonType type) = UngetKey(key);
if (referenceToString(objectIndex, type) is string decl)
{
sorted.Add(decl);
}
}
// Sort the array
sorted.Sort(StringComparerUE.OrdinalIgnoreCase);
// Remove duplicates. In some instances the different keys might return the same string.
// This removes those duplicates
if (sorted.Count > 1)
{
int priorOut = 0;
for (int index = 1; index < sorted.Count; ++index)
{
if (sorted[index] != sorted[priorOut])
{
++priorOut;
sorted[priorOut] = sorted[index];
}
}
if (priorOut < sorted.Count - 1)
{
sorted.RemoveRange(priorOut + 1, sorted.Count - priorOut - 1);
}
}
return sorted;
}
}
///
/// Standard implementation of the reference collector interface
///
public class UhtReferenceCollector : IUhtReferenceCollector
{
///
/// Collection of unique cross module references
///
public UhtUniqueReferenceCollection CrossModule { get; set; } = new();
///
/// Collection of unique declarations
///
public UhtUniqueReferenceCollection Declaration { get; set; } = new();
///
/// Collection of singletons
///
public List Singletons { get; } = [];
///
/// Collection of types to export
///
public List ExportTypes { get; } = [];
///
/// Collection of fields needing forward declarations
///
public HashSet ForwardDeclarations { get; } = [];
///
/// Collection of forward declarations in text form
///
public HashSet ForwardDeclarationStrings { get; } = [];
///
/// Collection of referenced headers
///
public HashSet ReferencedHeaders { get; } = [];
///
public void AddCrossModuleReference(UhtObject? obj, UhtSingletonType type)
{
CrossModule.Add(obj, type);
if (obj != null && obj is not UhtPackage && type == UhtSingletonType.Registered)
{
ReferencedHeaders.Add(obj.HeaderFile);
}
}
///
public void AddDeclaration(UhtObject obj, UhtSingletonType type)
{
Declaration.Add(obj, type);
}
///
public void AddSingleton(UhtField field)
{
Singletons.Add(field);
}
///
public void AddExportType(UhtField field)
{
ExportTypes.Add(field);
}
///
public void AddForwardDeclaration(UhtField field)
{
ForwardDeclarations.Add(field);
}
///
public void AddForwardDeclaration(UhtNamespace? namespaceObj, string? declaration)
{
if (!String.IsNullOrEmpty(declaration))
{
if (namespaceObj == null || namespaceObj.IsGlobal)
{
ForwardDeclarationStrings.Add(declaration);
}
else
{
// This is hardly used so just wrap the declaration in the namespace instead of
// trying to group them all together in a multiple line namespace declaration.
StringBuilder builder = new();
namespaceObj.AppendSingleLine(builder, (builder) => builder.Append(declaration));
}
}
}
}
}