mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
185 lines
5.4 KiB
C#
185 lines
5.4 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.Text;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Sandbox.Generator;
|
|
|
|
/// <summary>
|
|
/// A sync object to allow workers to exchange data abstractly. Should lock Worker.Sync when accessing.
|
|
/// </summary>
|
|
internal class Sync
|
|
{
|
|
HashSet<string> tags = new HashSet<string>();
|
|
|
|
/// <summary>
|
|
/// Add a tag. Return true if added, return false if not. This is used to let other
|
|
/// workers know that we're adding some code, so they don't also try to add the same code.
|
|
/// ie - adding attributes to classes can fuck up if they have a partial spread over multiple
|
|
/// files, because we'll try to add the attribute for each file.
|
|
/// </summary>
|
|
public bool AddTag( string tag )
|
|
{
|
|
if ( tags.Contains( tag ) )
|
|
return false;
|
|
|
|
tags.Add( tag );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public class Processor
|
|
{
|
|
public static Func<string, string> DefaultPackageAssetResolver;
|
|
|
|
/// <summary>
|
|
/// Generated file that will get stuff like global usings and assembly attributes.
|
|
/// </summary>
|
|
public const string CompilerExtraPath = "/.obj/__compiler_extra.cs";
|
|
|
|
public string AddonName { get; set; } = "AddonName";
|
|
|
|
public Dictionary<string, string> AddonFileMap { get; set; } = new Dictionary<string, string>( StringComparer.OrdinalIgnoreCase );
|
|
public CSharpCompilation Compilation { get; set; }
|
|
public CSharpCompilation LastSuccessfulCompilation { get; set; }
|
|
public ImmutableArray<SyntaxTree> BeforeILHotloadProcessingTrees { get; set; }
|
|
public Exception Exception { get; internal set; }
|
|
public SourceProductionContext? Context { get; set; }
|
|
|
|
public List<Diagnostic> Diagnostics = new List<Diagnostic>();
|
|
public bool ILHotloadSupported { get; set; }
|
|
|
|
/// <summary>
|
|
/// A function that will take a package name and return the path to the asset
|
|
/// </summary>
|
|
public Func<string, string> PackageAssetResolver { get; set; } = DefaultPackageAssetResolver;
|
|
|
|
public void AddTrees( IEnumerable<SyntaxTree> trees )
|
|
{
|
|
if ( Context == null )
|
|
{
|
|
Compilation = Compilation.AddSyntaxTrees( trees.ToArray() );
|
|
}
|
|
|
|
foreach ( var tree in trees )
|
|
{
|
|
Context?.AddSource( tree.FilePath, SourceText.From( tree.ToString(), Encoding.UTF8 ) );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can be called manually
|
|
/// </summary>
|
|
public void Run( CSharpCompilation compilation,
|
|
CSharpCompilation lastSuccessfulCompilation = null,
|
|
ImmutableArray<SyntaxTree> lastBeforeIlHotloadProcessingTrees = default )
|
|
{
|
|
Compilation = compilation;
|
|
LastSuccessfulCompilation = lastSuccessfulCompilation;
|
|
BeforeILHotloadProcessingTrees = lastBeforeIlHotloadProcessingTrees;
|
|
|
|
try
|
|
{
|
|
if ( Compilation.SyntaxTrees.Count() > 0 )
|
|
{
|
|
var sync = new Sync();
|
|
ConcurrentBag<Worker> workers = new();
|
|
ConcurrentBag<Exception> exceptions = new();
|
|
|
|
//
|
|
// Run all the processers in tasks so it's super fast
|
|
//
|
|
var result = System.Threading.Tasks.Parallel.ForEach( Compilation.SyntaxTrees, tree =>
|
|
{
|
|
try
|
|
{
|
|
var w = Worker.Process( Compilation, tree, AddonFileMap, Context == null, sync, this );
|
|
workers.Add( w );
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
exceptions.Add( e );
|
|
}
|
|
|
|
} );
|
|
|
|
// any exceptions?
|
|
if ( exceptions.Any() )
|
|
{
|
|
throw exceptions.First();
|
|
}
|
|
|
|
//
|
|
// Sort the workers, so added code is in a deterministic order
|
|
//
|
|
var sortedWorkers = workers.OrderBy( x => x.TreeInput.FilePath ).ToArray();
|
|
|
|
//
|
|
// Process the results
|
|
//
|
|
foreach ( var worker in sortedWorkers )
|
|
{
|
|
// Don't need to do this if just using Source Generator
|
|
if ( Context == null )
|
|
{
|
|
Compilation = Compilation.ReplaceSyntaxTree( worker.TreeInput, CSharpSyntaxTree.Create( worker.OutputNode, worker.TreeInput.Options as CSharpParseOptions, worker.TreeInput.FilePath, worker.TreeInput.Encoding ) );
|
|
|
|
// Copy each worker's diagnostics so they're accessible outside of Sandbox.Generator
|
|
Diagnostics.AddRange( worker.Diagnostics );
|
|
}
|
|
else
|
|
{
|
|
foreach ( var diag in worker.Diagnostics )
|
|
{
|
|
Context.Value.ReportDiagnostic( diag );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If trees were added, add them to the source
|
|
//
|
|
var trees = sortedWorkers.SelectMany( x => x.AddedTrees ).ToList();
|
|
|
|
//
|
|
// If we added loose code
|
|
//
|
|
var extraCode = string.Join( "\n", sortedWorkers.Where( x => !string.IsNullOrEmpty( x.AddedCode ) ).Select( x => x.AddedCode ) );
|
|
if ( !string.IsNullOrWhiteSpace( extraCode ) )
|
|
{
|
|
trees.Add( CSharpSyntaxTree.ParseText( extraCode, path: "_gen__AddedCode.cs", encoding: System.Text.Encoding.UTF8 ) );
|
|
}
|
|
|
|
//
|
|
// Write all the new trees
|
|
//
|
|
if ( trees.Count() > 0 )
|
|
{
|
|
AddTrees( trees );
|
|
}
|
|
}
|
|
|
|
var beforeIlHotloadTrees = Compilation.SyntaxTrees;
|
|
|
|
if ( Context == null )
|
|
{
|
|
ILHotloadProcessor.Process( this );
|
|
}
|
|
|
|
BeforeILHotloadProcessingTrees = beforeIlHotloadTrees;
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
Exception = e;
|
|
var desc = new DiagnosticDescriptor( "SB5000", "Generator Crash", $"Code Generator Crashed {Exception.StackTrace.Trim( '\n', '\r', ' ', '\t' ).Replace( "\n", "" ).Replace( "\r", "" )} - {e.Message}", "generator", DiagnosticSeverity.Error, true );
|
|
|
|
Context?.ReportDiagnostic( Diagnostic.Create( desc, null ) );
|
|
}
|
|
}
|
|
}
|