diff --git a/Source/Mocha.Common/Utils/TaskPool.cs b/Source/Mocha.Common/Utils/TaskPool.cs new file mode 100644 index 00000000..19a2c443 --- /dev/null +++ b/Source/Mocha.Common/Utils/TaskPool.cs @@ -0,0 +1,62 @@ +namespace Mocha.Common; + +public sealed class TaskPool +{ + public delegate Task TaskCallback( T[] taskQueue ); + public delegate void Continuation(); + + private readonly Task[] _tasks; + + private TaskPool( T[] queue, TaskCallback taskStart ) + { + var maxTasks = (int)(Environment.ProcessorCount * 0.5) - 1; + var batchSize = queue.Length / maxTasks; + + if ( batchSize == 0 ) + batchSize = 1; + + var batched = queue + .Select( ( value, index ) => new + { + Value = value, + Index = index + } ) + .GroupBy( p => p.Index / batchSize ) + .Select( g => g.Select( p => p.Value ).ToArray() ) + .ToArray(); + + _tasks = new Task[batched.Length]; + for ( int i = 0; i < batched.Length; i++ ) + { + var taskQueue = batched[i]; + + _tasks[i] = Task.Run( () => taskStart( taskQueue ) ); + } + } + + public static TaskPool Dispatch( T[] queue, TaskCallback taskStart ) + { + return new TaskPool( queue, taskStart ); + } + + public static TaskPool Dispatch( IEnumerable queue, TaskCallback taskStart ) + { + return new TaskPool( queue.ToArray(), taskStart ); + } + + public TaskPool Then( Continuation continuation ) + { + Task.WhenAll( _tasks ).ContinueWith( t => continuation() ).Wait(); + return this; + } + + public async Task WaitForCompleteAsync() + { + await Task.WhenAll( _tasks ); + } + + public void WaitForComplete() + { + WaitForCompleteAsync().Wait(); + } +} diff --git a/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs b/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs index eeb6a576..0f4ca016 100644 --- a/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs +++ b/Source/MochaTool.InteropGen/CodeGen/ManagedCodeGenerator.cs @@ -66,8 +66,8 @@ internal static string GenerateCode( IEnumerable units ) { switch ( unit ) { - case Class c when c.IsNamespace: - GenerateNamespaceCode( writer, c ); + case Namespace n: + GenerateNamespaceCode( writer, n ); break; case Class c: GenerateClassCode( writer, c ); @@ -95,10 +95,11 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) // // Gather everything we need into nice lists // - List decls = new(); + var decls = new string[c.Methods.Length]; - foreach ( var method in c.Methods ) + for ( var i = 0; i < decls.Length; i++ ) { + var method = c.Methods[i]; var returnType = Utils.GetManagedType( method.ReturnType ); var name = method.Name; @@ -128,7 +129,7 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) // any parameters. The return type is the last type argument passed to // the delegate. // - decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{c.Name}_{name}MethodPtr;" ); + decls[i] = $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{c.Name}_{name}MethodPtr;"; } // @@ -269,15 +270,16 @@ private static void GenerateStructCode( IndentedTextWriter writer, Struct s ) /// /// The writer to append the code to. /// The namespace to write code for. - private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) + private static void GenerateNamespaceCode( IndentedTextWriter writer, Namespace ns ) { // // Gather everything we need into nice lists // - List decls = new(); + var decls = new string[ns.Methods.Length]; - foreach ( var method in ns.Methods ) + for ( var i = 0; i < decls.Length; i++ ) { + var method = ns.Methods[i]; var returnType = Utils.GetManagedType( method.ReturnType ); var name = method.Name; @@ -298,7 +300,7 @@ private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) // any parameters. The return type is the last type argument passed to // the delegate. // - decls.Add( $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{ns.Name}_{name}MethodPtr;" ); + decls[i] = $"private static {delegateSignature} _{name} = ({delegateSignature})Mocha.Common.Global.UnmanagedArgs.__{ns.Name}_{name}MethodPtr;"; } // diff --git a/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs b/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs index 5721ae72..2f97b6e0 100644 --- a/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs +++ b/Source/MochaTool.InteropGen/CodeGen/NativeCodeGenerator.cs @@ -38,19 +38,18 @@ internal static string GenerateCode( string headerPath, IEnumerable units writer.WriteLine(); writer.WriteLine( "#pragma once" ); - writer.WriteLine( $"#include \"..\\{headerPath}\"" ); + writer.WriteLine( $"#include \"{Path.Combine( "..", headerPath )}\"" ); writer.WriteLine(); foreach ( var unit in units ) { - if ( unit is not Class c ) - continue; - - if ( c.IsNamespace ) - GenerateNamespaceCode( writer, c ); - else + if ( unit is Namespace n ) + GenerateNamespaceCode( writer, n ); + else if ( unit is Class c ) GenerateClassCode( writer, c ); + else + continue; writer.WriteLine(); } @@ -116,7 +115,7 @@ private static void GenerateClassCode( IndentedTextWriter writer, Class c ) /// /// The writer to append the code to. /// The namespace to write code for. - private static void GenerateNamespaceCode( IndentedTextWriter writer, Class ns ) + private static void GenerateNamespaceCode( IndentedTextWriter writer, Namespace ns ) { foreach ( var method in ns.Methods ) { diff --git a/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs b/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs index 0a0056b9..78563cec 100644 --- a/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs +++ b/Source/MochaTool.InteropGen/Extensions/ILoggerExtensions.cs @@ -16,6 +16,15 @@ internal static partial class ILoggerExtensions Message = "Generating C# <--> C++ interop code..." )] internal static partial void LogIntro( this ILogger logger ); + /// + /// Logs an error about missing required args. + /// + /// The instance to log to. + [LoggerMessage( EventId = -1, + Level = LogLevel.Error, + Message = "The base directory to generate code from is required to run this tool" )] + internal static partial void LogIntroError( this ILogger logger ); + /// /// Logs a timed operation to the user. /// diff --git a/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj b/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj index 812e74a8..5181a0fe 100644 --- a/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj +++ b/Source/MochaTool.InteropGen/MochaTool.InteropGen.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/Source/MochaTool.InteropGen/Parsing/Class.cs b/Source/MochaTool.InteropGen/Parsing/Class.cs index 0ec9c643..1f9cdee3 100644 --- a/Source/MochaTool.InteropGen/Parsing/Class.cs +++ b/Source/MochaTool.InteropGen/Parsing/Class.cs @@ -3,14 +3,12 @@ namespace MochaTool.InteropGen.Parsing; /// -/// Represents a class or namespace in C++. +/// Represents a class in C++. /// -internal sealed class Class : IUnit +internal sealed class Class : IContainerUnit { /// public string Name { get; } - /// - public bool IsNamespace { get; } /// public ImmutableArray Fields { get; } @@ -20,14 +18,12 @@ internal sealed class Class : IUnit /// /// Initializes a new instance of . /// - /// The name of the class or namespace. - /// Whether or not it is a class or namespace. + /// The name of the class. /// All of the fields that are contained. /// All of the methods that are contained. - private Class( string name, bool isNamespace, in ImmutableArray fields, in ImmutableArray methods ) + private Class( string name, in ImmutableArray fields, in ImmutableArray methods ) { Name = name; - IsNamespace = isNamespace; Fields = fields; Methods = methods; @@ -40,7 +36,7 @@ private Class( string name, bool isNamespace, in ImmutableArray fields /// A new instance of the with the fields given. internal Class WithFields( in ImmutableArray fields ) { - return new Class( Name, IsNamespace, fields, Methods ); + return new Class( Name, fields, Methods ); } /// @@ -50,7 +46,7 @@ internal Class WithFields( in ImmutableArray fields ) /// A new instance of the with the methods given. internal Class WithMethods( in ImmutableArray methods ) { - return new Class( Name, IsNamespace, Fields, methods ); + return new Class( Name, Fields, methods ); } /// @@ -60,9 +56,9 @@ public override string ToString() } /// - IUnit IUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); + IContainerUnit IContainerUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); /// - IUnit IUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); + IContainerUnit IContainerUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); /// /// Returns a new instance of . @@ -71,20 +67,10 @@ public override string ToString() /// The fields contained in the class. /// The methods contained in the class. /// A new instance of . - internal static Class NewClass( string name, in ImmutableArray fields, in ImmutableArray methods ) + internal static Class Create( string name, in ImmutableArray fields, in ImmutableArray methods ) { - return new Class( name, false, fields, methods ); + return new Class( name, fields, methods ); } - /// - /// Returns a new instance of as a namespace. - /// - /// The name of the namespace. - /// The fields contained in the namespace. - /// The methods contained in the namespace. - /// A new instance of as a namespace. - internal static Class NewNamespace( string name, in ImmutableArray fields, in ImmutableArray methods ) - { - return new Class( name, true, fields, methods ); - } + } diff --git a/Source/MochaTool.InteropGen/Parsing/ContainerBuilder.cs b/Source/MochaTool.InteropGen/Parsing/ContainerBuilder.cs new file mode 100644 index 00000000..5b3b0a8b --- /dev/null +++ b/Source/MochaTool.InteropGen/Parsing/ContainerBuilder.cs @@ -0,0 +1,81 @@ +using System.Collections.Immutable; + +namespace MochaTool.InteropGen.Parsing; + +/// +/// Builds a representation of a C++ container. +/// +internal sealed class ContainerBuilder +{ + /// + /// The type of container that is being built. + /// + internal ContainerType Type { get; } + /// + /// The name of the container. + /// + internal string Name { get; } + + /// + /// Whether or not the container has any items within it. + /// + internal bool IsEmpty => fields.Count == 0 && methods.Count == 0; + + private readonly ImmutableArray.Builder fields; + private readonly ImmutableArray.Builder methods; + + /// + /// Initializes a new instance of . + /// + /// The type of the container. + /// The name of the container. + internal ContainerBuilder( ContainerType type, string name ) + { + Type = type; + Name = name; + + fields = ImmutableArray.CreateBuilder(); + methods = ImmutableArray.CreateBuilder(); + } + + /// + /// Adds a new field to the container. + /// + /// The field to add. + /// The same instance of . + internal ContainerBuilder AddField( Variable field ) + { + fields.Add( field ); + return this; + } + + /// + /// Adds a new method to the container. + /// + /// The method to add. + /// The same instance of . + internal ContainerBuilder AddMethod( Method method ) + { + methods.Add( method ); + return this; + } + + /// + /// Constructs a new instance of the container. + /// + /// A new container instance that implements the interface. + /// Thrown when trying to build a container with an invalid type. + internal IContainerUnit Build() + { + fields.Capacity = fields.Count; + methods.Capacity = methods.Count; + + return Type switch + { + ContainerType.Class => Class.Create( Name, fields.MoveToImmutable(), methods.MoveToImmutable() ), + ContainerType.Namespace => Namespace.Create( Name, fields.MoveToImmutable(), methods.MoveToImmutable() ), + ContainerType.Struct => Struct.Create( Name, fields.MoveToImmutable(), methods.MoveToImmutable() ), + _ => throw new ArgumentOutOfRangeException() + }; + } +} diff --git a/Source/MochaTool.InteropGen/Parsing/ContainerType.cs b/Source/MochaTool.InteropGen/Parsing/ContainerType.cs new file mode 100644 index 00000000..0e257839 --- /dev/null +++ b/Source/MochaTool.InteropGen/Parsing/ContainerType.cs @@ -0,0 +1,12 @@ +namespace MochaTool.InteropGen.Parsing; + +/// +/// Defines a type of container defined in C++. +/// +internal enum ContainerType : byte +{ + Class, + Namespace, + Struct, + Invalid = 255 +} diff --git a/Source/MochaTool.InteropGen/Parsing/IContainerUnit.cs b/Source/MochaTool.InteropGen/Parsing/IContainerUnit.cs new file mode 100644 index 00000000..4fc72c3e --- /dev/null +++ b/Source/MochaTool.InteropGen/Parsing/IContainerUnit.cs @@ -0,0 +1,31 @@ +using System.Collections.Immutable; + +namespace MochaTool.InteropGen.Parsing; + +/// +/// Defines a container declaration in C++. +/// +internal interface IContainerUnit : IUnit +{ + /// + /// All of the fields contained in the . + /// + ImmutableArray Fields { get; } + /// + /// All of the methods contained in the . + /// + ImmutableArray Methods { get; } + + /// + /// Returns a new instance of the with the fields given. + /// + /// The new fields to place in the instance. + /// A new instance of the with the fields given. + IContainerUnit WithFields( in ImmutableArray fields ); + /// + /// Returns a new instance of the with the methods given. + /// + /// The new methods to place in the instance. + /// A new instance of the with the methods given. + IContainerUnit WithMethods( in ImmutableArray methods ); +} diff --git a/Source/MochaTool.InteropGen/Parsing/IUnit.cs b/Source/MochaTool.InteropGen/Parsing/IUnit.cs index ad8b68fb..e2e89e25 100644 --- a/Source/MochaTool.InteropGen/Parsing/IUnit.cs +++ b/Source/MochaTool.InteropGen/Parsing/IUnit.cs @@ -1,9 +1,7 @@ -using System.Collections.Immutable; - -namespace MochaTool.InteropGen.Parsing; +namespace MochaTool.InteropGen.Parsing; /// -/// Defines a container for fields and methods defined in C++. +/// Defines any declaration in C++. /// internal interface IUnit { @@ -11,26 +9,4 @@ internal interface IUnit /// The name of the . /// string Name { get; } - - /// - /// All of the fields contained in the . - /// - ImmutableArray Fields { get; } - /// - /// All of the methods contained in the . - /// - ImmutableArray Methods { get; } - - /// - /// Returns a new instance of the with the fields given. - /// - /// The new fields to place in the instance. - /// A new instance of the with the fields given. - IUnit WithFields( in ImmutableArray fields ); - /// - /// Returns a new instance of the with the methods given. - /// - /// The new methods to place in the instance. - /// A new instance of the with the methods given. - IUnit WithMethods( in ImmutableArray methods ); } diff --git a/Source/MochaTool.InteropGen/Parsing/Method.cs b/Source/MochaTool.InteropGen/Parsing/Method.cs index 8b9540cc..c3d41f3a 100644 --- a/Source/MochaTool.InteropGen/Parsing/Method.cs +++ b/Source/MochaTool.InteropGen/Parsing/Method.cs @@ -5,12 +5,12 @@ namespace MochaTool.InteropGen.Parsing; /// /// Represents a method in C++. /// -internal sealed class Method +internal sealed class Method : IUnit { /// /// The name of the method. /// - internal string Name { get; } + public string Name { get; } /// /// The literal string containing the return type of the method. /// diff --git a/Source/MochaTool.InteropGen/Parsing/Namespace.cs b/Source/MochaTool.InteropGen/Parsing/Namespace.cs new file mode 100644 index 00000000..db359206 --- /dev/null +++ b/Source/MochaTool.InteropGen/Parsing/Namespace.cs @@ -0,0 +1,74 @@ +using System.Collections.Immutable; + +namespace MochaTool.InteropGen.Parsing; + +/// +/// Represents a namespace in C++. +/// +internal sealed class Namespace : IContainerUnit +{ + /// + public string Name { get; } + + /// + public ImmutableArray Fields { get; } + /// + public ImmutableArray Methods { get; } + + /// + /// Initializes a new instance of . + /// + /// The name of the class. + /// All of the fields that are contained. + /// All of the methods that are contained. + private Namespace( string name, in ImmutableArray fields, in ImmutableArray methods ) + { + Name = name; + + Fields = fields; + Methods = methods; + } + + /// + /// Returns a new instance of the with the fields given. + /// + /// The new fields to place in the instance. + /// A new instance of the with the fields given. + internal Namespace WithFields( in ImmutableArray fields ) + { + return new Namespace( Name, fields, Methods ); + } + + /// + /// Returns a new instance of the with the methods given. + /// + /// The new methods to place in the instance. + /// A new instance of the with the methods given. + internal Namespace WithMethods( in ImmutableArray methods ) + { + return new Namespace( Name, Fields, methods ); + } + + /// + public override string ToString() + { + return Name; + } + + /// + IContainerUnit IContainerUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); + /// + IContainerUnit IContainerUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); + + /// + /// Returns a new instance of . + /// + /// The name of the namespace. + /// The fields contained in the class. + /// The methods contained in the class. + /// A new instance of . + internal static Namespace Create( string name, in ImmutableArray fields, in ImmutableArray methods ) + { + return new Namespace( name, fields, methods ); + } +} diff --git a/Source/MochaTool.InteropGen/Parsing/Parser.cs b/Source/MochaTool.InteropGen/Parsing/Parser.cs index 14810da4..fd1adf53 100644 --- a/Source/MochaTool.InteropGen/Parsing/Parser.cs +++ b/Source/MochaTool.InteropGen/Parsing/Parser.cs @@ -16,16 +16,17 @@ internal static class Parser private static readonly string[] s_launchArgs = GetLaunchArgs(); /// - /// Parses a header file and returns all of the s contained inside. + /// Parses a header file and returns all of the s contained inside. /// /// The absolute path to the header file to parse. - /// All of the s contained inside the header file. - internal unsafe static IEnumerable GetUnits( string path ) + /// All of the s contained inside the header file. + internal unsafe static IEnumerable GetUnits( string path ) { - var units = new List(); + using var _time = new StopwatchLog( $"Parse {path}" ); + var units = new List(); using var index = CXIndex.Create(); - using var unit = CXTranslationUnit.Parse( index, path, s_launchArgs, ReadOnlySpan.Empty, CXTranslationUnit_Flags.CXTranslationUnit_None ); + using var unit = CXTranslationUnit.Parse( index, path, s_launchArgs, ReadOnlySpan.Empty, CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies ); // Only start walking diagnostics if logging is enabled to the minimum level. if ( Log.IsEnabled( LogLevel.Warning ) ) @@ -48,63 +49,63 @@ internal unsafe static IEnumerable GetUnits( string path ) } } - CXChildVisitResult cursorVisitor( CXCursor cursor, CXCursor parent, void* data ) + ContainerBuilder? currentContainer = null; + + // Visits all immediate members inside of a class/struct/namespace declaration. + CXChildVisitResult cursorMemberVisitor( CXCursor cursor, CXCursor parent, void* data ) { if ( !cursor.Location.IsFromMainFile ) return CXChildVisitResult.CXChildVisit_Continue; switch ( cursor.Kind ) { - // - // Struct / class / namespace - // - case CXCursorKind.CXCursor_ClassDecl: - units.Add( Class.NewClass( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - case CXCursorKind.CXCursor_StructDecl: - units.Add( Struct.NewStructure( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - case CXCursorKind.CXCursor_Namespace: - units.Add( Class.NewNamespace( cursor.Spelling.ToString(), ImmutableArray.Empty, ImmutableArray.Empty ) ); - break; - // // Methods // case CXCursorKind.CXCursor_Constructor: case CXCursorKind.CXCursor_CXXMethod: case CXCursorKind.CXCursor_FunctionDecl: - return VisitMethod( cursor, units ); + return VisitMethod( cursor, currentContainer ); // // Field // case CXCursorKind.CXCursor_FieldDecl: - return VisitField( cursor, units ); + return VisitField( cursor, currentContainer ); } - return CXChildVisitResult.CXChildVisit_Recurse; + return CXChildVisitResult.CXChildVisit_Continue; } - unit.Cursor.VisitChildren( cursorVisitor, default ); - - // - // Remove all items with duplicate names - // - for ( int i = 0; i < units.Count; i++ ) + // Visits all elements within the translation unit. + CXChildVisitResult cursorContainerVisitor( CXCursor cursor, CXCursor parent, void* data ) { - var item = units[i]; - item = item.WithFields( item.Fields.GroupBy( x => x.Name ).Select( x => x.First() ).ToImmutableArray() ) - .WithMethods( item.Methods.GroupBy( x => x.Name ).Select( x => x.First() ).ToImmutableArray() ); + if ( !cursor.Location.IsFromMainFile ) + return CXChildVisitResult.CXChildVisit_Continue; - units[i] = item; - } + var containerType = cursor.Kind switch + { + CXCursorKind.CXCursor_ClassDecl => ContainerType.Class, + CXCursorKind.CXCursor_StructDecl => ContainerType.Struct, + CXCursorKind.CXCursor_Namespace => ContainerType.Namespace, + _ => ContainerType.Invalid + }; + + // Bail from recursing through if it's not an item we care about. + if ( containerType == ContainerType.Invalid ) + return CXChildVisitResult.CXChildVisit_Continue; + + currentContainer = new ContainerBuilder( containerType, cursor.Spelling.ToString() ); + cursor.VisitChildren( cursorMemberVisitor, default ); + + if ( !currentContainer.IsEmpty ) + units.Add( currentContainer.Build() ); - // - // Remove any units that have no methods or fields - // - units = units.Where( x => x.Methods.Length > 0 || x.Fields.Length > 0 ).ToList(); + // Recurse through nested classes and structs. + return CXChildVisitResult.CXChildVisit_Recurse; + } + unit.Cursor.VisitChildren( cursorContainerVisitor, default ); return units; } @@ -112,22 +113,18 @@ CXChildVisitResult cursorVisitor( CXCursor cursor, CXCursor parent, void* data ) /// The visitor method for walking a method declaration. /// /// The cursor that is traversing the method. - /// The collection to fetch method owners from. + /// The current C++ container being parsed. /// The next action the cursor should take in traversal. - private static unsafe CXChildVisitResult VisitMethod( in CXCursor cursor, ICollection units ) + private static unsafe CXChildVisitResult VisitMethod( in CXCursor cursor, ContainerBuilder? currentContainer ) { // Early bails. + if ( currentContainer is null ) + return CXChildVisitResult.CXChildVisit_Continue; if ( !cursor.HasGenerateBindingsAttribute() ) return CXChildVisitResult.CXChildVisit_Continue; if ( cursor.CXXAccessSpecifier != CX_CXXAccessSpecifier.CX_CXXPublic && cursor.Kind != CXCursorKind.CXCursor_FunctionDecl ) return CXChildVisitResult.CXChildVisit_Continue; - // Verify that the method has an owner. - var ownerName = cursor.LexicalParent.Spelling.ToString(); - var owner = units.FirstOrDefault( x => x.Name == ownerName ); - if ( owner is null ) - return CXChildVisitResult.CXChildVisit_Continue; - string name; string returnType; bool isStatic; @@ -139,7 +136,7 @@ private static unsafe CXChildVisitResult VisitMethod( in CXCursor cursor, IColle if ( cursor.Kind == CXCursorKind.CXCursor_Constructor ) { name = "Ctor"; - returnType = owner.Name + '*'; + returnType = currentContainer.Name + '*'; isStatic = false; isConstructor = true; isDestructor = false; @@ -148,7 +145,7 @@ private static unsafe CXChildVisitResult VisitMethod( in CXCursor cursor, IColle else if ( cursor.Kind == CXCursorKind.CXCursor_Destructor ) { name = "DeCtor"; - returnType = '~' + owner.Name; + returnType = '~' + currentContainer.Name; isStatic = false; isConstructor = false; isDestructor = true; @@ -189,9 +186,7 @@ CXChildVisitResult methodChildVisitor( CXCursor cursor, CXCursor parent, void* d method = Method.NewMethod( name, returnType, isStatic, parametersBuilder.ToImmutable() ); // Update owner with new method. - var newOwner = owner.WithMethods( owner.Methods.Add( method ) ); - units.Remove( owner ); - units.Add( newOwner ); + currentContainer.AddMethod( method ); return CXChildVisitResult.CXChildVisit_Continue; } @@ -200,24 +195,18 @@ CXChildVisitResult methodChildVisitor( CXCursor cursor, CXCursor parent, void* d /// The visitor method for walking a field declaration. /// /// The cursor that is traversing the method. - /// The collection to fetch method owners from. + /// The current C++ container being parsed. /// The next action the cursor should take in traversal. - private static CXChildVisitResult VisitField( in CXCursor cursor, ICollection units ) + private static CXChildVisitResult VisitField( in CXCursor cursor, ContainerBuilder? currentContainer ) { // Early bail. + if ( currentContainer is null ) + return CXChildVisitResult.CXChildVisit_Continue; if ( !cursor.HasGenerateBindingsAttribute() ) return CXChildVisitResult.CXChildVisit_Continue; - // Verify that the field has an owner. - var ownerName = cursor.LexicalParent.Spelling.ToString(); - var owner = units.FirstOrDefault( x => x.Name == ownerName ); - if ( owner is null ) - return CXChildVisitResult.CXChildVisit_Recurse; - // Update owner with new field. - var newOwner = owner.WithFields( owner.Fields.Add( new Variable( cursor.Spelling.ToString(), cursor.Type.ToString() ) ) ); - units.Remove( owner ); - units.Add( newOwner ); + currentContainer.AddField( new Variable( cursor.Spelling.ToString(), cursor.Type.ToString() ) ); return CXChildVisitResult.CXChildVisit_Recurse; } @@ -229,7 +218,7 @@ private static CXChildVisitResult VisitField( in CXCursor cursor, ICollection { diff --git a/Source/MochaTool.InteropGen/Parsing/Struct.cs b/Source/MochaTool.InteropGen/Parsing/Struct.cs index d48fd6ba..9356df6f 100644 --- a/Source/MochaTool.InteropGen/Parsing/Struct.cs +++ b/Source/MochaTool.InteropGen/Parsing/Struct.cs @@ -5,7 +5,7 @@ namespace MochaTool.InteropGen.Parsing; /// /// Represents a struct in C++. /// -internal sealed class Struct : IUnit +internal sealed class Struct : IContainerUnit { /// public string Name { get; } @@ -56,9 +56,9 @@ public override string ToString() } /// - IUnit IUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); + IContainerUnit IContainerUnit.WithFields( in ImmutableArray fields ) => WithFields( fields ); /// - IUnit IUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); + IContainerUnit IContainerUnit.WithMethods( in ImmutableArray methods ) => WithMethods( methods ); /// /// Returns a new instance of . @@ -67,7 +67,7 @@ public override string ToString() /// The fields contained in the struct. /// The methods contained in the struct. /// A new instance of . - internal static Struct NewStructure( string name, in ImmutableArray fields, in ImmutableArray methods ) + internal static Struct Create( string name, in ImmutableArray fields, in ImmutableArray methods ) { return new( name, fields, methods ); } diff --git a/Source/MochaTool.InteropGen/Parsing/Variable.cs b/Source/MochaTool.InteropGen/Parsing/Variable.cs index 60c5e1e8..c33f8ca2 100644 --- a/Source/MochaTool.InteropGen/Parsing/Variable.cs +++ b/Source/MochaTool.InteropGen/Parsing/Variable.cs @@ -3,12 +3,12 @@ /// /// Represents a variable in C++. This can be a field, parameter, etc. /// -internal sealed class Variable +internal sealed class Variable : IUnit { /// /// The name of the variable. /// - internal string Name { get; } + public string Name { get; } /// /// The literal string containing the type of the variable. /// diff --git a/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs b/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs index d7317fca..ee9e270a 100644 --- a/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs +++ b/Source/MochaTool.InteropGen/Parsing/VcxprojParser.cs @@ -1,4 +1,5 @@ -using System.Xml; +using System.Collections.Frozen; +using System.Xml; namespace MochaTool.InteropGen.Parsing; @@ -14,6 +15,27 @@ internal static class VcxprojParser private const string ExternalIncludePath = "/rs:Project/rs:PropertyGroup[@Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\"]/rs:ExternalIncludePath"; private const string IncludePath = "/rs:Project/rs:PropertyGroup[@Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\"]/rs:IncludePath"; + private static readonly FrozenDictionary EnvironmentVariables = new Dictionary() + { + { + "VULKAN_SDK", + + Environment.GetEnvironmentVariable( "VULKAN_SDK" ) ?? + Path.Combine( "C:", "VulkanSDK", "1.3.224.1", "Include" ) + }, + { "ProjectDir", Path.Combine( "..", "Mocha.Host" ) }, + { "SolutionDir", $"..{Path.DirectorySeparatorChar}" }, + { "Platform", "x64" }, + { + "VcpkgRoot", + + Environment.GetEnvironmentVariable( "VCPKG_ROOT" ) ?? + Path.Combine( "C:", "Users", Environment.UserName, "vcpkg" ) + }, + { "IncludePath", Path.Combine( "..", "Mocha.Host" ) }, + { "ExternalIncludePath", "" } + }.ToFrozenDictionary(); + /// /// Parse the include list from a vcxproj file. /// @@ -48,18 +70,6 @@ internal static List ParseIncludes( string path ) includes.AddRange( includeStr.Split( ';', StringSplitOptions.TrimEntries ) ); } - // Define environment variables - var environmentVariables = new Dictionary() - { - { "VULKAN_SDK", Environment.GetEnvironmentVariable( "VULKAN_SDK" ) ?? @"C:\VulkanSDK\1.3.224.1\Include" }, - { "ProjectDir", "..\\Mocha.Host\\" }, - { "SolutionDir", "..\\" }, - { "Platform", "x64" }, - { "VcpkgRoot", Environment.GetEnvironmentVariable( "VCPKG_ROOT" ) ?? $@"C:\Users\{Environment.UserName}\vcpkg" }, - { "IncludePath", "..\\Mocha.Host\\" }, - { "ExternalIncludePath", "" } - }; - var parsedIncludes = new List(); // Simple find-and-replace for macros and environment variables @@ -67,7 +77,7 @@ internal static List ParseIncludes( string path ) { var processedInclude = include; - foreach ( var environmentVariable in environmentVariables ) + foreach ( var environmentVariable in EnvironmentVariables ) processedInclude = processedInclude.Replace( $"$({environmentVariable.Key})", environmentVariable.Value ); parsedIncludes.Add( processedInclude ); diff --git a/Source/MochaTool.InteropGen/Program.cs b/Source/MochaTool.InteropGen/Program.cs index 6e815d58..fa18709a 100644 --- a/Source/MochaTool.InteropGen/Program.cs +++ b/Source/MochaTool.InteropGen/Program.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Mocha.Common; using MochaTool.InteropGen.CodeGen; using MochaTool.InteropGen.Extensions; using MochaTool.InteropGen.Parsing; @@ -13,19 +14,25 @@ public static class Program /// /// Contains all of the parsed units to generate bindings for. /// - private static readonly List s_units = new(); + private static readonly List s_units = []; /// /// Contains all of the files that need to be generated. /// - private static readonly List s_files = new(); + private static readonly List s_files = []; /// /// The entry point to the program. /// /// The command-line arguments given to the program. - public static void Main( string[] args ) + public static async Task Main( string[] args ) { - using var _totalTime = new StopwatchLog( "InteropGen", LogLevel.Information ); + if ( args.Length != 1 ) + { + Log.LogIntroError(); + return; + } + + using var _totalTime = new StopwatchLog( "InteropGen", Microsoft.Extensions.Logging.LogLevel.Information ); var baseDir = args[0]; Log.LogIntro(); @@ -36,19 +43,22 @@ public static void Main( string[] args ) DeleteExistingFiles( baseDir ); using ( var _parseTime = new StopwatchLog( "Parsing" ) ) - Parse( baseDir ); + await ParseAsync( baseDir ); // // Expand methods out into list of (method name, method) // - var methods = s_units.OfType().SelectMany( unit => unit.Methods, ( unit, method ) => (unit.Name, method) ).ToList(); + var methods = s_units.SelectMany( unit => unit.Methods, ( unit, method ) => (unit.Name, method) ).ToArray(); // // Write files // - WriteManagedStruct( baseDir, methods ); - WriteNativeStruct( baseDir, methods ); - WriteNativeIncludes( baseDir ); + using var _writeTime = new StopwatchLog( "Writing" ); + var managedStructTask = WriteManagedStructAsync( baseDir, methods ); + var nativeStructTask = WriteNativeStructAsync( baseDir, methods ); + var nativeIncludesTask = WriteNativeIncludesAsync( baseDir ); + + await Task.WhenAll( managedStructTask, nativeIncludesTask, nativeIncludesTask ); } /// @@ -57,8 +67,8 @@ public static void Main( string[] args ) /// The base directory that contains the source projects. private static void DeleteExistingFiles( string baseDir ) { - var destCsDir = $"{baseDir}\\Mocha.Common\\Glue"; - var destHeaderDir = $"{baseDir}\\Mocha.Host\\generated"; + var destCsDir = Path.Combine( baseDir, "Mocha.Common", "Glue" ); + var destHeaderDir = Path.Combine( baseDir, "Mocha.Host", "generated" ); if ( Directory.Exists( destHeaderDir ) ) Directory.Delete( destHeaderDir, true ); @@ -73,22 +83,21 @@ private static void DeleteExistingFiles( string baseDir ) /// Parses all header files in the Mocha.Host project for interop generation. /// /// The base directory that contains the source projects. - private static void Parse( string baseDir ) + private static async Task ParseAsync( string baseDir ) { // Find and queue all of the header files to parse. var queue = new List(); - QueueDirectory( queue, baseDir + "\\Mocha.Host" ); + QueueDirectory( queue, Path.Combine( baseDir, "Mocha.Host" ) ); // Dispatch jobs to parse all files. - var dispatcher = new ThreadDispatcher( async ( files ) => + var dispatcher = TaskPool.Dispatch( queue, async files => { foreach ( var path in files ) await ProcessHeaderAsync( baseDir, path ); - }, queue ); + } ); // Wait for all threads to finish... - while ( !dispatcher.IsComplete ) - Thread.Sleep( 1 ); + await dispatcher.WaitForCompleteAsync(); } /// @@ -96,7 +105,7 @@ private static void Parse( string baseDir ) /// /// The base directory that contains the source projects. /// An enumerable list of all of the methods to write in the struct. - private static void WriteManagedStruct( string baseDir, IEnumerable<(string Name, Method method)> methods ) + private static async Task WriteManagedStructAsync( string baseDir, IEnumerable<(string Name, Method method)> methods ) { var (baseManagedStructWriter, managedStructWriter) = Utils.CreateWriter(); @@ -119,7 +128,8 @@ private static void WriteManagedStruct( string baseDir, IEnumerable<(string Name managedStructWriter.WriteLine( '}' ); managedStructWriter.Dispose(); - File.WriteAllText( $"{baseDir}/Mocha.Common/Glue/UnmanagedArgs.cs", baseManagedStructWriter.ToString() ); + var path = Path.Combine( baseDir, "Mocha.Common", "Glue", "UnmanagedArgs.cs" ); + await File.WriteAllTextAsync( path, baseManagedStructWriter.ToString() ); } /// @@ -127,7 +137,7 @@ private static void WriteManagedStruct( string baseDir, IEnumerable<(string Name /// /// The base directory that contains the source projects. /// An enumerable list of all of the methods to write in the struct. - private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, Method method)> methods ) + private static async Task WriteNativeStructAsync( string baseDir, IEnumerable<(string Name, Method method)> methods ) { var (baseNativeStructWriter, nativeStructWriter) = Utils.CreateWriter(); @@ -166,14 +176,15 @@ private static void WriteNativeStruct( string baseDir, IEnumerable<(string Name, nativeStructWriter.WriteLine( "#endif // __GENERATED_UNMANAGED_ARGS_H" ); nativeStructWriter.Dispose(); - File.WriteAllText( $"{baseDir}Mocha.Host\\generated\\UnmanagedArgs.generated.h", baseNativeStructWriter.ToString() ); + var path = Path.Combine( baseDir, "Mocha.Host", "generated", "UnmanagedArgs.generated.h" ); + await File.WriteAllTextAsync( path, baseNativeStructWriter.ToString() ); } /// /// Writes the C++ includes for the host project. /// /// The base directory that contains the source projects. - private static void WriteNativeIncludes( string baseDir ) + private static async Task WriteNativeIncludesAsync( string baseDir ) { var (baseNativeListWriter, nativeListWriter) = Utils.CreateWriter(); @@ -190,7 +201,8 @@ private static void WriteNativeIncludes( string baseDir ) nativeListWriter.WriteLine(); nativeListWriter.WriteLine( "#endif // __GENERATED_INTEROPLIST_H" ); - File.WriteAllText( $"{baseDir}Mocha.Host\\generated\\InteropList.generated.h", baseNativeListWriter.ToString() ); + var path = Path.Combine( baseDir, "Mocha.Host", "generated", "InteropList.generated.h" ); + await File.WriteAllTextAsync( path, baseNativeListWriter.ToString() ); } /// @@ -208,13 +220,15 @@ private static async Task ProcessHeaderAsync( string baseDir, string path ) // Generate interop code. var managedCode = ManagedCodeGenerator.GenerateCode( units ); - var relativePath = Path.GetRelativePath( $"{baseDir}/Mocha.Host/", path ); + var relativePath = Path.GetRelativePath( Path.Combine( baseDir, "Mocha.Host" ), path ); var nativeCode = NativeCodeGenerator.GenerateCode( relativePath, units ); // Write interop code. var fileName = Path.GetFileNameWithoutExtension( path ); - var csTask = File.WriteAllTextAsync( $"{baseDir}Mocha.Common\\Glue\\{fileName}.generated.cs", managedCode ); - var nativeTask = File.WriteAllTextAsync( $"{baseDir}Mocha.Host\\generated\\{fileName}.generated.h", nativeCode ); + var csPath = Path.Combine( baseDir, "Mocha.Common", "Glue", $"{fileName}.generated.cs" ); + var csTask = File.WriteAllTextAsync( csPath, managedCode ); + var nativePath = Path.Combine( baseDir, "Mocha.Host", "generated", $"{fileName}.generated.h" ); + var nativeTask = File.WriteAllTextAsync( nativePath, nativeCode ); // Wait for writing to finish. await Task.WhenAll( csTask, nativeTask ); diff --git a/Source/MochaTool.InteropGen/TaskPool.cs b/Source/MochaTool.InteropGen/TaskPool.cs new file mode 100644 index 00000000..19a2c443 --- /dev/null +++ b/Source/MochaTool.InteropGen/TaskPool.cs @@ -0,0 +1,62 @@ +namespace Mocha.Common; + +public sealed class TaskPool +{ + public delegate Task TaskCallback( T[] taskQueue ); + public delegate void Continuation(); + + private readonly Task[] _tasks; + + private TaskPool( T[] queue, TaskCallback taskStart ) + { + var maxTasks = (int)(Environment.ProcessorCount * 0.5) - 1; + var batchSize = queue.Length / maxTasks; + + if ( batchSize == 0 ) + batchSize = 1; + + var batched = queue + .Select( ( value, index ) => new + { + Value = value, + Index = index + } ) + .GroupBy( p => p.Index / batchSize ) + .Select( g => g.Select( p => p.Value ).ToArray() ) + .ToArray(); + + _tasks = new Task[batched.Length]; + for ( int i = 0; i < batched.Length; i++ ) + { + var taskQueue = batched[i]; + + _tasks[i] = Task.Run( () => taskStart( taskQueue ) ); + } + } + + public static TaskPool Dispatch( T[] queue, TaskCallback taskStart ) + { + return new TaskPool( queue, taskStart ); + } + + public static TaskPool Dispatch( IEnumerable queue, TaskCallback taskStart ) + { + return new TaskPool( queue.ToArray(), taskStart ); + } + + public TaskPool Then( Continuation continuation ) + { + Task.WhenAll( _tasks ).ContinueWith( t => continuation() ).Wait(); + return this; + } + + public async Task WaitForCompleteAsync() + { + await Task.WhenAll( _tasks ); + } + + public void WaitForComplete() + { + WaitForCompleteAsync().Wait(); + } +} diff --git a/Source/MochaTool.InteropGen/ThreadDispatcher.cs b/Source/MochaTool.InteropGen/ThreadDispatcher.cs deleted file mode 100644 index 598c0c4d..00000000 --- a/Source/MochaTool.InteropGen/ThreadDispatcher.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace MochaTool.InteropGen; - -internal class ThreadDispatcher -{ - internal delegate void ThreadCallback( List threadQueue ); - internal delegate Task AsyncThreadCallback( List threadQueue ); - - internal bool IsComplete => _threadsCompleted >= _threadCount; - - private int _threadCount = (int)Math.Ceiling( Environment.ProcessorCount * 0.75 ); - private int _threadsCompleted = 0; - - internal ThreadDispatcher( ThreadCallback threadStart, List queue ) - { - Setup( queue, threadQueue => threadStart( threadQueue ) ); - } - - internal ThreadDispatcher( AsyncThreadCallback threadStart, List queue ) - { - Setup( queue, threadQueue => threadStart( threadQueue ).Wait() ); - } - - private void Setup( List queue, Action> threadStart ) - { - var batchSize = queue.Count / (_threadCount - 1); - - if ( batchSize == 0 ) - throw new InvalidOperationException( "There are no items to batch for threads" ); - - var batched = queue - .Select( ( Value, Index ) => new { Value, Index } ) - .GroupBy( p => p.Index / batchSize ) - .Select( g => g.Select( p => p.Value ).ToList() ) - .ToList(); - - _threadCount = batched.Count; - - for ( int i = 0; i < batched.Count; i++ ) - { - var threadQueue = batched[i]; - var thread = new Thread( () => - { - threadStart( threadQueue ); - _threadsCompleted++; - } ); - - thread.Start(); - } - } -} diff --git a/Source/MochaTool.InteropGen/Utils.cs b/Source/MochaTool.InteropGen/Utils.cs index 3bdb93b8..7d9f58df 100644 --- a/Source/MochaTool.InteropGen/Utils.cs +++ b/Source/MochaTool.InteropGen/Utils.cs @@ -1,5 +1,6 @@ using MochaTool.InteropGen.Extensions; using System.CodeDom.Compiler; +using System.Collections.Frozen; namespace MochaTool.InteropGen; @@ -11,7 +12,7 @@ internal static class Utils /// /// Used as a lookup table for mapping native types to managed ones. /// - private static readonly Dictionary s_lookupTable = new() + private static readonly FrozenDictionary s_lookupTable = new Dictionary() { // Native type Managed type //------------------------------- @@ -40,7 +41,7 @@ internal static class Utils { "Quaternion", "Rotation" }, { "InteropStruct", "IInteropArray" }, { "Handle", "uint" } - }; + }.ToFrozenDictionary(); /// /// Returns whether or not the string represents a pointer.