Skip to content

Commit 7b786f0

Browse files
committed
- Assembly loading work.
1 parent ed6604c commit 7b786f0

File tree

2 files changed

+131
-24
lines changed

2 files changed

+131
-24
lines changed

Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Immutable;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Dynamic;
7+
using System.IO;
78
using System.Linq;
89
using System.Reflection;
910
using System.Runtime.CompilerServices;
@@ -12,7 +13,11 @@
1213
using Barotrauma.LuaCs;
1314
using Microsoft.CodeAnalysis;
1415
using Basic.Reference.Assemblies;
16+
using FluentResults;
17+
using FluentResults.LuaCs;
18+
using LightInject;
1519
using Microsoft.CodeAnalysis.CSharp;
20+
using Path = Barotrauma.IO.Path;
1621

1722
[assembly: InternalsVisibleTo(IAssemblyLoaderService.InternalsAwareAssemblyName)]
1823

@@ -37,7 +42,9 @@ public bool IsDisposed
3742
/// </summary>
3843
private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion);
3944
private readonly ConcurrentDictionary<string, AssemblyDependencyResolver> _dependencyResolvers = new();
40-
private readonly ConcurrentDictionary<string, Assembly> _memoryCompiledAssemblies = new();
45+
private readonly ConcurrentDictionary<string, (Assembly, byte[])> _memoryCompiledAssemblies = new();
46+
private readonly ConcurrentDictionary<Assembly, ImmutableArray<MetadataReference>> _loadedAssemblyReferences = new();
47+
private readonly ConcurrentDictionary<Assembly, ImmutableArray<Type>> _typeCache = new();
4148

4249
private ThreadLocal<bool> _isResolving = new(static()=>false); // cyclic resolution exit
4350

@@ -60,16 +67,90 @@ public AssemblyLoader(IPluginManagementService pluginManagementService,
6067
base.Unloading += OnUnload;
6168
}
6269
}
63-
70+
71+
public FluentResults.Result AddDependencyPaths(ImmutableArray<string> paths)
72+
{
73+
if (paths.Length == 0)
74+
return FluentResults.Result.Ok();
75+
var res = new FluentResults.Result();
76+
foreach (var path in paths)
77+
{
78+
try
79+
{
80+
var p = Path.GetFullPath(path.CleanUpPath());
81+
_dependencyResolvers[p] = new AssemblyDependencyResolver(p);
82+
}
83+
catch (Exception ex)
84+
{
85+
return res.WithError(new ExceptionalError(ex));
86+
}
87+
}
88+
return FluentResults.Result.Ok();
89+
}
90+
6491
public FluentResults.Result<Assembly> CompileScriptAssembly(
65-
[NotNull] string friendlyAssemblyName,
92+
[NotNull] string assemblyName,
6693
bool compileWithInternalAccess,
67-
string assemblyInternalName,
68-
[NotNull] IEnumerable<SyntaxTree> syntaxTrees,
94+
ImmutableArray<SyntaxTree> syntaxTrees,
6995
ImmutableArray<MetadataReference> metadataReferences,
70-
[NotNull] CSharpCompilationOptions compilationOptions)
96+
CSharpCompilationOptions compilationOptions = null)
7197
{
72-
throw new NotImplementedException();
98+
if (assemblyName.IsNullOrWhiteSpace())
99+
{
100+
return new FluentResults.Result<Assembly>().WithError(new Error($"The name provided is null!")
101+
.WithMetadata(MetadataType.ExceptionObject, this)
102+
.WithMetadata(MetadataType.RootObject, syntaxTrees));
103+
}
104+
105+
if (_memoryCompiledAssemblies.ContainsKey(assemblyName))
106+
{
107+
return new FluentResults.Result<Assembly>().WithError(new Error($"The name provided is already assigned to an assembly!")
108+
.WithMetadata(MetadataType.ExceptionObject, this)
109+
.WithMetadata(MetadataType.RootObject, syntaxTrees));
110+
}
111+
112+
var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName;
113+
114+
compilationOptions ??= new CSharpCompilationOptions(
115+
outputKind: OutputKind.DynamicallyLinkedLibrary,
116+
optimizationLevel: OptimizationLevel.Release,
117+
concurrentBuild: true,
118+
reportSuppressedDiagnostics: true,
119+
allowUnsafe: true);
120+
121+
typeof(CSharpCompilationOptions)
122+
.GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic)
123+
?.SetValue(compilationOptions, (uint)1 << 22);
124+
125+
using var asmMemoryStream = new MemoryStream();
126+
var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream);
127+
if (!result.Success)
128+
{
129+
var res = new FluentResults.Result().WithError(
130+
new Error($"Compilation failed for assembly {assemblyName}!"));
131+
var failuresDiag = result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error);
132+
foreach (var diag in failuresDiag)
133+
{
134+
res = res.WithError(new Error(diag.GetMessage())
135+
.WithMetadata(MetadataType.ExceptionObject, this)
136+
.WithMetadata(MetadataType.ExceptionDetails, diag.Descriptor.Description));
137+
}
138+
return res;
139+
}
140+
141+
asmMemoryStream.Seek(0, SeekOrigin.Begin);
142+
try
143+
{
144+
var assembly = LoadFromStream(asmMemoryStream);
145+
var assemblyImage = asmMemoryStream.ToArray();
146+
_memoryCompiledAssemblies[assemblyName] = (assembly, assemblyImage);
147+
_typeCache[assembly] = assembly.GetSafeTypes().ToImmutableArray();
148+
return new FluentResults.Result<Assembly>().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(assembly);
149+
}
150+
catch (Exception ex)
151+
{
152+
return new FluentResults.Result().WithError(new ExceptionalError(ex));
153+
}
73154
}
74155

75156
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
@@ -81,12 +162,40 @@ public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePa
81162

82163
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName)
83164
{
84-
throw new NotImplementedException();
165+
if (assemblyName.IsNullOrWhiteSpace())
166+
{
167+
return FluentResults.Result.Fail(new Error($"Assembly name is null")
168+
.WithMetadata(MetadataType.ExceptionObject, this));
169+
}
170+
171+
if (_memoryCompiledAssemblies.TryGetValue(assemblyName, out var assembly))
172+
{
173+
return new FluentResults.Result<Assembly>().WithSuccess(new Success($"Assembly found")).WithValue(assembly.Item1);
174+
}
175+
176+
foreach (var assembly1 in Assemblies)
177+
{
178+
if (assembly1.GetName().Name == assemblyName)
179+
{
180+
return new FluentResults.Result<Assembly>().WithSuccess(new Success($"Assembly found")).WithValue(assembly1);
181+
}
182+
}
183+
184+
return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!"));
85185
}
86186

87-
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies(bool includeReferenceExplicitOnly = false)
187+
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies()
88188
{
89-
throw new NotImplementedException();
189+
try
190+
{
191+
return new FluentResults.Result<ImmutableArray<Type>>().WithValue([
192+
.._typeCache.SelectMany(kvp => kvp.Value)
193+
]);
194+
}
195+
catch (Exception e)
196+
{
197+
return FluentResults.Result.Fail(new ExceptionalError(e));
198+
}
90199
}
91200

92201
#endregion

Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ delegate IAssemblyLoaderService AssemblyLoaderDelegate(
3434
/// intended for execution.
3535
/// </summary>
3636
bool IsReferenceOnlyMode { get; }
37-
/// <summary>
38-
/// Indicates that Unload was called on this context and that all strong references to it should be discarded.
39-
/// </summary>
40-
public bool IsDisposed { get; }
41-
4237
/// <summary>
4338
/// Runtime value of constant <see cref="InternalsAwareAssemblyName"/> for extensibility use.
4439
/// </summary>
@@ -48,18 +43,24 @@ delegate IAssemblyLoaderService AssemblyLoaderDelegate(
4843
/// </summary>
4944
public const string InternalsAwareAssemblyName = "InternalsAwareAssembly";
5045

46+
/// <summary>
47+
/// Add additional locations for dependency resolution to use.
48+
/// </summary>
49+
/// <param name="paths"></param>
50+
/// <returns></returns>
51+
public FluentResults.Result AddDependencyPaths(ImmutableArray<string> paths);
52+
5153
/// <summary>
5254
/// Compiles the supplied syntaxtrees and options into an in-memory assembly image.
5355
/// Builds metadata from loaded assemblies, only supply your own if you have in-memory images not managed by the
5456
/// AssemblyManager class.
5557
/// </summary>
56-
/// <param name="friendlyAssemblyName"><c>[NotNull]</c>Name reference of the assembly.
58+
/// <param name="assemblyName"><c>[NotNull]</c>Name reference of the assembly.
5759
/// <para><b>[IMPORTANT]</b> This is used to reference this assembly as the true name will be forced if
5860
/// publicized assemblies are not used (InternalsVisibleTo Attrib).</para>
5961
/// Must be supplied for in-memory assemblies.
6062
/// <para>Must be unique to all other assemblies explicitly loaded using this context.</para></param>
6163
/// <param name="compileWithInternalAccess">Forces the assembly name to <see cref="InternalsAccessAssemblyName"/> and grants access to <c>internal</c>.</param>
62-
/// <param name="assemblyInternalName">The real assembly name used in compilation.
6364
/// <para><b>[IMPORTANT]</b>Cannot be null or empty if <see cref="compileWithInternalAccess"/> is false.</para></param>
6465
/// <param name="syntaxTrees"><c>[NotNull]</c>Syntax trees to compile into the assembly.</param>
6566
/// <param name="metadataReferences">All <c>MetadataReference<c/>s to be used for compilation.
@@ -68,12 +69,11 @@ delegate IAssemblyLoaderService AssemblyLoaderDelegate(
6869
/// <param name="compilationOptions"><c>[NotNull]</c>CSharp compilation options. This method automatically adds the 'IgnoreAccessChecks' property for compilation.</param>
6970
/// <returns>Success state of the operation.</returns>
7071
public FluentResults.Result<Assembly> CompileScriptAssembly(
71-
[NotNull] string friendlyAssemblyName,
72+
[NotNull] string assemblyName,
7273
bool compileWithInternalAccess,
73-
string assemblyInternalName,
74-
[NotNull] IEnumerable<SyntaxTree> syntaxTrees,
74+
ImmutableArray<SyntaxTree> syntaxTrees,
7575
ImmutableArray<MetadataReference> metadataReferences,
76-
[NotNull] CSharpCompilationOptions compilationOptions);
76+
CSharpCompilationOptions compilationOptions = null);
7777

7878
/// <summary>
7979
/// Loads the assembly from the provided location and registers all new paths provided with dependency resolution.
@@ -94,10 +94,8 @@ public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePa
9494
/// <summary>
9595
/// Gets the list of <c>Type</c>s from loaded assemblies.
9696
/// </summary>
97-
/// <param name="includeReferenceExplicitOnly">Only include assemblies that were explicitly loaded and not automatically
98-
/// loaded dependencies.</param>
9997
/// <returns></returns>
100-
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies(bool includeReferenceExplicitOnly = false);
98+
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies();
10199

102100
/// <summary>
103101
/// List of loaded assemblies.

0 commit comments

Comments
 (0)