C# Interpreter Console for Unity 3D

CSI is a simple C# interpreter that was originally developed by Steve Donovan and it works similar to the more recently developed C# interactive shell of Mono, named CsharpRepl. For a while I’ve been making some improvements to CSI and recently developed a plug-in/script version of it for Unity, the 3D game-development environment, which is compatible with the older version of Mono that Unity makes use of. The plugin allows one to execute C# code in an interactive console while the game is running, which can assist with debugging and be used to explore the runtime environment. The software is known to work on Unity 2.6.1 in both the Editor application and Standalone Player on Windows, but it might also work on the Mac OS. It also works on Unity 3.0.0 and 3.1.0 for PC (as of CSI version 0.8.24.3), as long as your project’s “API Compatibility Level” is set to “.NET 2.0” (not the default “.NET 2.0 Subset”).

Download and Install the Plugin

Files of CSI Plugin shown in Project window of Unity EditorThe CSI plug-in is available as a free download and released as open-source software under the MIT License. It is distributed in multiple formats: a ZIP file, a Unity Package and a Git repository, all containing the four files that need to be installed somewhere under the Assets directory-structure of your Unity project. If you prefer the packaged version, import the files into your project through the Unity Editor’s “Assets | Import Package...” menu. Alternatively, you can decompress the ZIP file’s content into an assets subdirectory, for example “Assets\Plugins\CSI” underneath you project’s root folder. After installation, you should see something in your Project window that is similar to the image shown; it represents the four files of the plug-in:

  • CSharpInterpreter.cs
  • CSharpInterpreter_Include.txt
  • interpreter.cs
  • prepro.cs

The first file, CSharpInterpreter.cs, is the primary script that implements the hosting environment for the C# Interpreter, which is a Component that can be attached to a GameObject in Unity 3D. For example, to get the interpreter GUI to show up at runtime, use the “GameObject | Create Empty” menu item in the Unity Editor, which would result in a new GameObject being shown in your project’s Hierarchy window; then drag-and-drop the CSharpInterpreter script from the Project window onto your new GameObject to attach the script to the object. You can now “Play” the scene and should see the interactive console’s user interface (discussed in more detail in the following section).

By default, the CSharpInterpreter_Include.txt file gets included during startup to configure the interpreter environment. It specifies additional namespaces and assembly references that must be included, as well as macros (such as print) that can save you some typing. You can edit this text file to tweak the interpreter environment to your liking – each line in this file is effectively processed as if it were provided manually as input to the interpreter, one step at a time.

The remaining two files, interpreter.cs and prepro.cs, contain the underlying CSI interpreter and preprocessor code that provide support for the CSI game component. You would rarely need to work with them directly.

Runtime User Interface

Once you have the CSI plug-in for Unity installed and running, its runtime user-interface will display as overlay controls shown on top of your game scene in the background – for example, see the screenshot below.

Screenshot of CSI Simple C# Interpreter on Unity 3D

At the top is the interpreter’s output window, which is a read-only text area where additional output gets added at the bottom while the older output scrolls up. You can navigate to this area and copy some of the output text if you want.

Below the output window, on the left side, there’s a large toggle button that can be used to show or hide the rest of the GUI. You can either click on the button or use the default Alt+F2 shortcut to toggle the display state. Underneath the main toggle button you’ll find a couple of small buttons: the one on the left is a regular button that can be used to clear the output window when it gets too cluttered, the remaining buttons are all toggle buttons. Hover over the buttons with the mouse pointer to see tooltips that are shown in the bottom window to describe their purpose. The small toggle button at the top would show or hide the output window, and the one at the bottom does the same but for the tooltip bar. When running in the Editor, a third toggle button appears between these two and it controls whether the resulting objects that are returned by the interpreted C# code would be selected automatically in the Editor, for example to allow for additional editing or viewing through the Editor’s regular inspector windows.

The remaining text area is the input window where you enter the code to be interpreted. While the input window has focus, a number of keyboard keys are interpreted in special ways. For example, hitting the Enter key would send all of the input text to the C# interpreter for processing and typically result in code execution. To enter line-breaks in the input window, you can use Shift+Enter, but you can often also feed multi-line input to the interpreter in multiple steps using the Enter key by itself between steps; doing so, the prompt displayed on the large toggle-button would change from “>>>” to “...” that indicates the continuation state of the interpreter, as illustrated below.

>>>  for (int i = 1; i <= 3; i++) {
... print(UnityEngine.Random.value); }
0.4331492
0.1738945
0.5771966

Hitting the Enter key without any input text would reevaluate the previous input. You can use the up and down arrow-keys to navigate to previous input and then copy or edit it. To move the cursor between lines in a multi-line input, you need to hold down either Ctrl or Caps-Lock while pressing the arrow keys – it can take a while getting used to it, but even if you slip up and accidently navigate your history instead of the input lines, you can simply navigate the history in the opposite direction without losing earlier changes to the input text.

The input box also implements a rudimentary auto-completion or help system that gets triggered through the Alt+F1 shortcut; however, the context is typically the result of the previous step’s code (and not so much the current input text). What this means is that, to use this functionality, you’ll often end up executing an expression in incremental steps, each time navigating to the previous step’s input and appending more code to it while triggering the system. Even so, this feature can help a lot once you know how to use it. For example, as illustrated by the next session log, you can enter “DateTime.Now” (without the quotes) as input and press Enter to get the current time and make that result the current context for help; if you now press Alt+F1 you’ll get a long list of all the DateTime methods and properties available for this type of result (such as AddDays and Ticks). If you then enter “DateTime.Now.ToF” (most of which can be retrieved with the up arrow) and then press Alt+F1, the input-line would auto-complete to “DateTime.Now.ToFileTime” and show additional help: only two of the members will now show up as options (namely ToFileTimeUtc and ToFileTime), since the last portion of the input, “ToF”, is applied as a regular-expression filter to the context. The auto-completed text is the common portion of the filtered member names, and, because the auto-completed text also matches one of the member names exactly, the method prototype for ToFileTime is displayed as additional help in the output window.

>>>  DateTime.Now
(DateTime) 13/03/2010 2:28:19 PM
-------------------------------
.ctor AddDays AddHours AddMilliseconds AddMinutes AddMonths
AddSeconds AddTicks AddYears Add CompareTo Date DayOfWeek DayOfYear
Day Equals GetDateTimeFormats GetHashCode GetTypeCode GetType Hour
IsDaylightSavingTime Kind Millisecond Minute Month Second Subtract
Ticks TimeOfDay ToBinary ToFileTimeUtc ToFileTime ToLocalTime
ToLongDateString ToLongTimeString ToOADate ToShortDateString
ToShortTimeString ToString ToUniversalTime Year
-------------------------------
ToFileTimeUtc ToFileTime
-------------------------------
Int64 ToFileTime()

Furthermore, if you wanted help on the static members of the DateTime class, type “typeof(DateTime)” as input text followed by Enter to make the type itself the current help context, then press Alt+F1 to display static member names such as IsLeapYear and UtcNow, as shown below.

>>>  typeof(DateTime)
(Type) System.DateTime
-------------------------------
Compare DaysInMonth Equals FromBinary FromFileTimeUtc FromFileTime
FromOADate IsLeapYear MaxValue MinValue Now op_Addition
op_Equality op_GreaterThanOrEqual op_GreaterThan op_Inequality
op_LessThanOrEqual op_LessThan op_Subtraction ParseExact Parse
ReferenceEquals SpecifyKind Today TryParseExact TryParse UtcNow

Although the input text would primarily consist of C# code, the interpreter does have a preprocessor that extends the interpreter’s syntax. Refer to the original CSI documentation to get a feel for some of the possibilities using the preprocessor macros (for example, you can also display data-type metadata through a macro by typing “/M DateTime”). More importantly, the original documentation also explains how the dollar-sign session variables are implemented.

You use dollar-sign session variables to store and retrieve intermediate results in the interpreter, since the regular C# way of manipulating intermediate results as local variables does now work as you would expect. What it effectively boils down to is that, as an alternative, you have a global lookup table of variables that can be shared between consecutive interpreted steps. For example, instead of writing “int x=5” and “int y=x+3” as real C# variables, you would need to write “$x=5” and “$y=$x+3” where x and y are the names of the variables stored in the lookup table, as illustrated by the following CSI session:

>>>  $x=5
>>> $x
(int) 5
>>> $y=$x+3
>>> $y+1
(int) 9

The original CSI dollar-sign syntax has been extended for the Unity C# Interpreter plug-in. The mechanism will first try to interpret the names as those of variables you have defined, but, when retrieving variable values, it can fall back to some of the query mechanisms provided by Unity if the specified name is not a session variable. For example, if you type “$Sphere” and hit Enter, the interpreter would first look for a session variable named Sphere that may have been defined through an earlier assignment. But if that variable does not exist, the interpreter would look for a GameObject named Sphere and use that object if it exists. And if that also does not exist, it would try to find GameObject instances with the specified tag, or finally would try to interpret the specified name as that of an object type of which the instances need to be located. Therefore, writing something like “$Light” would typically give you quick access to all the light objects in your current game scene, since all those game objects would have a component of type UnityEngine.Light associated with them. To be able to include spaces or funny characters in the names, you wrap the name in curly brackets, for example, you can write “${Directional light}” and “${/Root Object/Child Object}”; also, “${Light}” and “$Light” means exactly the same thing.

Configuration Options through Inspector

CSI configuration options in Inspector window of Unity EditorWhen you have the GameObject with the CSI component selected in the Unity Editor, the Inspector window displays a number of configuration options (see image). Of course, these options are also available at runtime through programmatic access, although some of the values are only interpreted during the initialization phase of the interpreter. The configuration options are as follows:

  • Include File – This specifies the filename, either as a relative or absolute path, of the file that will be included during initialization, such as the default CSharpInterpreter_Include.txt file. Leave it blank if no file should be included.
  • Include Asset – Instead of specifying an include file though its filename, you can define a text asset in your project and drag-and-drop the asset (such as “CSharpInterpreter_Include”) from the Project window to this property to be included during initialization.
  • Queued Asset – This specifies a text asset that will be executed at runtime during the next update cycle as if it were entered as input text. Once executed, the property resets again. Its initial value can be used to specify another asset that must be executed immediately after initialization, but, more importantly, it can be used while the scene is playing in the Unity Editor to execute pre-written scripts for use at runtime. Therefore, you can build yourself a library of C# scripts that you want to execute through the interpreter and drag-and-drop those scripts at runtime to this property when needed.
  • Max History Size – This is the maximum number of items that will be kept in your input history for navigation.
  • Max Output Size – Specifies the maximum number of characters that will be displayed in the output window.
  • Show Interactive GUI, Show Output Text, Show Output As Editor Selection, Show Tooltip Text – These properties correspond with the toggle buttons on the runtime user-interface (discussed in the previous section).
  • Left Margin, Top Margin, Right Margin, Bottom Margin – These represent percentages of the screen width or height that surround the runtime user-interface (when fully expanded). Values set to NaN will be calculated automatically.
  • Toolbox Width – This represents the pixel width of the toolbox area to the left of the input window.
  • Splitter Fraction – Specifies an approximate percentage of the user-interface height (when fully expanded) that gets split between the output window and the controls below it.
  • Max Output Line Width, Max Output Line Count – With these options you can limit the maximum characters per line and the number of full lines that are written to the output window for results. (The output window will still automatically break the lines further if they are wider than the output window.)

Final Thoughts

While I’m on the topic of a project that extends the original CSI code, I would like to mention a related project by Samuel Christie called XNA Console, which sports a Python interpreter. Well, I also wrote a C# interpreter extension for the XNA Console, similar to this one for Unity and also based on CSI – it hasn’t been released publically, though, maybe someday if there is enough interest.

Also, if you look at the default CSharpInterpreter_Include.txt file, there is a line that would include the System.Linq namespace, but it is commented out. The reason for this is that the LINQ functionality that is included with the older version of Mono for Unity 2.6.1 is not fully functional (for one thing, Enumerable.Range doesn’t work quite right). But if you feel adventurous, add the namespace and see what is available; for example, doing so would make the following console interaction possible:

>>>  new int[]{7,13,19}.Select(x=>x*2).OrderByDescending(x=>x)
(Linq.OrderedSequence<int>)
{38,26,14}

Although the code above doesn’t seem to be possible in a regular Unity C# script, it does work in the C# interpreter. However, something more complex like “$Transform.Select(t=>t.position)” still doesn’t work because the Select extension method cannot be found. Hopefully the Unity team will upgrade their Mono version to a newer one before too long. With a newer version of Mono, one could perhaps even rewrite the plug-in to use Mono’s own csharp shell, which might improve speed and stability.

Oh, and it often helps to implement a pause feature for your game and use it while working in the interpreter, unless you don’t mind falling off a cliff while navigating the input history, or a monster destroying your $Player game-object before you hit Enter, or... you get the idea. Have fun!

Downloads:

Comments

Re: Another console window?

Bob: You are probably looking for the ExecuteCode method in the CSharpInterpreter class, which takes a string parameter for code to be executed from your custom text-field. If you want the output to also be redirected to a different text field, you may want to implement a custom IConsole interface and assign an instance to CSI.Interpreter.Console.

Thanks for the reply

Hi Tiaan

Thanks for the reply it was really helpful. So far I can type code to execute in my console window, hit return inside the CSI window and have the code run from my text input :)

I see that the OnExecuteInput() handles the actual running of the code i.e when Return is pressed within the CSI. I'm trying to make it so that the player types code inside my console window and clicks a 'Run Code' GUI button it will run the code.
I have changed the OnExecuteInput method to public so that my console class can access & execute it, but so far after various combinations I can't seem to get it to execute the code string from my console, plus I'm not getting any error messages. Am I on the right path or does another method do what I'm looking for?

Thanks Bob

Re: Thanks for the reply

Bob: It sounds like you're on the right track. The quickest way to do what you want with the output might be to just modify the existing code in the CSharpInterpreter.cs file. For example, to handle the output text, modify the CSI.IConsole.Write method (near line 1486), and progressively concatenate and write the resulting text as it arrives to wherever you want to see it. Also, to execute the input code, you can probably just pass your input text directly from your button-click handler to the ExecuteCode method that is already public (that is, you should be able to bypass OnExecuteInput entirely).

Again thanks

Hi Tiaan

Again thanks for the speedy reply :)

Also, it now works as I want it to. I took your advice on passing the string from my console straight to the ExecuteCode() method, it took a bit of trail and error as the advancement I had in my last post was making Unity crash out (big time), plus I was referencing the CSharpInterpreter class wrongly.

Again, thank you so much for your help, most people wouldn't have offered the support you have, but you've given me the right amount of encouragement for me to reach my goal.

Bob

Re: Again thanks

You're welcome, Bob! I'm glad to hear you got the modification with the custom input window working the way you wanted.

Editor Freezes the moment any Code is executed

Hey Tiaan,

Your Tool is just great, I'm currently playing around with it for my diploma thesis where I'm working on a game where students learn to code with C#.

However, I'm having a problem in the Unity Editor on Windows 7. Whenever I try to execute code in the editor it just freezes, if I run the same code in the standalone build it works just fine.

However, and this is where it gets funny: if I add a breakpoint somewhere in the csharpinterpreter.cs or the interpreter.cs itself and try to debug it, it also compiles in the editor. But as soon as I restart Unity and don't use a breakpoint it freezes again.

I added /Mono/bin and /Mono/lib to the build's root directory (so it runs on Systems without the mono framework installed) and the corresponding search paths to the csharpinterpreter's RunOnce method.

Do you have any idea, what the problem might be because I've been debugging the past 3 days without any luck ;(.

Re: Editor Freezes the moment any Code is executed

CSI works just fine for me with Unity 5.1 on PC (Windows 8), even when running in the Unity Editor.

It could be a Mac-specific issue that I currently cannot test. Maybe it is related to the issue that John mentioned in January 2013 when trying on Mac OS with Unity 4.

Also, when you debug with breakpoints (using MonoDevelop and such), I won't be surprised if the static initialization involving RunOnce may execute somewhat differently than directly in the Editor without breakpoints. You may be better off simply with Debug.Log messages to debug the flow and find the problem on your machine. If the Editor freezes such that you cannot see the Console messages, check the Editor log file for your debug message or use a custom log method that writes to your own file.

Writing that RunOnce method involved a lot of trial and error to get it to work initially, and you may have to restart Unity (and the debugger if you do decide to still use it) when you make any change to ensure consistent behavior while testing to make sure the change really works.

Please let me know what you discover.

Re: Editor Freezes the moment any Code is executed

Ok, in a new example project everything worked just fine. So I compared the editor logs and removed the .dll files I was using in my project. That way I found out, that the Unity-VisualStudio plugin caused the freezes in the editor.
However, I had the 2013 version of Visual Studio, so I updated it to the Community version of 2015, downloaded the new UnityVS Plugin and it just worked.

Thanks for the quick help, and of course for the amazing project!

Re: Editor Freezes the moment any Code is executed

@Squirrel: You're welsome, and I'm happy to hear CSI works with Visual Studio 2015 Tools for Unity (a.k.a. SyntaxTree's UnityVS) in the Community edition. I also think the free UnityVS plugin is just awesome. Thanks for the feedback!

Unity 5 UI Support

There's a problem with the new UI System of Unity 5. If you reference a new UI class somewhere in one of your classes you can't call anything on them unless you reference the unityengine.UI.dll and add the UnityEngine.UI namespace.

I added a private variable "private Assembly unityUIAssembly" and added this snippet to Reinitialize() of CSharpInterpreter.cs. (Sorry for indentless code ...)

if (this.unityUIAssembly == null)
{
try
{
if (assembly.FullName.StartsWith("UnityEngine.UI,", StringComparison.OrdinalIgnoreCase) == true)
{
this.unityUIAssembly = assembly;
}
}
catch
{
// Skip problematic assemblies
}
}
if (this.unityUIAssembly != null)
{
string filename = GetFullPathOfAssembly(this.unityUIAssembly);
if (File.Exists(filename))
{
csharpEngine.AddReference(filename);
csharpEngine.AddNamespace("UnityEngine.UI");
}
else
this.unityUIAssembly = null;
}
if (this.unityUIAssembly == null)
Debug.LogWarning("UnityEngine.UI is not referenced!");

Re: Unity 5 UI Support

@Squirrel: Thanks for your contribution! This looks like something I might want to add for Unity 4.6 and above. If you're familiar with Git, can you send me a Pull Request on the project's GitHub repository with your changes?

what is the variable

what is the variable 'assembly' referencing in this snippet?

Re: what is the variable

Have a look at the Reinitialize method: There you'll find the for-each loop that defines the assembly variable.

Running CSI after build!

Hey man, thanks a lot for this interpreter, works like a charm.
I'm doing a school project where I teach people to code by playing a unity game.
A console pops up and they can run code to pass challenges.
Everything works fine in the UnityPlayer as well as after Build, although it doesn't work on other computers.

Does it require any dependencies to MonoDevelop or Unity?

Re: Running CSI after build!

I’m glad to hear that CSI still works after all these years. I suppose you’re using Unity 5.3, or maybe the 5.4 beta?

At runtime the interpreter must find Unity’s C# compiler (gmcs.exe and such). You may need to define the CSI_COMPILER_PATH environment variable for the problematic computers. There are some earlier comments about it that may be helpful.

Can it work like in the game Space Engineers?

Hello!
I was super exited when I found this tool! I searched for it for a long time.
However, once I tested it, I am became sad, since turned out that it works only like a simple console which runs commands which can be executed only once (not each frame).
Because when I run even a simple code, it requires almost a second to execute it. So each invoke during each frame is impossible.

So it is only a simple console and I cannot create some kind of "Programmable Block" like in the game Space Engineers?
Where I can get variables from one device, process it with the code and transmit to another devices, as well as running their methods?
Check this link about Space Engineers
https://www.youtube.com/watch?v=FfLPjNZiCQo

Or similar functionality presents is some plugins from Unity Asset store:
Java https://www.assetstore.unity3d.com/en/#!/content/2345
Python https://www.assetstore.unity3d.com/en/#!/content/645
Lua https://www.assetstore.unity3d.com/en/#!/content/391

But I would prefer C# instead of these language.

So, can it work in such way? Maybe I just do not understand something.
If so, it would be great to have some examples and slightly better documentation. I am even ready to buy such tool if it will be available on the Asset Store!

Thanks :)

Re: Can it work like in the game Space Engineers?

Artaani, what you're describing can probably be done, but not using the current version of CSI. Although the compilation step does take a little while, once the code is compiled it executes just as fast as the regular C# scripts in Unity. It would therefore be possible to call simple methods on every frame once they are compiled. However, you'll have to enhance the current code-base to do exactly what you want. I do not currently plan to make such enhancements or write additional documentation any time soon.

Re: Can it work like in the game Space Engineers?

Thanks for the answer!
No problem, that's okay. I already found similar solutions which seems like can do what I need.
If you're interested:
1. https://www.assetstore.unity3d.com/en/#!/content/23510
2. https://github.com/aeroson/mcs-ICodeCompiler

But thanks anyway! :)

I'm a bit stumped

I'm a bit stumped by an error I'm seeing.

When I try to create a new instance of a CSI.CSharpInterpreter I ultimately end up with this error:


TypeLoadException: A type load exception has occurred.
at CSharpInterpreter.Start () [0x00000] in :0

Debugging/breakpointing my code is tricky for various reasons, so instead I've leaned on logging. I've verified that the error above occurs when CSharpInterpreter.Start() tries to call this.Reinitialize().

Within CSharpInterpreter.Reinitialize() I've wrapped try/catch blocks around almost everything. For example, I changed the first conditional to the following:


private bool Reinitialize()
{
Debug.Log("Starting in CSharpInterpreter.Reinitialize()");
bool a = false;
bool b = false;
bool c = false;

try {
a = csharpEngine != null; /* Has an interpreter */
} catch {
Debug.Log("csharpEngine != null failed");
}
try {
b = CSI.Interpreter.Console != null; /* Has a console */
} catch {
Debug.Log("CSI.Interpreter.Console != null failed");
}
try {
c = !object.ReferenceEquals (CSI.Interpreter.Console, this); /* Console is another object */
} catch {
Debug.Log("!object.ReferenceEquals failed");
}

if (!a && !b && !c) {
Debug.Log ("a, b, and c are still false");
return false;
}

if (a) Debug.Log ("csharpEngine is not null");
if (b) Debug.Log ("CSI.Interpreter.Console is not null");
if (c) Debug.Log ("Console is another object");

return true;

if (a && b && c) // never reached, but should work properly if above 'return true' is removed
{
Debug.Log("a, b, and c are all true, moving on");

The error occurs before any of those log messages show up. That's funky, right? I would expect to see some log entries before returning true. But I don't. Just calling this.Reinitialize() from Start() seems to throw the exception. It doesn't matter what's in Reinitialize.

Changing Reinitialize to still produces the same error. The difference here is that the log entry shows up, immediately followed by the original error:


private bool Reinitialize()
{
Debug.Log("Starting in CSharpInterpreter.Reinitialize()");
return true;
}

For now, I'm stumped.

While writing this it occurs to me that I should probably see what's special about the Reinitialize method and how it's changed over time.

As far as system details go, I'm running mono 4.8.0 on OSX 10.11.5. I don't know exactly what version of Unity engine is being used - I'm adding an external library to existing Unity assemblies. I'd assume I could find out what version they are, but currently don't know how I'd find out.

Re: I'm a bit stumped

@Buffington: Given the TypeLoadException, my guess is that your added external library doesn't play nice with whole setup. It can be very tricky indeed.