From 6e267698e3fdb91855e101634fac4620a29fb27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Kou=C5=99il?= Date: Sun, 5 Oct 2025 18:49:16 +0200 Subject: [PATCH 1/2] Refactored codegen into two passes; fixed some edge cases of the emit to have more correct bindings --- .../CSharpCodeGen/CSharpClassType.cs | 9 + .../CSharpCodeGen/CSharpEnumType.cs | 33 ++ .../CSharpCodeGen/CSharpEnumValue.cs | 23 + .../CSharpCodeGen/CSharpField.cs | 42 ++ .../CSharpCodeGen/CSharpFile.cs | 68 +++ .../CSharpCodeGen/CSharpMethod.cs | 91 ++++ .../CSharpCodeGen/CSharpNamespace.cs | 37 ++ .../CSharpCodeGen/CSharpProperty.cs | 50 ++ .../CSharpCodeGen/CSharpStructType.cs | 9 + .../CSharpCodeGen/CSharpType.cs | 87 ++++ .../CSharpCodeGen/CSharpTypeMember.cs | 31 ++ .../CSharpCodeGen/ICodeFragmentProvider.cs | 7 + src/SharpMetal.Generator/CodeGenContext.cs | 23 + src/SharpMetal.Generator/HeaderInfo.cs | 50 +- .../Instances/ClassInstance.cs | 204 ++------ .../Instances/EnumInstance.cs | 52 +- .../Instances/MethodInstance.cs | 215 +-------- .../Instances/ObjectiveCInstance.cs | 36 -- .../Instances/PropertyInstance.cs | 133 +----- .../Instances/SelectorInstance.cs | 17 +- .../Instances/StructInstance.cs | 37 +- .../Instances/TypeInstance.cs | 12 + src/SharpMetal.Generator/ModelTransformer.cs | 175 +++++++ src/SharpMetal.Generator/Namespaces.cs | 1 + src/SharpMetal.Generator/ParsedModel.cs | 53 +++ src/SharpMetal.Generator/Program.cs | 192 +------- .../Transformers/ClassTransformer.cs | 450 ++++++++++++++++++ .../Transformers/EnumTransformer.cs | 21 + .../Transformers/StructTransformer.cs | 22 + .../Utilities/GeneratorUtils.cs | 34 ++ 30 files changed, 1403 insertions(+), 811 deletions(-) create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpClassType.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumType.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumValue.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpField.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpFile.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpMethod.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpNamespace.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpProperty.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpStructType.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpType.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/CSharpTypeMember.cs create mode 100644 src/SharpMetal.Generator/CSharpCodeGen/ICodeFragmentProvider.cs create mode 100644 src/SharpMetal.Generator/Instances/TypeInstance.cs create mode 100644 src/SharpMetal.Generator/ModelTransformer.cs create mode 100644 src/SharpMetal.Generator/ParsedModel.cs create mode 100644 src/SharpMetal.Generator/Transformers/ClassTransformer.cs create mode 100644 src/SharpMetal.Generator/Transformers/EnumTransformer.cs create mode 100644 src/SharpMetal.Generator/Transformers/StructTransformer.cs diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpClassType.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpClassType.cs new file mode 100644 index 0000000..60a0f98 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpClassType.cs @@ -0,0 +1,9 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpClassType : CSharpType + { + public CSharpClassType(string name) : base(TypeKind.Class, name) + { + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumType.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumType.cs new file mode 100644 index 0000000..b304e24 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumType.cs @@ -0,0 +1,33 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpEnumType : CSharpType + { + public CSharpEnumType(string name) : base(TypeKind.Enum, name) + { + } + + public void SetPrimitiveType(string type) + { + BaseTypes.Clear(); + BaseTypes.Add(type); + } + + public void MarkAsFlags() + { + Attributes.Add("[Flags]"); + } + + public void AddValues(Dictionary values) + { + foreach (var value in values) + { + var field = new CSharpEnumValue(value.Key); + if (!string.IsNullOrEmpty(value.Value)) + { + field.DefaultValue = value.Value; + } + AddMember(field); + } + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumValue.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumValue.cs new file mode 100644 index 0000000..5cd3095 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpEnumValue.cs @@ -0,0 +1,23 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpEnumValue : CSharpTypeMember + { + public string? DefaultValue { get; set; } + + public CSharpEnumValue(string name) : base(name, MemberKind.Field) + { + } + + public override void Generate(CodeGenContext context) + { + if (DefaultValue != null) + { + context.WriteLine($"{Name} = {DefaultValue},"); + } + else + { + context.WriteLine($"{Name},"); + } + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpField.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpField.cs new file mode 100644 index 0000000..28a35c1 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpField.cs @@ -0,0 +1,42 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpField : CSharpTypeMember + { + public string VariableType { get; private set; } + public string? DefaultValue { get; set; } + public bool IsStatic { get; set; } + public bool IsReadonly { get; set; } + + public CSharpField(string variableType, string name) : base(name, MemberKind.Field) + { + VariableType = variableType; + } + + public override void Generate(CodeGenContext context) + { + var sb = context.AcquireTempStringBuilder(); + sb.Append(VisibilityModifier); + if (IsStatic) + { + sb.Append(" static"); + } + + if (IsReadonly) + { + sb.Append(" readonly"); + } + + sb.Append(' '); + sb.Append(VariableType); + sb.Append(' '); + sb.Append(Name); + if (DefaultValue != null) + { + sb.Append($" = {DefaultValue}"); + } + + context.WriteLine($"{sb.ToString().Trim()};"); + context.ReleaseTempStringBuilder(sb); + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpFile.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpFile.cs new file mode 100644 index 0000000..3ce0fcc --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpFile.cs @@ -0,0 +1,68 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpFile : ICodeFragmentProvider + { + private readonly List _usings = []; + private readonly List _namespaces = []; + + public string Name { get; } + public string Directory { get; } + public string FilePath { get; } + + public CSharpFile(string name) + { + Name = name; + Directory = ""; + FilePath = $"{name}.cs"; + } + + public CSharpFile(string name, string directory) + { + Name = name; + Directory = directory; + FilePath = $"{directory}/{name}.cs"; + } + + public CSharpNamespace GetOrCreateNamespace(string @namespace) + { + foreach (var ns in _namespaces) + { + if (ns.Name == @namespace) + { + return ns; + } + } + + var rv = new CSharpNamespace(@namespace); + _namespaces.Add(rv); + return rv; + } + + public void Generate(CodeGenContext context) + { + if (_usings.Count > 0) + { + foreach (var @using in _usings) + { + context.WriteLine($"using {@using};"); + } + context.WriteLine(); + } + + for (var index = 0; index < _namespaces.Count; index++) + { + var ns = _namespaces[index]; + ns.Generate(context); + if (index != _namespaces.Count - 1) + { + context.WriteLine(); + } + } + } + + public void AddUsing(string @namespace) + { + _usings.Add(@namespace); + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpMethod.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpMethod.cs new file mode 100644 index 0000000..6466735 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpMethod.cs @@ -0,0 +1,91 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpMethod : CSharpTypeMember + { + private readonly List _bodyContents = []; + private readonly List<(string Type, string Name, string Attribute)> _parameterList; + + public bool HasExpressionBody => PreferExpressionBody && _bodyContents.Count == 1; + public bool PreferExpressionBody { get; set; } + public string ReturnType { get; private set; } + public bool IsStatic { get; set; } + public bool IsPartial { get; set; } + + public CSharpMethod(string name, string returnType) : base(name, MemberKind.Method) + { + ReturnType = returnType; + _parameterList = []; + } + + public CSharpMethod(string name, string returnType, (string Type, string Name, string Attribute) parameter) : base(name, MemberKind.Method) + { + ReturnType = returnType; + _parameterList = [parameter]; + } + + public CSharpMethod(string name, string returnType, List<(string Type, string Name, string Attribute)> parameterList) : base(name, MemberKind.Method) + { + ReturnType = returnType; + _parameterList = parameterList; + } + + public void AddBodyLine(string line) + { + _bodyContents.Add(line); + } + + public override void Generate(CodeGenContext context) + { + foreach (var attribute in _attributes) + { + context.WriteLine(attribute); + } + + var sb = context.AcquireTempStringBuilder(); + sb.Append(VisibilityModifier); + if (IsStatic) + { + sb.Append(" static"); + } + if (IsPartial) + { + sb.Append(" partial"); + } + // Special formatting case for ctors that have empty return type + if (!string.IsNullOrEmpty(ReturnType)) + { + sb.Append(' '); + sb.Append(ReturnType); + } + sb.Append(' '); + sb.Append(Name); + sb.Append('('); + sb.Append(string.Join(", ", _parameterList.Select(x => $"{x.Attribute} {x.Type} {x.Name}".Trim()))); + sb.Append(')'); + if (!IsPartial) + { + if (HasExpressionBody) + { + sb.Append($" => {_bodyContents[0]};"); + context.WriteLine(sb.ToString()); + } + else + { + context.WriteLine(sb.ToString()); + context.EnterScope(); + foreach (var line in _bodyContents) + { + context.WriteLine($"{line};"); + } + context.LeaveScope(); + } + } + else + { + sb.Append(';'); + context.WriteLine(sb.ToString()); + } + context.ReleaseTempStringBuilder(sb); + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpNamespace.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpNamespace.cs new file mode 100644 index 0000000..c56b161 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpNamespace.cs @@ -0,0 +1,37 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpNamespace : ICodeFragmentProvider + { + private readonly List _types = []; + + public string Name { get; } + + public CSharpNamespace(string name) + { + Name = name; + } + + public void AddType(CSharpType csType) + { + _types.Add(csType); + } + + public void Generate(CodeGenContext context) + { + context.WriteLine($"namespace {Name}"); + context.EnterScope(); + + for (var index = 0; index < _types.Count; index++) + { + var type = _types[index]; + type.Generate(context); + if (index != _types.Count - 1) + { + context.WriteLine(); + } + } + + context.LeaveScope(); + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpProperty.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpProperty.cs new file mode 100644 index 0000000..2ec6996 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpProperty.cs @@ -0,0 +1,50 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpProperty : CSharpTypeMember + { + public bool IsStatic { get; set; } + public string Type { get; private set; } + public string? Getter { get; set; } + public string? Setter { get; set; } + + public CSharpProperty(string name, string type) : base(name, MemberKind.Property) + { + Type = type; + } + + public override void Generate(CodeGenContext context) + { + foreach (var attribute in _attributes) + { + context.WriteLine(attribute); + } + + var sb = context.AcquireTempStringBuilder(); + sb.Append(VisibilityModifier); + if (IsStatic) + { + sb.Append(" static"); + } + sb.Append(' '); + sb.Append(Type); + sb.Append(' '); + sb.Append(Name); + if (string.IsNullOrEmpty(Setter)) + { + sb.Append(" => "); + sb.Append(Getter); + sb.Append(';'); + context.WriteLine(sb.ToString()); + } + else + { + context.WriteLine(sb.ToString()); + context.EnterScope(); + context.WriteLine($"get => {Getter};"); + context.WriteLine($"set => {Setter};"); + context.LeaveScope(); + } + context.ReleaseTempStringBuilder(sb); + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpStructType.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpStructType.cs new file mode 100644 index 0000000..ad9b315 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpStructType.cs @@ -0,0 +1,9 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public class CSharpStructType : CSharpType + { + public CSharpStructType(string name) : base(TypeKind.Struct, name) + { + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpType.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpType.cs new file mode 100644 index 0000000..735c2e3 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpType.cs @@ -0,0 +1,87 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public enum TypeKind + { + Class = 0, + Struct = 1, + Enum = 2, + } + + public abstract class CSharpType : ICodeFragmentProvider + { + public TypeKind Kind { get; } + public string Name { get; } + public List Members { get; } + public List Attributes { get; } + public List BaseTypes { get; } + public bool IsPartial { get; set; } + public bool IsStatic { get; set; } + public string VisibilityModifier { get; set; } + + public CSharpType(TypeKind kind, string name) + { + Kind = kind; + Name = name; + VisibilityModifier = "public"; + BaseTypes = []; + Members = []; + Attributes = []; + } + + public void AddMember(CSharpTypeMember member) + { + Members.Add(member); + } + + public void Generate(CodeGenContext context) + { + context.WriteLine("[SupportedOSPlatform(\"macos\")]"); + foreach (var attribute in Attributes) + { + context.WriteLine(attribute); + } + + string typeDeclaration = $"{VisibilityModifier} {(IsStatic ? "static " : "")}{(IsPartial ? "partial " : "")}{KindToType(Kind)} {Name}"; + if (BaseTypes.Count > 0) + { + typeDeclaration += $" : {string.Join(", ", BaseTypes)}"; + } + + context.WriteLine(typeDeclaration); + context.EnterScope(); + + for (var i = 0; i < Members.Count; i++) + { + var value = Members[i]; + if (i > 0) + { + // Add extra lines in special cases, to keep identical emits to previous versions formatting-wise + var oneLiner = value is CSharpMethod m && m.HasExpressionBody; + if ((value.Kind != MemberKind.Field || value.Kind != Members[i - 1].Kind) && !oneLiner) + { + context.WriteLine(); + } + } + + value.Generate(context); + } + + context.LeaveScope(); + + static string KindToType(TypeKind kind) + { + switch (kind) + { + case TypeKind.Struct: + return "struct"; + case TypeKind.Enum: + return "enum"; + case TypeKind.Class: + return "class"; + } + + throw new ArgumentOutOfRangeException(nameof(kind), $"'{kind}' not supported."); + } + } + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/CSharpTypeMember.cs b/src/SharpMetal.Generator/CSharpCodeGen/CSharpTypeMember.cs new file mode 100644 index 0000000..960af15 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/CSharpTypeMember.cs @@ -0,0 +1,31 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public enum MemberKind + { + Field, + Property, + Method, + } + + public abstract class CSharpTypeMember : ICodeFragmentProvider + { + protected readonly List _attributes = []; + + public string Name { get; } + public string VisibilityModifier { get; set; } = "public"; + public MemberKind Kind { get; } + + public CSharpTypeMember(string name, MemberKind kind) + { + Name = name; + Kind = kind; + } + + public void AddAttribute(string attribute) + { + _attributes.Add(attribute); + } + + public abstract void Generate(CodeGenContext context); + } +} diff --git a/src/SharpMetal.Generator/CSharpCodeGen/ICodeFragmentProvider.cs b/src/SharpMetal.Generator/CSharpCodeGen/ICodeFragmentProvider.cs new file mode 100644 index 0000000..8db6705 --- /dev/null +++ b/src/SharpMetal.Generator/CSharpCodeGen/ICodeFragmentProvider.cs @@ -0,0 +1,7 @@ +namespace SharpMetal.Generator.CSharpCodeGen +{ + public interface ICodeFragmentProvider + { + public void Generate(CodeGenContext context); + } +} diff --git a/src/SharpMetal.Generator/CodeGenContext.cs b/src/SharpMetal.Generator/CodeGenContext.cs index 31360f3..9c8245e 100644 --- a/src/SharpMetal.Generator/CodeGenContext.cs +++ b/src/SharpMetal.Generator/CodeGenContext.cs @@ -1,3 +1,5 @@ +using System.Text; + namespace SharpMetal.Generator { public class CodeGenContext : IDisposable @@ -5,6 +7,7 @@ public class CodeGenContext : IDisposable private const string Tab = " "; private readonly StreamWriter _sw; private int _depth; + private readonly Stack _stringBuilderPool = []; public string Indentation { get; private set; } @@ -12,6 +15,25 @@ public CodeGenContext(StreamWriter sw) { _sw = sw; Indentation = ""; + for (int i = 0; i < 2; i++) + { + _stringBuilderPool.Push(new StringBuilder()); + } + } + + public StringBuilder AcquireTempStringBuilder() + { + if (_stringBuilderPool.Count == 0) + { + _stringBuilderPool.Push(new StringBuilder()); + } + return _stringBuilderPool.Pop(); + } + + public void ReleaseTempStringBuilder(StringBuilder sb) + { + sb.Clear(); + _stringBuilderPool.Push(sb); } public void WriteLine() @@ -72,6 +94,7 @@ private static string GetIndentation(int level) public void Dispose() { _sw.Dispose(); + _stringBuilderPool.Clear(); GC.SuppressFinalize(this); } } diff --git a/src/SharpMetal.Generator/HeaderInfo.cs b/src/SharpMetal.Generator/HeaderInfo.cs index fd77571..d34fd00 100644 --- a/src/SharpMetal.Generator/HeaderInfo.cs +++ b/src/SharpMetal.Generator/HeaderInfo.cs @@ -5,31 +5,36 @@ namespace SharpMetal.Generator { public class HeaderInfo { + private readonly List _inFlightUnscopedMethods = []; + public string FilePath { get; } - public IncludeFlags IncludeFlags = IncludeFlags.None; - public List EnumInstances = []; - public List ClassInstances = []; - public List StructInstances = []; - public List InFlightUnscopedMethods = []; + public string FileName { get; } + public string FullNamespace { get; } + public IncludeFlags Flags { get; } = IncludeFlags.None; + public List EnumInstances { get; } = []; + public List ClassInstances { get; } = []; + public List StructInstances { get; } = []; public HeaderInfo(string filePath) { FilePath = filePath; + FileName = Path.GetFileNameWithoutExtension(filePath); + FullNamespace = Namespaces.GetFullNamespace(filePath); using var sr = new StreamReader(File.OpenRead(filePath)); var namespacePrefix = Namespaces.GetNamespace(filePath); var macroNamespacePrefix = Namespaces.GetMacroNamespace(namespacePrefix); - var inMtlPrivateDefSel = false; while (!sr.EndOfStream) { - var line = (sr.ReadLine() ?? "").Trim(); + var line = GeneratorUtils.ReadNextCodeLine(sr); + if (line == null) + { + break; + } // Ignore garbage - if (line == string.Empty || - line.StartsWith("//") || - line.StartsWith("::") || + if (line.StartsWith("::") || line.StartsWith("[[") || - line.StartsWith("/**") || line.StartsWith("#error") || line.StartsWith("#pragma") || line.StartsWith("#define") || @@ -72,13 +77,8 @@ public HeaderInfo(string filePath) // These take two lines, no idea why if (line.StartsWith("_MTL_PRIVATE_DEF_SEL")) { - inMtlPrivateDefSel = true; - continue; - } - - if (inMtlPrivateDefSel) - { - inMtlPrivateDefSel = false; + // Let's just consume the second line straight away + sr.ReadLine(); continue; } @@ -86,29 +86,29 @@ public HeaderInfo(string filePath) { if (line.Contains("Foundation")) { - IncludeFlags |= IncludeFlags.Foundation; + Flags |= IncludeFlags.Foundation; } else if (line.Contains("Metal")) { - IncludeFlags |= IncludeFlags.Metal; + Flags |= IncludeFlags.Metal; } else if (line.Contains("QuartzCore")) { - IncludeFlags |= IncludeFlags.QuartzCore; + Flags |= IncludeFlags.QuartzCore; } } else if (line.StartsWith("class")) { if (!line.Contains(';')) { - if (InFlightUnscopedMethods.Count > 0) + if (_inFlightUnscopedMethods.Count > 0) { Console.WriteLine($"Unscoped methods found in header {filePath}:"); - foreach (var unscopedMethod in InFlightUnscopedMethods) + foreach (var unscopedMethod in _inFlightUnscopedMethods) { Console.WriteLine($"- {unscopedMethod.Name}"); } - InFlightUnscopedMethods.Clear(); + _inFlightUnscopedMethods.Clear(); } var classInstance = ClassInstance.Build(line, namespacePrefix, sr); if (classInstance.IsValid && !GeneratorUtils.IsBannedType(classInstance.Name)) @@ -232,7 +232,7 @@ public HeaderInfo(string filePath) if (method != null) { - InFlightUnscopedMethods.Add(method); + _inFlightUnscopedMethods.Add(method); } } } diff --git a/src/SharpMetal.Generator/Instances/ClassInstance.cs b/src/SharpMetal.Generator/Instances/ClassInstance.cs index 49893ea..b88b146 100644 --- a/src/SharpMetal.Generator/Instances/ClassInstance.cs +++ b/src/SharpMetal.Generator/Instances/ClassInstance.cs @@ -5,14 +5,14 @@ namespace SharpMetal.Generator.Instances public class ClassInstance { public readonly string Name; - private string _namespacePrefix = ""; - private bool _hasAlloc; - private bool _hasInit; - private string _parent = string.Empty; - private readonly List _propertyInstances = []; - private readonly List _methodInstances = []; - private readonly List _selectorInstances = []; + + public bool HasAlloc { get; private set; } + public bool HasInit { get; private set; } + public string Parent { get; private set; } = string.Empty; + public List PropertyInstances { get; } = []; + public List MethodInstances { get; } = []; + public List SelectorInstances { get; } = []; public bool IsValid => !string.IsNullOrWhiteSpace(Name); @@ -23,15 +23,15 @@ private ClassInstance(string name) public void AddSelector(SelectorInstance selectorInstance) { - if (!_selectorInstances.Exists(x => x.Selector == selectorInstance.Selector)) + if (!SelectorInstances.Exists(x => x.Selector == selectorInstance.Selector)) { - _selectorInstances.Add(selectorInstance); + SelectorInstances.Add(selectorInstance); } } public void AddMethod(MethodInstance methodInstance) { - foreach (var method in _methodInstances) + foreach (var method in MethodInstances) { if (method.Name == methodInstance.Name) { @@ -42,151 +42,24 @@ public void AddMethod(MethodInstance methodInstance) } } - _methodInstances.Add(methodInstance); + MethodInstances.Add(methodInstance); } public void AddProperty(PropertyInstance propertyInstance) { - if (!_propertyInstances.Exists(x => x.Name == propertyInstance.Name)) + if (!PropertyInstances.Exists(x => x.Name == propertyInstance.Name)) { - _propertyInstances.Add(propertyInstance); + PropertyInstances.Add(propertyInstance); } } - public List Generate(List classCache, List enumCache, List structCache, CodeGenContext context) + public static ClassInstance Build(string classDeclarationLine, string namespacePrefix, StreamReader sr) { - // Make copies since we will modify these by adding the hiearchy - // This is not ideal, but is the simplest way to make it independent on the processing order - var propertyInstances = new List(_propertyInstances); - var methodInstances = new List(_methodInstances); - var selectorInstances = new List(_selectorInstances); - - var parentName = _parent; - while (parentName != string.Empty) - { - // To properly fit within expected C# patterns, we - // should generate an interface to hold the associated - // properties and methods to allow for proper casting - // between types. Right now, due to the limitations of - // struct inheritance, we will just duplicate all - // properties and methods from the parent to the child. - // This limitation is not present with the old method - // using classes, however that comes with performance - // and memory drawbacks, that structs are able to avoid. - - var parent = classCache.FirstOrDefault(x => x.Name == parentName); - parentName = parent?._parent ?? string.Empty; - if (parent != null) - { - // Just add unique instances, since the MTLAllocation inheritance would cause doubled-up properties - foreach (var property in parent._propertyInstances) - { - if (!propertyInstances.Any(x => x.Name == property.Name && x.Type == property.Type)) - { - propertyInstances.Add(property); - } - } - methodInstances.AddRange(parent._methodInstances); - foreach (var selector in parent._selectorInstances) - { - if (selectorInstances.All(x => x.Name != selector.Name)) - { - selectorInstances.Add(selector); - } - } - } - } - - propertyInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - methodInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - - var objectiveCInstances = new List(); - - context.WriteLine("[SupportedOSPlatform(\"macos\")]"); - - var modifier = GeneratorUtils.IsPartialType(Name) ? "partial " : ""; - var classDecl = $"public {modifier}struct {Name} : IDisposable"; - - context.WriteLine(classDecl); - - context.EnterScope(); - - context.WriteLine("public IntPtr NativePtr;"); - context.WriteLine($"public static implicit operator IntPtr({Name} obj) => obj.NativePtr;"); - if (_parent != string.Empty) - { - context.WriteLine($"public static implicit operator {_parent}({Name} obj) => new(obj.NativePtr);"); - } - context.WriteLine($"public {Name}(IntPtr ptr) => NativePtr = ptr;"); - - if (_hasAlloc) - { - context.WriteLine(); - context.WriteLine($"public {Name}()"); - context.EnterScope(); - - context.WriteLine($"var cls = new ObjectiveCClass(\"{Name}\");"); - - context.WriteLine(_hasInit ? "NativePtr = cls.AllocInit();" : "NativePtr = cls.Alloc();"); - - context.LeaveScope(); - } - - context.WriteLine(); - context.WriteLine("public void Dispose()"); - context.EnterScope(); - - context.WriteLine("ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release);"); - - context.LeaveScope(); - - if (propertyInstances.Count != 0) - { - context.WriteLine(); - } - - var unsortedSelectorInstances = new List(selectorInstances); - // These have to be sorted after making the copy, otherwise it might resolve to wrong selector due to the Find-based mechanism when generating the calls - selectorInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - - for (var j = 0; j < propertyInstances.Count; j++) - { - objectiveCInstances.Add(propertyInstances[j].Generate(unsortedSelectorInstances, enumCache, structCache, context)); - - if (j != propertyInstances.Count - 1) - { - context.WriteLine(); - } - } - - foreach (var method in methodInstances) - { - objectiveCInstances.Add(method.Generate(unsortedSelectorInstances, enumCache, structCache, context, _namespacePrefix)); - } - - if (selectorInstances.Count != 0) - { - context.WriteLine(); - } - - foreach (var selector in selectorInstances) - { - context.WriteLine($"private static readonly Selector {selector.Name} = \"{selector.Selector}\";"); - } - - context.WriteLine($"private static readonly Selector sel_release = \"release\";"); - - context.LeaveScope(); - return objectiveCInstances; - } - - public static ClassInstance Build(string line, string namespacePrefix, StreamReader sr) - { - var classInfo = line.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + var classInfo = classDeclarationLine.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (classInfo.Length < 3) { - Console.WriteLine($"BAD CLASS! {line}"); + Console.WriteLine($"BAD CLASS! {classDeclarationLine}"); return new ClassInstance(""); } @@ -204,10 +77,9 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea var ancestors = info.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); for (var i = 1; i < ancestors.Length; i++) { - if (ancestors[i] != "_Base" && ancestors[i] != "Type" && ancestors[i] != "objc_object" && - ancestors[i] != "Value") + if (ancestors[i] != "_Base" && ancestors[i] != "Type" && ancestors[i] != "objc_object" && ancestors[i] != "Value") { - instance._parent = Types.ConvertType(ancestors[i], namespacePrefix); + instance.Parent = Types.ConvertType(ancestors[i], namespacePrefix); } } } @@ -216,31 +88,15 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea instance._namespacePrefix = namespacePrefix; var classEnded = false; - var enteredComment = false; var isDeprecated = false; while (!classEnded) { - var nextLine = sr.ReadLine(); + var nextLine = GeneratorUtils.ReadNextCodeLine(sr); if (nextLine == null) { - continue; - } - - if (nextLine.Contains("/**")) - { - enteredComment = true; - continue; - } - - if (enteredComment) - { - if (nextLine.Contains("*/")) - { - enteredComment = false; - } - continue; + break; } if (nextLine.Contains('}') || nextLine.Contains("private:") || nextLine.Contains("protected:")) @@ -255,8 +111,8 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea continue; } - // Ignore empty lines etc... - if (nextLine is "" or "{" or "public:") + // Ignore opening literals for scopes we are interested in + if (nextLine is "{" or "public:") { continue; } @@ -310,13 +166,13 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea { if (name == "Alloc") { - instance._hasAlloc = true; + instance.HasAlloc = true; continue; } if (name == "Init") { - instance._hasInit = true; + instance.HasInit = true; continue; } @@ -330,7 +186,7 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea } else if (!(isStatic && returnType.Contains(name))) { - instance.AddProperty(new PropertyInstance(instance, returnType, name, isStatic: isStatic, isDeprecated: tempDeprecated)); + instance.AddProperty(new PropertyInstance(instance, returnType, name, rawName, isStatic: isStatic, isDeprecated: tempDeprecated)); } } else @@ -396,15 +252,15 @@ public static ClassInstance Build(string line, string namespacePrefix, StreamRea } } - for (var i = instance._propertyInstances.Count - 1; i >= 0; i--) + for (var i = instance.PropertyInstances.Count - 1; i >= 0; i--) { - var property = instance._propertyInstances[i]; - if (instance._methodInstances.Exists(x => x.Name == property.Name)) + var property = instance.PropertyInstances[i]; + if (instance.MethodInstances.Exists(x => x.Name == property.Name)) { // We can't have a property AND methods with the same name // in this case, the solution is to turn the property into a method - instance._propertyInstances.RemoveAt(i); - instance.AddMethod(new MethodInstance(property.Type, property.Name, rawName, isStatic, tempDeprecated, [])); + instance.PropertyInstances.RemoveAt(i); + instance.AddMethod(new MethodInstance(property.Type, property.Name, property.RawName, property.IsStatic, property.IsDeprecated, [])); } } } diff --git a/src/SharpMetal.Generator/Instances/EnumInstance.cs b/src/SharpMetal.Generator/Instances/EnumInstance.cs index 10d5905..f286fb3 100644 --- a/src/SharpMetal.Generator/Instances/EnumInstance.cs +++ b/src/SharpMetal.Generator/Instances/EnumInstance.cs @@ -1,47 +1,19 @@ using System.Text.RegularExpressions; +using SharpMetal.Generator.Utilities; namespace SharpMetal.Generator.Instances { - public partial class EnumInstance + public partial class EnumInstance : TypeInstance { - public readonly string Type; - public readonly string Name; + public readonly string BackingType; + public readonly bool IsFlag; + public readonly Dictionary Values; - private readonly bool _isFlag; - private readonly Dictionary _values; - - private EnumInstance(string type, string name, bool isFlag, Dictionary values) + private EnumInstance(string backingType, string name, bool isFlag, Dictionary values) : base(name) { - Type = type; - Name = name; - _isFlag = isFlag; - _values = values; - } - - public void Generate(CodeGenContext context) - { - context.WriteLine("[SupportedOSPlatform(\"macos\")]"); - if (_isFlag) - { - context.WriteLine("[Flags]"); - } - context.WriteLine($"public enum {Name} : {Type}"); - context.EnterScope(); - - foreach (var value in _values) - { - if (value.Value != string.Empty) - { - context.WriteLine($"{value.Key} = {value.Value},"); - } - else - { - context.WriteLine($"{value.Key},"); - } - } - - context.LeaveScope(); - context.WriteLine(); + BackingType = backingType; + IsFlag = isFlag; + Values = values; } public static EnumInstance Build(string line, string namespacePrefix, StreamReader sr, bool skipValues = false) @@ -75,11 +47,11 @@ public static EnumInstance Build(string line, string namespacePrefix, StreamRead while (!finishedEnumerating) { - var nextLine = sr.ReadLine(); + var nextLine = GeneratorUtils.ReadNextCodeLine(sr); - if (string.IsNullOrEmpty(nextLine)) + if (nextLine == null) { - continue; + break; } if (nextLine.Contains("};")) diff --git a/src/SharpMetal.Generator/Instances/MethodInstance.cs b/src/SharpMetal.Generator/Instances/MethodInstance.cs index 49e25ef..5322591 100644 --- a/src/SharpMetal.Generator/Instances/MethodInstance.cs +++ b/src/SharpMetal.Generator/Instances/MethodInstance.cs @@ -5,19 +5,19 @@ public class MethodInstance public readonly string Name; public readonly List InputInstances; - private readonly string _returnType; - private readonly bool _isStatic; - private readonly string _rawName; - private readonly bool _isDeprecated; + public string ReturnType { get; } + public bool IsStatic { get; } + public string RawName { get; } + public bool IsDeprecated { get; } public MethodInstance(string returnType, string name, string rawName, bool isStatic, bool isDeprecated, List inputInstances) { Name = name; InputInstances = inputInstances; - _returnType = returnType; - _isStatic = isStatic; - _isDeprecated = isDeprecated; + ReturnType = returnType; + IsStatic = isStatic; + IsDeprecated = isDeprecated; var rawNameComponents = rawName.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); for (var index = 0; index < rawNameComponents.Length; index++) @@ -33,206 +33,9 @@ public MethodInstance(string returnType, string name, string rawName, bool isSta } } - // TODO: Clean this up - rawName = rawName.Replace(";", ""); - rawName = rawName.Replace(" class ", " "); - rawName = rawName.Replace("(class ", "("); - rawName = rawName.Replace("MTL::", ""); - rawName = rawName.Replace("NS::", ""); + rawName = rawName.TrimEnd(';'); - _rawName = rawName; - } - - public ObjectiveCInstance Generate(List selectorInstances, List enumCache, List structCache, CodeGenContext context, string namespacePrefix, bool prependSpace = true) - { - // Disallow the selectors used in properties to prevent doubling of properties and methods that do the same - var selector = selectorInstances.Find(x => !x.UsedInProperty && x.RawName.ToLower().Replace(" class ", " ").Replace("mtl::", "").Replace("ns::", "").Contains(_rawName.ToLower())); - - if (selector != null) - { - var staticString = _isStatic ? "static " : ""; - // TODO: Handle array inputs - var hasArrayInput = false; - - if (prependSpace) - { - context.WriteLine(); - } - context.Write(context.Indentation + $"public {staticString}{_returnType} {Name}("); - - for (var i = 0; i < InputInstances.Count; i++) - { - var input = InputInstances[i]; - - if (input.Type.Contains("[]")) - { - hasArrayInput = true; - } - - context.Write($"{(input.Reference ? "ref " : "")}{input.Type} {input.Name}"); - - if (i != InputInstances.Count - 1) - { - context.Write(", "); - } - } - - context.Write(")\n"); - context.EnterScope(); - if (_returnType == "void" && !hasArrayInput) - { - if (_isStatic) - { - context.WriteLine("throw new NotSupportedException();"); - } - else - { - context.Write($"{context.Indentation}ObjectiveCRuntime.objc_msgSend(NativePtr, {selector.Name}"); - - for (var index = 0; index < InputInstances.Count; index++) - { - var cast = ""; - var enumInstance = enumCache.Find(x => x.Name == InputInstances[index].Type); - - if (enumInstance != null) - { - cast = $"({enumInstance.Type})"; - } - - context.Write($", {cast}{InputInstances[index].Name}"); - } - context.Write(");\n"); - } - } - else if (!hasArrayInput) - { - context.Write($"{context.Indentation}return "); - var returnEnum = enumCache.Find(x => x.Name == _returnType); - var returnStruct = structCache.Find(x => x.Name == _returnType); - var needsOuterBracket = false; - - if (returnEnum != null) - { - context.Write($"({_returnType})ObjectiveCRuntime.{returnEnum.Type}_"); - } - else - { - if (Types.CSharpNativeTypes.Contains(_returnType) || returnStruct != null) - { - context.Write($"ObjectiveCRuntime.{_returnType}_"); - } - else - { - context.Write($"new(ObjectiveCRuntime.IntPtr_"); - needsOuterBracket = true; - } - } - - context.Write($"objc_msgSend("); - - if (_isStatic) - { - context.Write($"new ObjectiveCClass(\"{_returnType}\")"); - } - else - { - context.Write("NativePtr"); - } - - context.Write($", {selector.Name}"); - - for (var index = 0; index < InputInstances.Count; index++) - { - var enumInstance = enumCache.Find(x => x.Name == InputInstances[index].Type); - var input = InputInstances[index]; - - if (enumInstance != null) - { - context.Write($", ({enumInstance.Type}){input.Name}"); - } - else if (input.Reference) - { - context.Write($", ref {input.Name}.NativePtr"); - } - else - { - context.Write($", {input.Name}"); - } - } - - if (needsOuterBracket) - { - context.Write(")"); - } - - context.Write(");\n"); - } - else - { - context.WriteLine("throw new NotImplementedException();"); - } - context.LeaveScope(); - } - - string type; - List inputs = []; - - for (var i = 0; i < InputInstances.Count; i++) - { - if (Types.CSharpNativeTypes.Contains(InputInstances[i].Type)) - { - inputs.Add(InputInstances[i].Type); - } - else - { - var enumInstance = enumCache.Find(x => x.Name == InputInstances[i].Type); - var structInstance = structCache.Find(x => x.Name == InputInstances[i].Type); - - if (enumInstance != null) - { - inputs.Add(enumInstance.Type); - } - else if (structInstance != null) - { - inputs.Add(structInstance.Name); - } - else if (InputInstances[i].Type == "NSError") - { - inputs.Add("ref IntPtr"); - } - else - { - inputs.Add("IntPtr"); - } - } - } - - if (_returnType == "void") - { - type = _returnType; - } - else - { - var returnStruct = structCache.Find(x => x.Name == _returnType); - if (Types.CSharpNativeTypes.Contains(_returnType) || returnStruct != null) - { - type = _returnType; - } - else - { - var returnEnum = enumCache.Find(x => x.Name == _returnType); - if (returnEnum != null) - { - type = returnEnum.Type; - } - else - { - type = "IntPtr"; - } - } - } - - return new ObjectiveCInstance(type, inputs); + RawName = rawName; } } } diff --git a/src/SharpMetal.Generator/Instances/ObjectiveCInstance.cs b/src/SharpMetal.Generator/Instances/ObjectiveCInstance.cs index c0b9b25..6b9e114 100644 --- a/src/SharpMetal.Generator/Instances/ObjectiveCInstance.cs +++ b/src/SharpMetal.Generator/Instances/ObjectiveCInstance.cs @@ -5,48 +5,12 @@ public class ObjectiveCInstance : IEquatable, IComparable inputs) { Type = type; Inputs = inputs.ToArray(); } - public void Generate(CodeGenContext context) - { - context.WriteLine("[LibraryImport(ObjectiveC.ObjCRuntime, EntryPoint = \"objc_msgSend\")]"); - if (Type == "bool") - { - context.WriteLine("[return: MarshalAs(UnmanagedType.Bool)]"); - } - context.Write($"{context.Indentation}internal static partial {Type} "); - - if (Type != "void") - { - context.Write($"{Type}_"); - } - - context.Write("objc_msgSend(IntPtr receiver, IntPtr selector"); - - for (var i = 0; i < Inputs.Length; i++) - { - context.Write(", "); - - if (Inputs[i] == "bool") - { - context.Write("[MarshalAs(UnmanagedType.Bool)] "); - } - - context.Write($"{Inputs[i]} {VarNames[i]}"); - } - - context.Write(");\n"); - } - public bool Equals(ObjectiveCInstance? other) { if (ReferenceEquals(null, other)) diff --git a/src/SharpMetal.Generator/Instances/PropertyInstance.cs b/src/SharpMetal.Generator/Instances/PropertyInstance.cs index 65e1888..1c7aa5a 100644 --- a/src/SharpMetal.Generator/Instances/PropertyInstance.cs +++ b/src/SharpMetal.Generator/Instances/PropertyInstance.cs @@ -8,8 +8,9 @@ public class PropertyInstance : IEquatable public readonly bool Reference; public readonly bool IsStatic; public readonly bool IsDeprecated; + public readonly string RawName; - public PropertyInstance(ClassInstance containingClass, string type, string name, bool reference = false, bool isStatic = false, bool isDeprecated = false) + public PropertyInstance(ClassInstance containingClass, string type, string name, string rawName, bool reference = false, bool isStatic = false, bool isDeprecated = false) { ContainingClass = containingClass; Type = type; @@ -17,135 +18,7 @@ public PropertyInstance(ClassInstance containingClass, string type, string name, Reference = reference; IsStatic = isStatic; IsDeprecated = isDeprecated; - } - - public ObjectiveCInstance Generate(List selectorInstances, List enumCache, List structCache, CodeGenContext context) - { - var selector = selectorInstances.Find(x => string.Equals(x.Selector, Name, StringComparison.InvariantCultureIgnoreCase)); - var type = ""; - - if (selector == null) - { - // This can sometimes select the wrong selector, so we only want to use it as a backup - selector = selectorInstances.Find(x => x.Selector.Contains(Name, StringComparison.InvariantCultureIgnoreCase)); - } - - if (selector != null) - { - selector.UsedInProperty = true; - // We assume a type of IntPtr, which encapsulates any possible type - var runtimeFuncReturn = "IntPtr"; - // Check for existing get/set pair - var setterSelector = ResolveSetterSelector(selectorInstances, selector); - - // If the property is a type that exists in C# then we can safely set the - // return type to be that type, otherwise further conversion will be needed later - if (Types.CSharpNativeTypes.Contains(Type)) - { - runtimeFuncReturn = Type; - } - - var enumInstance = enumCache.Find(x => x.Name == Type); - var structInstance = structCache.Find(x => x.Name == Type); - - var target = IsStatic ? $"new ObjectiveCClass(\"{ContainingClass.Name}\")" : "NativePtr"; - var modifier = IsStatic ? "static " : ""; - - if (IsDeprecated) - { - context.WriteLine("[System.Obsolete]"); - } - - if (setterSelector != null) - { - setterSelector.UsedInProperty = true; - context.WriteLine($"public {modifier}{Type} {Name}"); - context.EnterScope(); - - if (enumInstance != null) - { - type = enumInstance.Type; - - context.WriteLine($"get => ({enumInstance.Name})ObjectiveCRuntime.{enumInstance.Type}_objc_msgSend({target}, {selector.Name});"); - context.WriteLine($"set => ObjectiveCRuntime.objc_msgSend(NativePtr, {setterSelector.Name}, ({enumInstance.Type})value);"); - } - else if (structInstance != null) - { - type = structInstance.Name; - - context.WriteLine($"get => ObjectiveCRuntime.{structInstance.Name}_objc_msgSend({target}, {selector.Name});"); - context.WriteLine($"set => ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, value);"); - } - else - { - type = runtimeFuncReturn; - - if (runtimeFuncReturn == "IntPtr") - { - context.WriteLine($"get => new(ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name}));"); - context.WriteLine($"set => ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, value);"); - } - else - { - context.WriteLine($"get => ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name});"); - context.WriteLine($"set => ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, value);"); - } - } - - context.LeaveScope(); - } - else - { - if (enumInstance != null) - { - type = enumInstance.Type; - - context.WriteLine($"public {modifier}{Type} {Name} => ({enumInstance.Name})ObjectiveCRuntime.{enumInstance.Type}_objc_msgSend({target}, {selector.Name});"); - } - else if (structInstance != null) - { - type = structInstance.Name; - - context.WriteLine($"public {modifier}{Type} {Name} => ObjectiveCRuntime.{structInstance.Name}_objc_msgSend({target}, {selector.Name});"); - } - else - { - type = runtimeFuncReturn; - - if (runtimeFuncReturn == "IntPtr") - { - context.WriteLine($"public {modifier}{Type} {Name} => new(ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name}));"); - } - else - { - context.WriteLine($"public {modifier}{Type} {Name} => ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name});"); - } - } - } - } - - return new ObjectiveCInstance(type, []); - } - - private static SelectorInstance? ResolveSetterSelector(List selectorInstances, SelectorInstance selector) - { - // Try to match the "isFoo" + "setFoo" as a get/set property - var setterSelectorNameCandidate = selector.Selector; - if (setterSelectorNameCandidate.StartsWith("is")) - { - setterSelectorNameCandidate = setterSelectorNameCandidate.Substring(2); - } - setterSelectorNameCandidate = "set" + setterSelectorNameCandidate; - var setterSelector = selectorInstances.Find(x => x.Selector.Contains(setterSelectorNameCandidate, StringComparison.InvariantCultureIgnoreCase)); - - if (setterSelector == null) - { - // Fallback to setIsFoo, which apparently has some occurrences in the metal-cpp bindings - setterSelectorNameCandidate = "set" + selector.Selector; - setterSelector = selectorInstances.Find(x => x.Selector.Contains(setterSelectorNameCandidate, StringComparison.InvariantCultureIgnoreCase)); - } - - return setterSelector; + RawName = rawName; } public bool Equals(PropertyInstance? other) diff --git a/src/SharpMetal.Generator/Instances/SelectorInstance.cs b/src/SharpMetal.Generator/Instances/SelectorInstance.cs index 5e51516..7bb4544 100644 --- a/src/SharpMetal.Generator/Instances/SelectorInstance.cs +++ b/src/SharpMetal.Generator/Instances/SelectorInstance.cs @@ -6,9 +6,11 @@ public class SelectorInstance public readonly string RawName; public readonly string Selector; public bool UsedInProperty { get; set; } + public string MethodSignatureString { get; } - private SelectorInstance(string selector, string rawName) + private SelectorInstance(string selector, string rawName, string methodSignatureString) { + MethodSignatureString = methodSignatureString; Name = "sel_" + selector.Replace(":", ""); RawName = rawName; Selector = selector; @@ -20,6 +22,8 @@ public static void Build(string line, string namespacePrefix, StreamReader sr, L var parentStructName = string.Empty; var rawName = ""; + var methodSignatureString = ""; + for (var i = 0; i < inlineInfo.Length; i++) { var section = inlineInfo[i]; @@ -28,6 +32,15 @@ public static void Build(string line, string namespacePrefix, StreamReader sr, L var parentStructInfo = section.Split("::"); parentStructName = namespacePrefix + parentStructInfo[1]; + // Get the rest of the string as the method signature for future pairing lookups + var methodSignatureInfo = section.Split("("); + var methodSignatureBetweenColonsAndParentheses = methodSignatureInfo[0].Split("::"); + var indexOfMethodSignature = line.IndexOf(methodSignatureBetweenColonsAndParentheses[^1] + "(", StringComparison.InvariantCultureIgnoreCase); + if (indexOfMethodSignature > 0) + { + methodSignatureString = line.Substring(indexOfMethodSignature); + } + var rawNameStartIndex = line.IndexOf(section, StringComparison.Ordinal); if (rawNameStartIndex >= 0) { @@ -68,7 +81,7 @@ public static void Build(string line, string namespacePrefix, StreamReader sr, L if (parentIndex != -1) { - propertyOwners[parentIndex].AddSelector(new SelectorInstance(selector.Trim(), rawName)); + propertyOwners[parentIndex].AddSelector(new SelectorInstance(selector.Trim(), rawName, methodSignatureString)); } else { diff --git a/src/SharpMetal.Generator/Instances/StructInstance.cs b/src/SharpMetal.Generator/Instances/StructInstance.cs index 5d69db6..2015e7c 100644 --- a/src/SharpMetal.Generator/Instances/StructInstance.cs +++ b/src/SharpMetal.Generator/Instances/StructInstance.cs @@ -1,16 +1,15 @@ using System.Text.RegularExpressions; +using SharpMetal.Generator.Utilities; namespace SharpMetal.Generator.Instances { - public partial class StructInstance + public partial class StructInstance : TypeInstance { - public readonly string Name; - private readonly List _memberVariableInstances; + public List MemberVariableInstances { get; } - private StructInstance(string name) + private StructInstance(string name) : base(name) { - Name = name; - _memberVariableInstances = []; + MemberVariableInstances = []; } public void AddMemberVariable(MemberVariableInstance memberVariableInstance) @@ -21,28 +20,12 @@ public void AddMemberVariable(MemberVariableInstance memberVariableInstance) return; } - if (!_memberVariableInstances.Exists(x => x.Name == memberVariableInstance.Name)) + if (!MemberVariableInstances.Exists(x => x.Name == memberVariableInstance.Name)) { - _memberVariableInstances.Add(memberVariableInstance); + MemberVariableInstances.Add(memberVariableInstance); } } - public void Generate(CodeGenContext context) - { - context.WriteLine("[SupportedOSPlatform(\"macos\")]"); - context.WriteLine("[StructLayout(LayoutKind.Sequential)]"); - - context.WriteLine($"public struct {Name}"); - context.EnterScope(); - - for (var j = 0; j < _memberVariableInstances.Count; j++) - { - _memberVariableInstances[j].Generate(context); - } - - context.LeaveScope(); - } - public static StructInstance Build(string line, string namespacePrefix, StreamReader sr, bool skipValues = false) { var structInfo = line.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); @@ -55,7 +38,11 @@ public static StructInstance Build(string line, string namespacePrefix, StreamRe while (!structEnded) { - var propertyLine = sr.ReadLine() ?? ""; + var propertyLine = GeneratorUtils.ReadNextCodeLine(sr); + if (propertyLine == null) + { + break; + } if (propertyLine.Contains('}')) { diff --git a/src/SharpMetal.Generator/Instances/TypeInstance.cs b/src/SharpMetal.Generator/Instances/TypeInstance.cs new file mode 100644 index 0000000..a541063 --- /dev/null +++ b/src/SharpMetal.Generator/Instances/TypeInstance.cs @@ -0,0 +1,12 @@ +namespace SharpMetal.Generator.Instances +{ + public abstract class TypeInstance + { + public string Name { get; } + + protected TypeInstance(string name) + { + Name = name; + } + } +} diff --git a/src/SharpMetal.Generator/ModelTransformer.cs b/src/SharpMetal.Generator/ModelTransformer.cs new file mode 100644 index 0000000..07aae1c --- /dev/null +++ b/src/SharpMetal.Generator/ModelTransformer.cs @@ -0,0 +1,175 @@ +using SharpMetal.Generator.Instances; +using SharpMetal.Generator.CSharpCodeGen; +using SharpMetal.Generator.Transformers; +using SharpMetal.Generator.Utilities; + +namespace SharpMetal.Generator +{ + public class ModelTransformer + { + private static string[] VarNames { get; } = + [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" + ]; + + public List Link(ParsedModel parsedModel) + { + var objectiveCInstances = new HashSet(); + + var rv = new List(); + + foreach (var header in parsedModel.Headers) + { + var outputFile = new CSharpFile(header.FileName, header.FullNamespace); + var ns = outputFile.GetOrCreateNamespace($"SharpMetal.{header.FullNamespace}"); + + header.EnumInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + header.StructInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + header.ClassInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + + foreach (var enumInstance in header.EnumInstances) + { + ns.AddType(EnumTransformer.TransformEnum(enumInstance)); + } + + foreach (var structInstance in header.StructInstances) + { + ns.AddType(StructTransformer.TransformStruct(structInstance)); + } + + foreach (var classInstance in header.ClassInstances) + { + ns.AddType(ClassTransformer.TransformClass(parsedModel, objectiveCInstances, classInstance)); + } + + AddUsings(outputFile, header); + + rv.Add(outputFile); + } + + rv.Add(GenerateObjectiveC(objectiveCInstances)); + + return rv; + } + + public static CSharpFile GenerateObjectiveC(HashSet objectiveCInstances) + { + var outputFile = new CSharpFile("ObjectiveCRuntime"); + + objectiveCInstances.RemoveWhere(x => x.Type == string.Empty); + + outputFile.AddUsing("System.Runtime.InteropServices"); + outputFile.AddUsing("System.Runtime.Versioning"); + outputFile.AddUsing("SharpMetal.ObjectiveCCore"); + outputFile.AddUsing("SharpMetal.Foundation"); + outputFile.AddUsing("SharpMetal.Metal"); + + var ns = outputFile.GetOrCreateNamespace("SharpMetal"); + + var csClass = new CSharpClassType("ObjectiveCRuntime"); + csClass.IsPartial = true; + csClass.IsStatic = true; + csClass.VisibilityModifier = "internal"; + ns.AddType(csClass); + + var list = objectiveCInstances.ToList(); + list.Sort(ObjectiveCEmitOrderComparer); + + foreach (var item in list) + { + string name = item.Type != "void" ? $"{item.Type}_objc_msgSend" : "objc_msgSend"; + var parameters = new List<(string, string, string)>(); + parameters.Add(("IntPtr", "receiver", "")); + parameters.Add(("IntPtr", "selector", "")); + for (var i = 0; i < item.Inputs.Length; i++) + { + var attr = ""; + if (item.Inputs[i] == "bool") + { + attr = "[MarshalAs(UnmanagedType.Bool)]"; + } + parameters.Add((item.Inputs[i], VarNames[i], attr)); + } + var method = new CSharpMethod(name, item.Type, parameters); + method.IsStatic = true; + method.IsPartial = true; + method.VisibilityModifier = "internal"; + method.AddAttribute("[LibraryImport(ObjectiveC.ObjCRuntime, EntryPoint = \"objc_msgSend\")]"); + if (item.Type == "bool") + { + method.AddAttribute("[return: MarshalAs(UnmanagedType.Bool)]"); + } + + csClass.AddMember(method); + + } + + return outputFile; + + static int ObjectiveCEmitOrderComparer(ObjectiveCInstance x, ObjectiveCInstance y) + { + var typeComparison = string.Compare(x.Type, y.Type, StringComparison.InvariantCultureIgnoreCase); + if (typeComparison != 0) + { + return typeComparison; + } + + var lengthComparison = x.Inputs.Length.CompareTo(y.Inputs.Length); + if (lengthComparison != 0) + { + return lengthComparison; + } + + var xInputs = string.Join(" ", x.Inputs); + var yInputs = string.Join(" ", y.Inputs); + return string.Compare(xInputs, yInputs, StringComparison.InvariantCultureIgnoreCase); + } + } + + private static void AddUsings(CSharpFile outputFile, HeaderInfo headerInfo) + { + if (headerInfo.StructInstances.Count != 0) + { + outputFile.AddUsing("System.Runtime.InteropServices"); + } + + if (headerInfo.StructInstances.Count != 0 || headerInfo.ClassInstances.Count != 0 || headerInfo.EnumInstances.Count != 0) + { + outputFile.AddUsing("System.Runtime.Versioning"); + } + + // If have any class in the file, we need selectors due to ctors/disposes + if (headerInfo.ClassInstances.Count != 0) + { + outputFile.AddUsing("SharpMetal.ObjectiveCCore"); + } + + if (headerInfo.Flags != IncludeFlags.None) + { + if ((headerInfo.Flags & IncludeFlags.Foundation) == IncludeFlags.Foundation) + { + if (outputFile.Directory != "Foundation") + { + outputFile.AddUsing("SharpMetal.Foundation"); + } + } + + if ((headerInfo.Flags & IncludeFlags.Metal) == IncludeFlags.Metal) + { + if (outputFile.Directory != "Metal") + { + outputFile.AddUsing("SharpMetal.Metal"); + } + } + + if ((headerInfo.Flags & IncludeFlags.QuartzCore) == IncludeFlags.QuartzCore) + { + if (outputFile.Directory != "QuartzCore") + { + outputFile.AddUsing("SharpMetal.QuartzCore"); + } + } + } + } + } +} diff --git a/src/SharpMetal.Generator/Namespaces.cs b/src/SharpMetal.Generator/Namespaces.cs index f637a03..4bdff86 100644 --- a/src/SharpMetal.Generator/Namespaces.cs +++ b/src/SharpMetal.Generator/Namespaces.cs @@ -64,6 +64,7 @@ public static string GetMacroNamespace(string namespacePrefix) { return "MTLFX"; } + return namespacePrefix; } diff --git a/src/SharpMetal.Generator/ParsedModel.cs b/src/SharpMetal.Generator/ParsedModel.cs new file mode 100644 index 0000000..947eb1b --- /dev/null +++ b/src/SharpMetal.Generator/ParsedModel.cs @@ -0,0 +1,53 @@ +using SharpMetal.Generator.Instances; + +namespace SharpMetal.Generator +{ + /// + /// Collected data from the parser, representing the C++ code + /// + public readonly ref struct ParsedModel + { + public List Headers { get; } = []; + public List Enums { get; } = []; + public List Structs { get; } = []; + public List Classes { get; } = []; + + public ParsedModel() + { + } + + public void AddHeader(HeaderInfo header) + { + Headers.Add(header); + Enums.AddRange(header.EnumInstances); + Structs.AddRange(header.StructInstances); + Classes.AddRange(header.ClassInstances); + } + + public ClassInstance? FindClass(string typeName) + { + return Classes.Find(x => x.Name == typeName); + } + + public EnumInstance? FindEnum(string typeName) + { + return Enums.Find(x => x.Name == typeName); + } + + public TypeInstance? FindType(string typeName) + { + var enumInstance = FindEnum(typeName); + if (enumInstance != null) + { + return enumInstance; + } + + return FindStruct(typeName); + } + + public StructInstance? FindStruct(string typeName) + { + return Structs.Find(x => x.Name == typeName); + } + } +} diff --git a/src/SharpMetal.Generator/Program.cs b/src/SharpMetal.Generator/Program.cs index e706652..12807e8 100644 --- a/src/SharpMetal.Generator/Program.cs +++ b/src/SharpMetal.Generator/Program.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using SharpMetal.Generator.Instances; +using SharpMetal.Generator.CSharpCodeGen; namespace SharpMetal.Generator { @@ -31,33 +32,33 @@ public static void Main(string[] args) var headers = Directory.GetFiles(generatorProjectPath.FullName, "*.hpp", SearchOption.AllDirectories) .Where(header => !header.Contains("Defines") && !header.Contains("Private")).ToArray(); - var headerInfos = new List(); + var parsedModel = new ParsedModel(); - var enumCache = new List(); - var structCache = new List(); - var classCache = new List(); + // High-level overview of the generator: + // 0. Parse the files into our representation model (*Instance, uses cpp/swift naming) + // 1. Transform the model into actual C# representable types (uses C# naming) + // 2. Emit the code - lightweight emit that should not modify the prepared data in any way + // Parse foreach (var header in headers) { var info = GenerateHeaderInfo(header); if (info != null) { - headerInfos.Add(info); - enumCache.AddRange(info.EnumInstances); - structCache.AddRange(info.StructInstances); - classCache.AddRange(info.ClassInstances); + parsedModel.AddHeader(info); } } - var objectiveCInstances = new HashSet(); + // Transform + ModelTransformer transformer = new ModelTransformer(); + var csharpFiles = transformer.Link(parsedModel); - foreach (var header in headerInfos) + // Emit + foreach (var csharpFile in csharpFiles) { - Generate(header, classCache, enumCache, structCache, ref objectiveCInstances); + Generate(csharpFile); } - - GenerateObjectiveC(objectiveCInstances); } public static HeaderInfo? GenerateHeaderInfo(string filePath) @@ -72,170 +73,15 @@ public static void Main(string[] args) return headerInfo; } - public static void Generate(HeaderInfo headerInfo, List classCache, List enumCache, List structCache, ref HashSet objectiveCInstances) - { - var fileName = Path.GetFileNameWithoutExtension(headerInfo.FilePath); - var fullNamespace = Namespaces.GetFullNamespace(headerInfo.FilePath); - - Directory.CreateDirectory(fullNamespace); - - using CodeGenContext context = new(File.CreateText($"{fullNamespace}/{fileName}.cs")); - - GenerateUsings(headerInfo, context, fullNamespace); - - context.WriteLine($"namespace SharpMetal.{fullNamespace}"); - context.EnterScope(); - - headerInfo.EnumInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - headerInfo.StructInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - headerInfo.ClassInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); - - foreach (var instance in headerInfo.EnumInstances.OrderBy(x => x.Name)) - { - instance.Generate(context); - } - - for (var i = 0; i < headerInfo.StructInstances.Count; i++) - { - headerInfo.StructInstances[i].Generate(context); - - if (headerInfo.ClassInstances.Count != 0) - { - context.WriteLine(); - } - else if (i != headerInfo.StructInstances.Count - 1) - { - context.WriteLine(); - } - } - - for (var i = 0; i < headerInfo.ClassInstances.Count; i++) - { - var instances = headerInfo.ClassInstances[i].Generate(classCache, enumCache, structCache, context); - - foreach (var instance in instances) - { - objectiveCInstances.Add(instance); - } - - if (i != headerInfo.ClassInstances.Count - 1) - { - context.WriteLine(); - } - } - - context.LeaveScope(); - } - - public static void GenerateUsings(HeaderInfo headerInfo, CodeGenContext context, string fullNamespace) + public static void Generate(CSharpFile cSharpFile) { - var hasAnyUsings = false; - - if (headerInfo.StructInstances.Count != 0) + if (!string.IsNullOrEmpty(cSharpFile.Directory)) { - context.WriteLine("using System.Runtime.InteropServices;"); - hasAnyUsings = true; + Directory.CreateDirectory(cSharpFile.Directory); } + using CodeGenContext context = new(File.CreateText(cSharpFile.FilePath)); - if (headerInfo.StructInstances.Count != 0 || headerInfo.ClassInstances.Count != 0 || headerInfo.EnumInstances.Count != 0) - { - context.WriteLine("using System.Runtime.Versioning;"); - hasAnyUsings = true; - } - - // If have any class in the file, we need selectors due to ctors/disposes - if (headerInfo.ClassInstances.Count != 0) - { - context.WriteLine("using SharpMetal.ObjectiveCCore;"); - hasAnyUsings = true; - } - - if (headerInfo.IncludeFlags != IncludeFlags.None) - { - hasAnyUsings = true; - if ((headerInfo.IncludeFlags & IncludeFlags.Foundation) == IncludeFlags.Foundation) - { - if (fullNamespace != "Foundation") - { - context.WriteLine("using SharpMetal.Foundation;"); - } - } - if ((headerInfo.IncludeFlags & IncludeFlags.Metal) == IncludeFlags.Metal) - { - if (fullNamespace != "Metal") - { - context.WriteLine("using SharpMetal.Metal;"); - } - } - if ((headerInfo.IncludeFlags & IncludeFlags.QuartzCore) == IncludeFlags.QuartzCore) - { - if (fullNamespace != "QuartzCore") - { - context.WriteLine("using SharpMetal.QuartzCore;"); - } - } - } - - if (hasAnyUsings) - { - context.WriteLine(); - } - } - - public static void GenerateObjectiveC(HashSet objectiveCInstances) - { - objectiveCInstances.RemoveWhere(x => x.Type == string.Empty); - - using CodeGenContext context = new(File.CreateText("ObjectiveCRuntime.cs")); - - context.WriteLine("using System.Runtime.InteropServices;"); - context.WriteLine("using System.Runtime.Versioning;"); - context.WriteLine("using SharpMetal.ObjectiveCCore;"); - context.WriteLine("using SharpMetal.Foundation;"); - context.WriteLine("using SharpMetal.Metal;"); - context.WriteLine(); - - context.WriteLine("namespace SharpMetal"); - context.EnterScope(); - - context.WriteLine("[SupportedOSPlatform(\"macos\")]"); - context.WriteLine("internal static partial class ObjectiveCRuntime"); - context.EnterScope(); - - var list = objectiveCInstances.ToList(); - list.Sort(ObjectiveCEmitOrderComparer); - - for (var i = 0; i < list.Count; i++) - { - list[i].Generate(context); - if (i != list.Count - 1) - { - context.WriteLine(); - } - } - - context.LeaveScope(); - context.LeaveScope(); - return; - - static int ObjectiveCEmitOrderComparer(ObjectiveCInstance x, ObjectiveCInstance y) - { - var typeComparison = string.Compare(x.Type, y.Type, StringComparison.InvariantCultureIgnoreCase); - if (typeComparison != 0) - { - return typeComparison; - } - - var lengthComparison = x.Inputs.Length.CompareTo(y.Inputs.Length); - if (lengthComparison != 0) - { - return lengthComparison; - } - - var xInputs = string.Join(" ", x.Inputs); - var yInputs = string.Join(" ", y.Inputs); - return string.Compare(xInputs, yInputs, StringComparison.InvariantCultureIgnoreCase); - } + cSharpFile.Generate(context); } } } diff --git a/src/SharpMetal.Generator/Transformers/ClassTransformer.cs b/src/SharpMetal.Generator/Transformers/ClassTransformer.cs new file mode 100644 index 0000000..2a3d7e2 --- /dev/null +++ b/src/SharpMetal.Generator/Transformers/ClassTransformer.cs @@ -0,0 +1,450 @@ +using SharpMetal.Generator.CSharpCodeGen; +using SharpMetal.Generator.Instances; +using SharpMetal.Generator.Utilities; + +namespace SharpMetal.Generator.Transformers +{ + public static class ClassTransformer + { + public static CSharpStructType TransformClass(ParsedModel parsedModel, HashSet objectiveCInstances, ClassInstance classInstance) + { + // Make copies since we will modify these by adding the hiearchy + // This is not ideal, but is the simplest way to make it independent on the processing order + var propertyInstances = new List(classInstance.PropertyInstances); + var methodInstances = new List(classInstance.MethodInstances); + var selectorInstances = new List(classInstance.SelectorInstances); + + ResolveClassHierarchy(parsedModel, classInstance, propertyInstances, methodInstances, selectorInstances); + + propertyInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + methodInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + + var csClass = new CSharpStructType(classInstance.Name); + csClass.IsPartial = GeneratorUtils.IsPartialType(classInstance.Name); + csClass.BaseTypes.Add("IDisposable"); + csClass.AddMember(new CSharpField("IntPtr", "NativePtr")); + + var implicitToIntPtr = new CSharpMethod("IntPtr", "implicit operator", (classInstance.Name, "obj", "")); + implicitToIntPtr.IsStatic = true; + implicitToIntPtr.PreferExpressionBody = true; + implicitToIntPtr.AddBodyLine("obj.NativePtr"); + csClass.AddMember(implicitToIntPtr); + + if (classInstance.Parent != string.Empty) + { + var implicitToParent = new CSharpMethod(classInstance.Parent, "implicit operator", (classInstance.Name, "obj", "")); + implicitToParent.IsStatic = true; + implicitToParent.PreferExpressionBody = true; + implicitToParent.AddBodyLine("new(obj.NativePtr)"); + csClass.AddMember(implicitToParent); + } + + var ctor = new CSharpMethod(classInstance.Name, "", ("IntPtr", "ptr", "")); + ctor.PreferExpressionBody = true; + ctor.AddBodyLine("NativePtr = ptr"); + csClass.AddMember(ctor); + + if (classInstance.HasAlloc) + { + var parameterlessCtor = new CSharpMethod(classInstance.Name, ""); + parameterlessCtor.AddBodyLine($"var cls = new ObjectiveCClass(\"{classInstance.Name}\")"); + parameterlessCtor.AddBodyLine(classInstance.HasInit ? "NativePtr = cls.AllocInit()" : "NativePtr = cls.Alloc()"); + csClass.AddMember(parameterlessCtor); + } + + var dispose = new CSharpMethod("Dispose", "void"); + dispose.AddBodyLine("ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release)"); + csClass.AddMember(dispose); + + // These have to be sorted after making the copy, otherwise it might resolve to wrong selector due to the Find-based mechanism when generating the calls + selectorInstances.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase)); + + foreach (var property in propertyInstances) + { + LinkClassProperty(parsedModel, objectiveCInstances, selectorInstances, property, csClass); + } + + foreach (var method in methodInstances) + { + LinkClassMethod(parsedModel, objectiveCInstances, selectorInstances, method, csClass); + } + + foreach (var selector in selectorInstances) + { + var selectorField = new CSharpField("Selector", selector.Name); + selectorField.IsStatic = true; + selectorField.IsReadonly = true; + selectorField.VisibilityModifier = "private"; + selectorField.DefaultValue = $"\"{selector.Selector}\""; + csClass.AddMember(selectorField); + } + + var selectorReleaseField = new CSharpField("Selector", "sel_release"); + selectorReleaseField.IsStatic = true; + selectorReleaseField.IsReadonly = true; + selectorReleaseField.VisibilityModifier = "private"; + selectorReleaseField.DefaultValue = "\"release\""; + csClass.AddMember(selectorReleaseField); + + return csClass; + } + + private static void LinkClassMethod(ParsedModel parsedModel, HashSet objectiveCInstances, List selectorInstances, MethodInstance method, CSharpStructType csClass) + { + var matches = selectorInstances.FindAll(x => !x.UsedInProperty && string.Equals(SanitizeSelectorLookups(x.MethodSignatureString), SanitizeSelectorLookups(method.RawName), StringComparison.InvariantCultureIgnoreCase)); + if (matches.Count > 1) + { + Console.WriteLine($"Ambiguous matches for {method.RawName}"); + foreach (var match in matches) + { + Console.WriteLine($"- {match.MethodSignatureString}"); + } + } + + // Disallow the selectors used in properties to prevent doubling of properties and methods that do the same + var selector = matches.FirstOrDefault(); + if (selector != null) + { + var parameters = new List<(string, string, string)>(); + + // TODO: Handle array inputs + var hasArrayInput = false; + + for (var i = 0; i < method.InputInstances.Count; i++) + { + var input = method.InputInstances[i]; + + if (input.Type.Contains("[]")) + { + hasArrayInput = true; + } + + parameters.Add(($"{(input.Reference ? "ref " : "")}{input.Type}", input.Name, "")); + } + + var csMethod = new CSharpMethod(method.Name, method.ReturnType, parameters); + csMethod.IsStatic = method.IsStatic; + csClass.AddMember(csMethod); + + if (method.ReturnType == "void" && !hasArrayInput) + { + if (method.IsStatic) + { + csMethod.AddBodyLine("throw new NotSupportedException()"); + } + else + { + var call = $"ObjectiveCRuntime.objc_msgSend(NativePtr, {selector.Name}"; + + for (var index = 0; index < method.InputInstances.Count; index++) + { + var cast = ""; + var enumInstance = parsedModel.FindEnum(method.InputInstances[index].Type); + + if (enumInstance != null) + { + cast = $"({enumInstance.BackingType})"; + } + + call += $", {cast}{method.InputInstances[index].Name}"; + } + + call += ")"; + csMethod.AddBodyLine(call); + } + } + else if (!hasArrayInput) + { + var line = "return "; + var returnEnum = parsedModel.FindEnum(method.ReturnType); + var returnStruct = parsedModel.FindStruct(method.ReturnType); + var needsOuterBracket = false; + + if (returnEnum != null) + { + line += $"({method.ReturnType})ObjectiveCRuntime.{returnEnum.BackingType}_"; + } + else + { + if (Types.CSharpNativeTypes.Contains(method.ReturnType) || returnStruct != null) + { + line += $"ObjectiveCRuntime.{method.ReturnType}_"; + } + else + { + line += $"new(ObjectiveCRuntime.IntPtr_"; + needsOuterBracket = true; + } + } + + line += "objc_msgSend("; + + if (method.IsStatic) + { + line += $"new ObjectiveCClass(\"{method.ReturnType}\")"; + } + else + { + line += "NativePtr"; + } + + line += $", {selector.Name}"; + + for (var index = 0; index < method.InputInstances.Count; index++) + { + var enumInstance = parsedModel.FindEnum(method.InputInstances[index].Type); + var input = method.InputInstances[index]; + + if (enumInstance != null) + { + line += $", ({enumInstance.BackingType}){input.Name}"; + } + else if (input.Reference) + { + line += $", ref {input.Name}.NativePtr"; + } + else + { + line += $", {input.Name}"; + } + } + + if (needsOuterBracket) + { + line += ")"; + } + + line += ")"; + + csMethod.AddBodyLine(line); + } + else + { + csMethod.AddBodyLine("throw new NotImplementedException()"); + } + } + + string type; + List inputs = []; + + for (var i = 0; i < method.InputInstances.Count; i++) + { + if (Types.CSharpNativeTypes.Contains(method.InputInstances[i].Type)) + { + inputs.Add(method.InputInstances[i].Type); + } + else + { + var enumInstance = parsedModel.FindEnum(method.InputInstances[i].Type); + var structInstance = parsedModel.FindStruct(method.InputInstances[i].Type); + + if (enumInstance != null) + { + inputs.Add(enumInstance.BackingType); + } + else if (structInstance != null) + { + inputs.Add(structInstance.Name); + } + else if (method.InputInstances[i].Type == "NSError") + { + inputs.Add("ref IntPtr"); + } + else + { + inputs.Add("IntPtr"); + } + } + } + + if (method.ReturnType == "void") + { + type = method.ReturnType; + } + else + { + var returnStruct = parsedModel.FindStruct(method.ReturnType); + if (Types.CSharpNativeTypes.Contains(method.ReturnType) || returnStruct != null) + { + type = method.ReturnType; + } + else + { + var returnEnum = parsedModel.FindEnum(method.ReturnType); + if (returnEnum != null) + { + type = returnEnum.BackingType; + } + else + { + type = "IntPtr"; + } + } + } + + objectiveCInstances.Add(new ObjectiveCInstance(type, inputs)); + } + + private static void LinkClassProperty(ParsedModel parsedModel, HashSet objectiveCInstances, List selectorInstances, PropertyInstance property, CSharpStructType csClass) + { + var selector = selectorInstances.Find(x => string.Equals(x.Selector, property.Name, StringComparison.InvariantCultureIgnoreCase)); + var type = ""; + + if (selector == null) + { + // This can sometimes select the wrong selector, so we only want to use it as a backup + selector = selectorInstances.Find(x => x.Selector.Contains(property.Name, StringComparison.InvariantCultureIgnoreCase)); + } + + if (selector != null) + { + var csProperty = new CSharpProperty(property.Name, property.Type); + csClass.AddMember(csProperty); + + selector.UsedInProperty = true; + // We assume a type of IntPtr, which encapsulates any possible type + var runtimeFuncReturn = "IntPtr"; + // Check for existing get/set pair + var setterSelector = ResolveSetterSelector(selectorInstances, selector); + if (setterSelector != null) + { + setterSelector.UsedInProperty = true; + } + + // If the property is a type that exists in C# then we can safely set the + // return type to be that type, otherwise further conversion will be needed later + if (Types.CSharpNativeTypes.Contains(property.Type)) + { + runtimeFuncReturn = property.Type; + } + + var enumOrStructInstance = parsedModel.FindType(property.Type); + + var target = property.IsStatic ? $"new ObjectiveCClass(\"{property.ContainingClass.Name}\")" : "NativePtr"; + csProperty.IsStatic = property.IsStatic; + + if (property.IsDeprecated) + { + csProperty.AddAttribute("[System.Obsolete]"); + } + + if (enumOrStructInstance is EnumInstance enumInstance) + { + type = enumInstance.BackingType; + + csProperty.Getter = $"({enumInstance.Name})ObjectiveCRuntime.{enumInstance.BackingType}_objc_msgSend({target}, {selector.Name})"; + if (setterSelector != null) + { + csProperty.Setter = $"ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, ({enumInstance.BackingType})value)"; + } + } + else if (enumOrStructInstance is StructInstance structInstance) + { + type = structInstance.Name; + + csProperty.Getter = $"ObjectiveCRuntime.{structInstance.Name}_objc_msgSend({target}, {selector.Name})"; + if (setterSelector != null) + { + csProperty.Setter = $"ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, value)"; + } + } + else + { + type = runtimeFuncReturn; + + if (runtimeFuncReturn == "IntPtr") + { + csProperty.Getter = $"new(ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name}))"; + } + else + { + csProperty.Getter = $"ObjectiveCRuntime.{runtimeFuncReturn}_objc_msgSend({target}, {selector.Name})"; + } + + if (setterSelector != null) + { + csProperty.Setter = $"ObjectiveCRuntime.objc_msgSend({target}, {setterSelector.Name}, value)"; + } + } + } + + objectiveCInstances.Add(new ObjectiveCInstance(type, [])); + } + + private static void ResolveClassHierarchy(ParsedModel parsedModel, ClassInstance classInstance, List propertyInstances, List methodInstances, List selectorInstances) + { + var parentName = classInstance.Parent; + while (parentName != string.Empty) + { + // To properly fit within expected C# patterns, we + // should generate an interface to hold the associated + // properties and methods to allow for proper casting + // between types. Right now, due to the limitations of + // struct inheritance, we will just duplicate all + // properties and methods from the parent to the child. + // This limitation is not present with the old method + // using classes, however that comes with performance + // and memory drawbacks, that structs are able to avoid. + + var parent = parsedModel.FindClass(parentName); + parentName = parent?.Parent ?? string.Empty; + if (parent != null) + { + // Just add unique instances, since the MTLAllocation inheritance would cause doubled-up properties + foreach (var property in parent.PropertyInstances) + { + if (!propertyInstances.Any(x => x.Name == property.Name && x.Type == property.Type)) + { + propertyInstances.Add(property); + } + } + + methodInstances.AddRange(parent.MethodInstances); + foreach (var selector in parent.SelectorInstances) + { + if (selectorInstances.All(x => x.Name != selector.Name)) + { + selectorInstances.Add(selector); + } + } + } + } + } + + /// + /// Sanitize the lookups by removing the namespace prefixes. They are unfortunately inconsistently used in the cpp bindings, so this is the most reasonable way to approach the issue. + /// + /// The signature parsed from metal-cpp + /// Sanitized signature without namespace prefixes + private static string SanitizeSelectorLookups(string input) + { + return input.Replace(" class ", " ") + .Replace("(class ", "(") + .Replace("NS::", "") + .Replace("MTL4FX::", "") + .Replace("MTLFX::", ""); + } + + private static SelectorInstance? ResolveSetterSelector(List selectorInstances, SelectorInstance selector) + { + // Try to match the "isFoo" + "setFoo" as a get/set property + var setterSelectorNameCandidate = selector.Selector; + if (setterSelectorNameCandidate.StartsWith("is")) + { + setterSelectorNameCandidate = setterSelectorNameCandidate.Substring(2); + } + + setterSelectorNameCandidate = "set" + setterSelectorNameCandidate; + var setterSelector = selectorInstances.Find(x => x.Selector.Contains(setterSelectorNameCandidate, StringComparison.InvariantCultureIgnoreCase)); + + if (setterSelector == null) + { + // Fallback to setIsFoo, which apparently has some occurrences in the metal-cpp bindings + setterSelectorNameCandidate = "set" + selector.Selector; + setterSelector = selectorInstances.Find(x => x.Selector.Contains(setterSelectorNameCandidate, StringComparison.InvariantCultureIgnoreCase)); + } + + return setterSelector; + } + } +} diff --git a/src/SharpMetal.Generator/Transformers/EnumTransformer.cs b/src/SharpMetal.Generator/Transformers/EnumTransformer.cs new file mode 100644 index 0000000..e28c118 --- /dev/null +++ b/src/SharpMetal.Generator/Transformers/EnumTransformer.cs @@ -0,0 +1,21 @@ +using SharpMetal.Generator.CSharpCodeGen; +using SharpMetal.Generator.Instances; + +namespace SharpMetal.Generator.Transformers +{ + public static class EnumTransformer + { + public static CSharpEnumType TransformEnum(EnumInstance enumInstance) + { + var csEnum = new CSharpEnumType(enumInstance.Name); + csEnum.SetPrimitiveType(enumInstance.BackingType); + csEnum.AddValues(enumInstance.Values); + if (enumInstance.IsFlag) + { + csEnum.MarkAsFlags(); + } + + return csEnum; + } + } +} diff --git a/src/SharpMetal.Generator/Transformers/StructTransformer.cs b/src/SharpMetal.Generator/Transformers/StructTransformer.cs new file mode 100644 index 0000000..8c6be47 --- /dev/null +++ b/src/SharpMetal.Generator/Transformers/StructTransformer.cs @@ -0,0 +1,22 @@ +using SharpMetal.Generator.CSharpCodeGen; +using SharpMetal.Generator.Instances; + +namespace SharpMetal.Generator.Transformers +{ + public static class StructTransformer + { + public static CSharpStructType TransformStruct(StructInstance structInstance) + { + var csStruct = new CSharpStructType(structInstance.Name); + foreach (var memberVariable in structInstance.MemberVariableInstances) + { + csStruct.AddMember(new CSharpField(memberVariable.Type, memberVariable.Name)); + } + + // Mark as sequential layout + csStruct.Attributes.Add("[StructLayout(LayoutKind.Sequential)]"); + + return csStruct; + } + } +} diff --git a/src/SharpMetal.Generator/Utilities/GeneratorUtils.cs b/src/SharpMetal.Generator/Utilities/GeneratorUtils.cs index a91dbb0..62f8af2 100644 --- a/src/SharpMetal.Generator/Utilities/GeneratorUtils.cs +++ b/src/SharpMetal.Generator/Utilities/GeneratorUtils.cs @@ -19,5 +19,39 @@ public static class GeneratorUtils public static bool IsPartialType(string name) => PartialTypes.Contains(name); public static bool IsBannedType(string name) => BannedTypes.Contains(name); + + /// + /// Consumes given stream lines until it hits a non-empty line that is not part of a comment + /// + /// The code line or null if stream ended + public static string? ReadNextCodeLine(StreamReader sr) + { + bool insideComment = false; + while (!sr.EndOfStream) + { + var line = (sr.ReadLine() ?? "").Trim(); + if (line == string.Empty || line.StartsWith("//")) + { + continue; + } + + if (line.Contains("/**")) + { + insideComment = true; + } + + if (line.Contains("*/")) + { + insideComment = false; + } + + if (!insideComment) + { + return line; + } + } + + return null; + } } } From f11efe0e1553c430d50539caa82b4f69e2e14e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Kou=C5=99il?= Date: Mon, 20 Oct 2025 22:50:29 +0200 Subject: [PATCH 2/2] Regenerated bindings with edge case fixes and more consistent whitespaces --- src/SharpMetal/Foundation/NSEnumerator.cs | 2 -- src/SharpMetal/Foundation/NSLock.cs | 1 + src/SharpMetal/Foundation/NSObjCRuntime.cs | 1 - src/SharpMetal/Foundation/NSObject.cs | 1 + src/SharpMetal/Metal/MTL4CommandQueue.cs | 1 + src/SharpMetal/Metal/MTL4FunctionDescriptor.cs | 1 + src/SharpMetal/Metal/MTLCommandBuffer.cs | 6 +++--- src/SharpMetal/Metal/MTLDataType.cs | 1 - src/SharpMetal/Metal/MTLDevice.cs | 12 ++++++------ src/SharpMetal/Metal/MTLFunctionStitching.cs | 3 +++ src/SharpMetal/Metal/MTLIOCompressor.cs | 1 - src/SharpMetal/Metal/MTLLogState.cs | 1 + src/SharpMetal/Metal/MTLPixelFormat.cs | 1 - src/SharpMetal/MetalFX/MTL4FXTemporalScaler.cs | 1 - src/SharpMetal/MetalFX/MTLFXTemporalScaler.cs | 3 +-- 15 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/SharpMetal/Foundation/NSEnumerator.cs b/src/SharpMetal/Foundation/NSEnumerator.cs index bc6554a..9aad76b 100644 --- a/src/SharpMetal/Foundation/NSEnumerator.cs +++ b/src/SharpMetal/Foundation/NSEnumerator.cs @@ -24,8 +24,6 @@ public void Dispose() ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } - - public ulong CountByEnumerating(NSFastEnumerationState pState, NSObject pBuffer, ulong len) { return ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_countByEnumeratingWithStateobjectscount, pState, pBuffer, len); diff --git a/src/SharpMetal/Foundation/NSLock.cs b/src/SharpMetal/Foundation/NSLock.cs index ec1d298..fc3d990 100644 --- a/src/SharpMetal/Foundation/NSLock.cs +++ b/src/SharpMetal/Foundation/NSLock.cs @@ -59,6 +59,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } } diff --git a/src/SharpMetal/Foundation/NSObjCRuntime.cs b/src/SharpMetal/Foundation/NSObjCRuntime.cs index 66ea967..755e078 100644 --- a/src/SharpMetal/Foundation/NSObjCRuntime.cs +++ b/src/SharpMetal/Foundation/NSObjCRuntime.cs @@ -9,5 +9,4 @@ public enum NSComparisonResult : long OrderedSame, OrderedDescending, } - } diff --git a/src/SharpMetal/Foundation/NSObject.cs b/src/SharpMetal/Foundation/NSObject.cs index 80d2225..5541db3 100644 --- a/src/SharpMetal/Foundation/NSObject.cs +++ b/src/SharpMetal/Foundation/NSObject.cs @@ -63,6 +63,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } } diff --git a/src/SharpMetal/Metal/MTL4CommandQueue.cs b/src/SharpMetal/Metal/MTL4CommandQueue.cs index aa9a520..eee891a 100644 --- a/src/SharpMetal/Metal/MTL4CommandQueue.cs +++ b/src/SharpMetal/Metal/MTL4CommandQueue.cs @@ -216,6 +216,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } } diff --git a/src/SharpMetal/Metal/MTL4FunctionDescriptor.cs b/src/SharpMetal/Metal/MTL4FunctionDescriptor.cs index 8da06b0..6c0b0f8 100644 --- a/src/SharpMetal/Metal/MTL4FunctionDescriptor.cs +++ b/src/SharpMetal/Metal/MTL4FunctionDescriptor.cs @@ -21,6 +21,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } } diff --git a/src/SharpMetal/Metal/MTLCommandBuffer.cs b/src/SharpMetal/Metal/MTLCommandBuffer.cs index 17fcfae..0657fe4 100644 --- a/src/SharpMetal/Metal/MTLCommandBuffer.cs +++ b/src/SharpMetal/Metal/MTLCommandBuffer.cs @@ -104,7 +104,7 @@ public MTLAccelerationStructureCommandEncoder AccelerationStructureCommandEncode public MTLAccelerationStructureCommandEncoder AccelerationStructureCommandEncoder() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_accelerationStructureCommandEncoderWithDescriptor)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_accelerationStructureCommandEncoder)); } public MTLBlitCommandEncoder BlitCommandEncoder(MTLBlitPassDescriptor blitPassDescriptor) @@ -114,7 +114,7 @@ public MTLBlitCommandEncoder BlitCommandEncoder(MTLBlitPassDescriptor blitPassDe public MTLBlitCommandEncoder BlitCommandEncoder() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_blitCommandEncoderWithDescriptor)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_blitCommandEncoder)); } public void Commit() @@ -194,7 +194,7 @@ public MTLResourceStateCommandEncoder ResourceStateCommandEncoder(MTLResourceSta public MTLResourceStateCommandEncoder ResourceStateCommandEncoder() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_resourceStateCommandEncoderWithDescriptor)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_resourceStateCommandEncoder)); } public void UseResidencySet(MTLResidencySet residencySet) diff --git a/src/SharpMetal/Metal/MTLDataType.cs b/src/SharpMetal/Metal/MTLDataType.cs index 07f51ca..1a7464d 100644 --- a/src/SharpMetal/Metal/MTLDataType.cs +++ b/src/SharpMetal/Metal/MTLDataType.cs @@ -102,5 +102,4 @@ public enum MTLDataType : ulong BFloat4 = 124, Tensor = 140, } - } diff --git a/src/SharpMetal/Metal/MTLDevice.cs b/src/SharpMetal/Metal/MTLDevice.cs index af25435..e5e3730 100644 --- a/src/SharpMetal/Metal/MTLDevice.cs +++ b/src/SharpMetal/Metal/MTLDevice.cs @@ -487,7 +487,7 @@ public MTL4CommandAllocator NewCommandAllocator(MTL4CommandAllocatorDescriptor d public MTL4CommandAllocator NewCommandAllocator() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newCommandAllocatorWithDescriptorerror)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newCommandAllocator)); } public MTLCommandQueue NewCommandQueue(ulong maxCommandBufferCount) @@ -497,7 +497,7 @@ public MTLCommandQueue NewCommandQueue(ulong maxCommandBufferCount) public MTLCommandQueue NewCommandQueue() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newCommandQueueWithMaxCommandBufferCount)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newCommandQueue)); } public MTLCommandQueue NewCommandQueue(MTLCommandQueueDescriptor descriptor) @@ -542,7 +542,7 @@ public MTLLibrary NewDefaultLibrary(NSBundle bundle, ref NSError error) public MTLLibrary NewDefaultLibrary() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newDefaultLibraryWithBundleerror)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newDefaultLibrary)); } public MTLDepthStencilState NewDepthStencilState(MTLDepthStencilDescriptor descriptor) @@ -627,7 +627,7 @@ public MTLLogState NewLogState(MTLLogStateDescriptor descriptor, ref NSError err public MTL4CommandQueue NewMTL4CommandQueue() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newMTL4CommandQueueWithDescriptorerror)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newMTL4CommandQueue)); } public MTL4CommandQueue NewMTL4CommandQueue(MTL4CommandQueueDescriptor descriptor, ref NSError error) @@ -677,7 +677,7 @@ public MTLSamplerState NewSamplerState(MTLSamplerDescriptor descriptor) public MTLSharedEvent NewSharedEvent() { - return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newSharedEventWithHandle)); + return new(ObjectiveCRuntime.IntPtr_objc_msgSend(NativePtr, sel_newSharedEvent)); } public MTLSharedEvent NewSharedEvent(MTLSharedEventHandle sharedEventHandle) @@ -742,7 +742,7 @@ public ulong SparseTileSizeInBytes(MTLSparsePageSize sparsePageSize) public ulong SparseTileSizeInBytes() { - return ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_sparseTileSizeInBytesForSparsePageSize); + return ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_sparseTileSizeInBytes); } public bool SupportsCounterSampling(MTLCounterSamplingPoint samplingPoint) diff --git a/src/SharpMetal/Metal/MTLFunctionStitching.cs b/src/SharpMetal/Metal/MTLFunctionStitching.cs index c8a924b..c9ca4a3 100644 --- a/src/SharpMetal/Metal/MTLFunctionStitching.cs +++ b/src/SharpMetal/Metal/MTLFunctionStitching.cs @@ -24,6 +24,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } @@ -45,6 +46,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } @@ -206,6 +208,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } diff --git a/src/SharpMetal/Metal/MTLIOCompressor.cs b/src/SharpMetal/Metal/MTLIOCompressor.cs index 986f38d..5ae700b 100644 --- a/src/SharpMetal/Metal/MTLIOCompressor.cs +++ b/src/SharpMetal/Metal/MTLIOCompressor.cs @@ -9,5 +9,4 @@ public enum MTLIOCompressionStatus : long Complete = 0, Error = 1, } - } diff --git a/src/SharpMetal/Metal/MTLLogState.cs b/src/SharpMetal/Metal/MTLLogState.cs index 7442491..8b7fb62 100644 --- a/src/SharpMetal/Metal/MTLLogState.cs +++ b/src/SharpMetal/Metal/MTLLogState.cs @@ -33,6 +33,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } diff --git a/src/SharpMetal/Metal/MTLPixelFormat.cs b/src/SharpMetal/Metal/MTLPixelFormat.cs index 8ccbba9..bf0e110 100644 --- a/src/SharpMetal/Metal/MTLPixelFormat.cs +++ b/src/SharpMetal/Metal/MTLPixelFormat.cs @@ -147,5 +147,4 @@ public enum MTLPixelFormat : ulong X24Stencil8 = 262, Unspecialized = 263, } - } diff --git a/src/SharpMetal/MetalFX/MTL4FXTemporalScaler.cs b/src/SharpMetal/MetalFX/MTL4FXTemporalScaler.cs index c85e810..caa4543 100644 --- a/src/SharpMetal/MetalFX/MTL4FXTemporalScaler.cs +++ b/src/SharpMetal/MetalFX/MTL4FXTemporalScaler.cs @@ -135,7 +135,6 @@ public MTLTexture ReactiveMaskTexture set => ObjectiveCRuntime.objc_msgSend(NativePtr, sel_setReactiveMaskTexture, value); } - public MTLTextureUsage ReactiveTextureUsage => (MTLTextureUsage)ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_reactiveTextureUsage); public bool Reset diff --git a/src/SharpMetal/MetalFX/MTLFXTemporalScaler.cs b/src/SharpMetal/MetalFX/MTLFXTemporalScaler.cs index 4f6366e..1642394 100644 --- a/src/SharpMetal/MetalFX/MTLFXTemporalScaler.cs +++ b/src/SharpMetal/MetalFX/MTLFXTemporalScaler.cs @@ -15,6 +15,7 @@ public void Dispose() { ObjectiveCRuntime.objc_msgSend(NativePtr, sel_release); } + private static readonly Selector sel_release = "release"; } @@ -149,7 +150,6 @@ public MTLTexture ReactiveMaskTexture set => ObjectiveCRuntime.objc_msgSend(NativePtr, sel_setReactiveMaskTexture, value); } - public MTLTextureUsage ReactiveTextureUsage => (MTLTextureUsage)ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_reactiveTextureUsage); public bool Reset @@ -345,7 +345,6 @@ public MTLTexture ReactiveMaskTexture set => ObjectiveCRuntime.objc_msgSend(NativePtr, sel_setReactiveMaskTexture, value); } - public MTLTextureUsage ReactiveTextureUsage => (MTLTextureUsage)ObjectiveCRuntime.ulong_objc_msgSend(NativePtr, sel_reactiveTextureUsage); public bool Reset