How to use CodeAnalysis.CSharp to compile C# code in game dynamically ?

Godot Version

4.3 stable mono

Question

Hi there, I’m using godot to develop a ship controlling platform. What I want to do is to add a feature that allows users to control the ship by uploading their own C# code. The program will compile the code and run it in the game, the code is designed to invoke some functions in the game and fetch some datas related to their ship, which aim to achieve some specific goal.

To make it possible, I use Microsoft.CodeAnalysis.CSharp to compile the code send by users, which seems like below:

using Godot;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;


public partial class ScriptsUIManager : Node
{
	[Export] OptionButton languageOptionButton;
	[Export] Button openScriptButton;
	[Export] Button compileScriptButton;
	[Export] Button runScriptButton;
	[Export] FileDialog openScriptFileDialog;

	[Export] CodeEdit scriptCodeEdit;

	string scriptPath;

	private MethodInfo scriptMethod;
	private Task scriptTask;

	public override void _Ready()
	{
		openScriptButton.Pressed += OpenScriptButton_Pressed;
		openScriptFileDialog.FileSelected += OpenScriptFileDialog_FileSelected;
		compileScriptButton.Pressed += CompileScriptButton_Pressed;
		runScriptButton.Pressed += RunScriptButton_Pressed;
	}

	private MetadataReference[] InitReferences()
	{
		var currentAssembly = Assembly.GetExecutingAssembly();
		var referencedAssemblies = currentAssembly.GetReferencedAssemblies();
		var references = new List<MetadataReference>();
		foreach (var assemblyName in referencedAssemblies)
		{
			try {
				var assembly = Assembly.Load(assemblyName);
				references.Add(MetadataReference.CreateFromFile(assembly.Location));
				GD.Print("INFO: Loaded assembly: ", assembly.FullName);
			}
			catch {
				GD.PrintErr("ERROR: Failed to load assembly: ", assemblyName.FullName);
			}
		}
		try {
			references.Add(MetadataReference.CreateFromFile(currentAssembly.Location));
		}
		catch {
			GD.PrintErr("ERROR: Failed to load current assembly: ", currentAssembly.FullName);
		}
		return references.ToArray();
	}

	public override void _Process(double delta)
	{
	}

	void CompileScriptButton_Pressed()
	{
		scriptMethod = CompileScript(scriptCodeEdit.Text);
		if (scriptMethod != null)
		{
			GD.Print("Script compiled successfully.");
		}
		else
		{
			GD.PrintErr("FATAL: Script compilation failed.");
		}
	}

	void RunScriptButton_Pressed()
	{
		if (scriptMethod != null && scriptTask == null)
		{
			scriptTask = new Task(() => ExecuteTask(scriptMethod));
			scriptTask.Start();
		}
		else if (scriptTask != null)
		{
			GD.Print("INFO: Task is already running.");
		}
		else if (scriptMethod == null)
		{
			GD.PrintErr("ERROR: Script is not compiled.");
		}
	}

	void OpenScriptFileDialog_FileSelected(string path)
	{
		scriptPath = path;

		// 读取脚本文件内容并显示到CodeEdit控件中
		scriptCodeEdit.Text = File.ReadAllText(scriptPath);
	}

	private MethodInfo CompileScript(string codeContent)
	{
		GD.Print("Compiling script file at ", scriptPath);
		if (languageOptionButton.Selected == 0)
		{
			// Python
			// 之后再完善
			return null;
		}
		else
		{
			var code = codeContent;
			var syntaxTree = CSharpSyntaxTree.ParseText(code);
			var assemblyName = Path.GetRandomFileName();
			var references = InitReferences();
			var compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, null,
				new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
			var ms = new MemoryStream();
			var result = compilation.Emit(ms);
			GD.Print("Compilation result: ", result.Success);
			if (!result.Success)
			{
				var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
				foreach (var diagnostic in failures)
				{
					GD.PrintErr(diagnostic.Id, ": ", diagnostic.GetMessage());
				}
				return null;
			}
			else
			{
				ms.Seek(0, SeekOrigin.Begin);
				var assembly = Assembly.Load(ms.ToArray());
				var type = assembly.GetType("ShipOrder");
				var method = type.GetMethod("Execute");
				return method;
			}
		}
	}

	void OpenScriptButton_Pressed()
	{
		if (languageOptionButton.Selected == 0)
		{
			openScriptFileDialog.Filters = new string[] { "*.py;Python Script" };
		}
		else if (languageOptionButton.Selected == 1)
		{
			openScriptFileDialog.Filters = new string[] { "*.cs;C# Script" };
		}

		openScriptFileDialog.PopupCentered();
	}

	private void ExecuteTask(MethodInfo method)
	{
		if (method != null)
		{
			var instance = Activator.CreateInstance(method.DeclaringType);
			method.Invoke(instance, null);
		}
		else
		{
			GD.PrintErr("FATAL: Compile failed.");
		}
	}

}

However, when it comes to run, some MetadataReference was not loaded to the references, the errors comes like these:

 ERROR: Failed to load assembly: Microsoft.CodeAnalysis, Version=4.13.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
  ERROR: Failed to load assembly: Microsoft.CodeAnalysis.CSharp, Version=4.13.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
  ERROR: Failed to load assembly: System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  ERROR: Failed to load assembly: RJCP.SerialPortStream, Version=2.4.2.0, Culture=neutral, PublicKeyToken=5f5e7b70c6a74deb

Also this is my test code:

using System;
using Godot;
using System.Threading;

public class ShipOrder
{
    public void Execute()
    {
        // Debug AUVData Class
        GD.Print("ShipOrder.Execute() called");
        Thread.Sleep(500);
        // Fetch AUVData
        GD.Print("ShipOrder.Execute() fetching AUVData");
        GD.Print("Current Temperature: " + AUVData.AHT10Temp);
        Thread.Sleep(500);
        GD.Print("ShipOrder.Execute() finished");
    }
}

These assemblies failed to be loaded due to unknown reasons. However, when I did the same on MonoGame, the MetaDataReferences were fine loaded and it runs successfully. I’m not sure it’s related to Godot’s speacial features?

Anyway, Could anyone help me on this problem? Thanks a lot.