diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1108573..0000000 --- a/.gitignore +++ /dev/null @@ -1,168 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates -.vs -.vscode - -# Build results - -[Dd]ebug/ -[Rr]elease/ -x64/ -build/ -[Bb]in/ -[Oo]bj/ - -# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets -!packages/*/build/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.scc - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# Rider is a Visual Studio alternative -.idea/* - -# NCrunch -*.ncrunch* -.*crunch*.local.xml - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.Publish.xml - -# NuGet Packages Directory -## TODO: If you have NuGet Package Restore enabled, uncomment the next line -packages/ - -# Windows Azure Build Output -csx -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.[Pp]ublish.xml -*.pfx -*.publishsettings -packages/* -*.config - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - - -#LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml - -# ========================= -# Windows detritus -# ========================= - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac desktop service store files -.DS_Store - -# VS Launch Settings -launchSettings.json - -# NetCore Publishing Profiles -PublishProfiles/ diff --git a/Ryujinx.CustomTasks/GenerateArrays.cs b/Ryujinx.CustomTasks/GenerateArrays.cs new file mode 100644 index 0000000..871d294 --- /dev/null +++ b/Ryujinx.CustomTasks/GenerateArrays.cs @@ -0,0 +1,243 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Build.Framework; +using System.Collections.Generic; +using System.IO; +using Ryujinx.CustomTasks.SyntaxWalker; +using Ryujinx.CustomTasks.Helper; +using Task = Microsoft.Build.Utilities.Task; + +namespace Ryujinx.CustomTasks +{ + public class GenerateArrays : Task + { + private const string InterfaceFileName = "IArray.g.cs"; + private const string ArraysFileName = "Arrays.g.cs"; + + private readonly List _outputFiles = new List(); + + [Required] + public string ArrayNamespace { get; set; } + + [Required] + public string OutputPath { get; set; } + + [Required] + public ITaskItem[] InputFiles { get; set; } + + [Output] + public string[] OutputFiles { get; set; } + + private static int GetAvailableMaxSize(IReadOnlyList availableSizes, ref int index, int missingSize) + { + if (availableSizes.Count == 0 || missingSize == 1) + { + return 1; + } + + if (availableSizes.Count < index + 1) + { + return 0; + } + + int size = 0; + + while (size == 0 || size > missingSize && availableSizes.Count - index > 0) + { + index++; + size = availableSizes[availableSizes.Count - index]; + } + + return size; + } + + private void AddGeneratedSource(string filePath, string content) + { + File.WriteAllText(filePath, content); + _outputFiles.Add(filePath); + } + + private ICollection GetArraySizes(string itemPath) + { + Log.LogMessage(MessageImportance.Low, $"Searching for StructArray types in: {itemPath}"); + + SyntaxTree tree = CSharpSyntaxTree.ParseText(File.ReadAllText(itemPath), path: itemPath); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + + ArraySizeCollector collector = new ArraySizeCollector(); + + collector.Visit(root); + + foreach (int size in collector.ArraySizes) + { + Log.LogMessage(MessageImportance.Low, $"\tFound array size: {size}"); + } + + return collector.ArraySizes; + } + + private string GenerateInterface() + { + CodeGenerator generator = new CodeGenerator(); + + generator.EnterScope($"namespace {ArrayNamespace}"); + + generator.AppendLine("/// "); + generator.AppendLine("/// Array interface."); + generator.AppendLine("/// "); + generator.AppendLine("/// Element type);"); + generator.EnterScope("public interface IArray where T : unmanaged"); + + generator.AppendLine("/// "); + generator.AppendLine("/// Used to index the array."); + generator.AppendLine("/// "); + generator.AppendLine("/// Element index"); + generator.AppendLine("/// Element at the specified index"); + generator.AppendLine("ref T this[int index] { get; }"); + + generator.AppendLine(); + + generator.AppendLine("/// "); + generator.AppendLine("/// Number of elements on the array."); + generator.AppendLine("/// "); + generator.AppendLine("int Length { get; }"); + + generator.LeaveScope(); + generator.LeaveScope(); + + return generator.ToString(); + } + + private void GenerateArray(CodeGenerator generator, int size, IReadOnlyList availableSizes) + { + generator.EnterScope($"public struct Array{size} : IArray where T : unmanaged"); + + generator.AppendLine("T _e0;"); + + if (size > 1) + { + generator.AppendLine("#pragma warning disable CS0169"); + } + + if (availableSizes.Count == 0) + { + for (int i = 1; i < size; i++) + { + generator.AppendLine($"T _e{i};"); + } + } + else + { + int counter = 1; + int currentSize = 1; + int maxSizeIndex = 0; + int maxSize = 0; + + while (currentSize < size) + { + if (maxSize == 0 || currentSize + maxSize > size) + { + maxSize = GetAvailableMaxSize(availableSizes, ref maxSizeIndex, size - currentSize); + } + + generator.AppendLine(maxSize > 1 + ? counter == 1 + ? $"Array{maxSize} _other;" + : $"Array{maxSize} _other{counter};" + : $"T _e{counter};" + ); + + counter++; + currentSize += maxSize; + } + } + + if (size > 1) + { + generator.AppendLine("#pragma warning restore CS0169"); + } + + generator.AppendLine(); + generator.AppendLine($"public int Length => {size};"); + generator.AppendLine("public ref T this[int index] => ref AsSpan()[index];"); + generator.AppendLine("public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);"); + + generator.LeaveScope(); + generator.AppendLine(); + } + + public override bool Execute() + { + string interfaceFilePath = Path.Combine(OutputPath, InterfaceFileName); + string arraysFilePath = Path.Combine(OutputPath, ArraysFileName); + List arraySizes = new List(); + + File.Delete(interfaceFilePath); + File.Delete(arraysFilePath); + + foreach (var item in InputFiles) + { + string fullPath = item.GetMetadata("FullPath"); + + if (fullPath.EndsWith(".g.cs") || fullPath.Contains("obj\\Debug\\") || fullPath.Contains("obj\\Release\\")) + { + continue; + } + + foreach (int size in GetArraySizes(fullPath)) + { + if (!arraySizes.Contains(size)) + { + arraySizes.Add(size); + } + } + } + + if (arraySizes.Count == 0) + { + Log.LogWarning("No StructArray types found. Skipping code generation."); + + return true; + } + + arraySizes.Sort(); + + AddGeneratedSource(interfaceFilePath, GenerateInterface()); + + CodeGenerator generator = new CodeGenerator(); + List sizesAvailable = new List(); + + generator.AppendLine("using System;"); + generator.AppendLine("using System.Runtime.InteropServices;"); + generator.AppendLine(); + + generator.EnterScope($"namespace {ArrayNamespace}"); + + // Always generate Arrays for 1, 2 and 3 + GenerateArray(generator, 1, sizesAvailable); + sizesAvailable.Add(1); + GenerateArray(generator, 2, sizesAvailable); + sizesAvailable.Add(2); + GenerateArray(generator, 3, sizesAvailable); + sizesAvailable.Add(3); + + foreach (var size in arraySizes) + { + if (!sizesAvailable.Contains(size)) + { + GenerateArray(generator, size, sizesAvailable); + sizesAvailable.Add(size); + } + } + + generator.LeaveScope(); + + AddGeneratedSource(arraysFilePath, generator.ToString()); + + OutputFiles = _outputFiles.ToArray(); + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.CustomTasks/Ryujinx.CustomTasks.csproj b/Ryujinx.CustomTasks/Ryujinx.CustomTasks.csproj index 710b1d8..a3e23a3 100644 --- a/Ryujinx.CustomTasks/Ryujinx.CustomTasks.csproj +++ b/Ryujinx.CustomTasks/Ryujinx.CustomTasks.csproj @@ -2,11 +2,12 @@ netstandard2.0 + 8 true + Ryujinx.CustomTasks Ryujinx.CustomTasks A collection of custom MSBuild tasks. - 1.0.0 https://github.com/Ryujinx/Ryujinx.CustomTasks MIT @@ -19,7 +20,6 @@ NU5100 true - 8 @@ -34,9 +34,10 @@ + - + diff --git a/Ryujinx.CustomTasks/SyntaxWalker/ArraySizeCollector.cs b/Ryujinx.CustomTasks/SyntaxWalker/ArraySizeCollector.cs new file mode 100644 index 0000000..5882bbe --- /dev/null +++ b/Ryujinx.CustomTasks/SyntaxWalker/ArraySizeCollector.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +namespace Ryujinx.CustomTasks.SyntaxWalker +{ + class ArraySizeCollector : CSharpSyntaxWalker + { + public ICollection ArraySizes { get; } = new List(); + + private void AddArrayString(string name) + { + if (!name.StartsWith("Array") || !name.Contains("<")) + { + return; + } + + string rawArrayType = name.Split('<')[0]; + + if (int.TryParse(rawArrayType.Substring(5), out int size) && !ArraySizes.Contains(size)) + { + ArraySizes.Add(size); + } + } + + // https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.genericnamesyntax?view=roslyn-dotnet-4.3.0 + public override void VisitGenericName(GenericNameSyntax node) => AddArrayString(node.ToString()); + // https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.identifiernamesyntax?view=roslyn-dotnet-4.3.0 + public override void VisitIdentifierName(IdentifierNameSyntax node) => AddArrayString(node.ToString()); + } +} \ No newline at end of file