Search code examples
c#.netunity-game-engineroslyn

Is there a way to use Roslyn in Unity?


So, I have this project we will call X. X is a console application developed in .NET Core 3.1 and I want to give this application an interface with Unity. A core part of this application is the dynamic generation of code based on some text. In X I use Roslyn to do so. I have installed through NuGet the package Microsoft.CodeAnlysis.CSharp.Scripting and all the packages it carries over and it works wonderfully. "Converting" the project in Unity (set in the Player Settings to compile for .NET Framework) I did what I tought to be the most fool-proof thing. I copied and pasted all the scripts from X's folder into the Unity Asset folder and started re-writing what needed to be re-written. Everything worked wonderfully until I got to the code generation part. After some rumble I found out about NuGetForUnity, which I used to get the same NuGet packages I used in X on the latest version. With everything installed I tryed running my application. And I got this error:

> System.NotImplementedException: The method or operation is not implemented.
  at System.Runtime.Loader.AssemblyLoadContext.LoadFromStream (System.IO.Stream assembly, System.IO.Stream assemblySymbols) [0x00000] in <b903c2b7b82245b2878c8afa0d6b7576>:0 
  at Microsoft.CodeAnalysis.Scripting.Hosting.CoreAssemblyLoaderImpl.LoadFromStream (System.IO.Stream peStream, System.IO.Stream pdbStream) [0x00000] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.LoadAssemblyFromStream (System.IO.Stream peStream, System.IO.Stream pdbStream) [0x00000] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.Build[T] (Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, System.Boolean emitDebugInformation, System.Threading.CancellationToken cancellationToken) [0x000c6] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T] (Microsoft.CodeAnalysis.Scripting.ScriptCompiler compiler, Microsoft.CodeAnalysis.Compilation compilation, System.Boolean emitDebugInformation, System.Threading.CancellationToken cancellationToken) [0x00026] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at Microsoft.CodeAnalysis.Scripting.Script`1[T].GetExecutor (System.Threading.CancellationToken cancellationToken) [0x0002b] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at Microsoft.CodeAnalysis.Scripting.Script`1[T].CreateDelegate (System.Threading.CancellationToken cancellationToken) [0x0000d] in <7da58dd2a6fc48459bcdbf30dedfadbc>:0 
  at CodeGenerator.GenericExternalBuilder[T] (System.Collections.Concurrent.ConcurrentDictionary`2[TKey,TValue] coll) [0x008c2] in C:\Users\User\Documents\uXCOM\Assets\Scripts\CodeGenerator.cs:198 
  at (wrapper dynamic-method) System.Object.CallSite.Target(System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,System.Type,object)
  at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1] (System.Runtime.CompilerServices.CallSite site, T0 arg0, T1 arg1) [0x00113] in <dab7f68612224ba3ae40f651d44f9d4c>:0 
  at CodeGenerator.InitializeAllExternals () [0x00026] in C:\Users\User\Documents\uXCOM\Assets\Scripts\CodeGenerator.cs:52 
UnityEngine.Debug:LogError (object)
XCOM.XLogger:FixedUpdate () (at Assets/Scripts/XLogger.cs:100)

What really bugs me is that the main exception is a NotImplementedException. My first tought was that the latest version was not a stable one, so I downgraded trough NuGetForUnity, but another exception was thrown (I don't have it on me, but I will provide it if useful). After that I tryed everything I could come up with: downgrading to the version I used in X, installing the same packages through a .unitypackage, different versions of the package, switching the compiler version to .NET Standard 2.1 in the Player Settings, but nothing works. I'll leave a piece of code that throws the same exception so that is reproducible in your project:

private void Test()
{
    string code = "{ int a = 1; int b = 3; return a + b; }";
    ScriptRunner<int> script = CSharpScript.Create<int>(code).CreateDelegate();
    Debug.Log(script.Invoke());
}

This throws the same type of exception, just a little different:

> NotImplementedException: The method or operation is not implemented.
System.Runtime.Loader.AssemblyLoadContext.LoadFromStream (System.IO.Stream assembly, System.IO.Stream assemblySymbols) (at <b903c2b7b82245b2878c8afa0d6b7576>:0)
Microsoft.CodeAnalysis.Scripting.Hosting.CoreAssemblyLoaderImpl.LoadFromStream (System.IO.Stream peStream, System.IO.Stream pdbStream) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.LoadAssemblyFromStream (System.IO.Stream peStream, System.IO.Stream pdbStream) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
Microsoft.CodeAnalysis.Scripting.ScriptBuilder.Build[T] (Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.DiagnosticBag diagnostics, System.Boolean emitDebugInformation, System.Threading.CancellationToken cancellationToken) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T] (Microsoft.CodeAnalysis.Scripting.ScriptCompiler compiler, Microsoft.CodeAnalysis.Compilation compilation, System.Boolean emitDebugInformation, System.Threading.CancellationToken cancellationToken) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
Microsoft.CodeAnalysis.Scripting.Script`1[T].GetExecutor (System.Threading.CancellationToken cancellationToken) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
Microsoft.CodeAnalysis.Scripting.Script`1[T].CreateDelegate (System.Threading.CancellationToken cancellationToken) (at <7da58dd2a6fc48459bcdbf30dedfadbc>:0)
MainScript.Test () (at Assets/Scripts/MainScript.cs:33)
MainScript.Start () (at Assets/Scripts/MainScript.cs:22)

What i expect it to do is print on the editor console 4.


Solution

  • I did this in August 2022, maybe it's easier now. But these helped me get it working:

    How to reference another DLL in Roslyn dynamically-compiled code

    How do you add references to types compiled in a memory stream using Roslyn?

    C# reflection - load assembly and invoke a method if it exists

    It will likely take you a few weeks to get it all working.

    If you are targeting IL2CPP, you'll want to check out https://github.com/scottyboy805/dotnow-interpreter/wiki/Roslyn-C%23-Integration

    Alternately, there is Roslyn C# in the Unity Asset Store (might be on sale in 24 hours) -- leveraging it will cut the time in half. And you'll likely want to review https://forum.unity.com/threads/released-roslyn-c-runtime-c-compiler.651505/page-5#post-8102249

    There are a ton of corner cases (like "do you want to leverage Newtonsoft.Json" in your scripts) that will add extra challenges depending on your needs.