Recently, I became more and more interested in functional programming, and when choosing a language before me, I chose among two languages I really liked - Haskell and F # .
In F #, I was seduced by the fact that it can be compiled into an MSIL build, which makes it possible to use the F # class libraries in other Microsoft .Net languages, and also that it can use them. Everything else, I am also a beginning Unity3D developer, and a thought came to my mind: if I compiled into MSIL, can I use F # scripts in Unity? Googling gave the answer: humanly it is impossible. You can create a class library, put in the project links to the UnityEngine.dll library, compile and import as an asset , and then add Mono-behaviour components directly from the library, but this is not very convenient, agree. However, after going through Google, Reflection and Help on Unity , I still managed to bring (but not repeat exactly) the work with F # scripts inside the editor to the form in which the work with scripts in the built-in languages is performed. Details - under habrakat.
//Please, don't try to change namespace namespace Assembly_FSharp_vs open UnityEngine; type public SphereMoving () = inherit UnityEngine.MonoBehaviour() member public this.Start () = UnityEngine.Debug.Log("initialized") member public this.Update() = let mutable newpos:Vector3 = Vector3.zero newpos.x <- Mathf.Sin(Time.time) newpos.y <- Mathf.Cos(Time.time) this.transform.position <- newpos
Internal compiler error. See the console log for more information. output was:
Unhandled Exception: System.TypeLoadException: Could not load type 'System.CodeDom.Compiler.CompilerResults' from assembly 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
IEnumerator asmEnum = System.AppDomain.CurrentDomain.GetAssemblies().GetEnumerator(); while (asmEnum.MoveNext()) { Assembly asm = asmEnum.Current as Assembly; if (asm.FullName.Contains("System, V")) { comparamtype = asm.GetType("System.CodeDom.Compiler.CompilerParameters"); } if (asm.FullName.Contains("FSharp.Compiler.CodeDom")) { compilertype = asm.GetType("Microsoft.FSharp.Compiler.CodeDom.FSharpCodeProvider"); } }
object _params = System.Activator.CreateInstance(comparamtype, new object[] { new string[] { "System", "System.Core", UenginePath } }); comparamtype.GetProperty("IncludeDebugInformation").SetValue(_params, true, new object[] { }); comparamtype.GetProperty("OutputAssembly").SetValue(_params, @"Assets/Assembly/Assebly-FSharp-vs.dll", new object[] { }); object compiler = System.Activator.CreateInstance(compilertype);
static string UenginePath { get { return UnityEditor.EditorApplication.applicationContentsPath + "/Managed/UnityEngine.dll"; } }
string[] _files = Directory.GetFiles("Assets/", "*.fs"); typenameToObject = new Dictionary<string, UnityEngine.Object>(); foreach (string file in _files) { UnityEngine.Object o = AssetDatabase.LoadAssetAtPath(file, typeof(UnityEngine.Object)); if (CollectCompileDeploy.typenameToObject != null) { CollectCompileDeploy.typenameToObject.Add(o.name, o); } else { return; } }
[CustomEditor(typeof(UnityEngine.Object))] public class FSharpScriptInspector : Editor { public SerializedProperty test; string text; void OnEnable() { Repaint(); } public override void OnInspectorGUI() { GUI.enabled = true; if (!AssetDatabase.GetAssetPath(Selection.activeObject).EndsWith(".fs")) { DrawDefaultInspector(); } else { if (text == null) { StreamReader sr = File.OpenText(AssetDatabase.GetAssetPath(Selection.activeObject)); text = sr.ReadToEnd(); sr.Close(); } GUILayout.Label("Imported F# script"); EditorGUILayout.TextArea(text); } } }
using UnityEngine; using System.Collections; using UnityEditor; using System.IO; using System.Collections.Generic; [CustomEditor(typeof(UnityEngine.Transform))] public class ComponentCI : Editor { Vector3 position; void OnEnable() { Repaint(); } public override void OnInspectorGUI() { EditorGUILayout.BeginVertical(); (this.target as Transform).localRotation = Quaternion.Euler(EditorGUILayout.Vector3Field("Local Rotation", (this.target as Transform).localRotation.eulerAngles)); (this.target as Transform).localPosition = EditorGUILayout.Vector3Field("Local Position", (this.target as Transform).localPosition); (this.target as Transform).localScale = EditorGUILayout.Vector3Field("Local Scale", (this.target as Transform).localScale); EditorGUILayout.EndVertical(); if (Event.current.type == EventType.DragPerform) { if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).EndsWith(".fs")) { (this.target as Transform).gameObject.AddComponent(DragAndDrop.objectReferences[0].name); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using UnityEditor; using System.IO; [CustomEditor(typeof(Assembly_FSharp_vs.SphereMoving))] [CanEditMultipleObjects] public class ins_SphereMoving : Editor { public SerializedProperty prop1; public List<SerializedProperty> props; void OnEnable() { props = new List<SerializedProperty>(); System.Reflection.FieldInfo[] fields = typeof(Assembly_FSharp_vs.SphereMoving).GetFields(); foreach (System.Reflection.FieldInfo field in fields) { SerializedProperty mp = serializedObject.FindProperty(field.Name); if (mp != null) { props.Add(mp); } } Repaint(); } public override void OnInspectorGUI() { if (UnityEditor.EditorApplication.isCompiling) { EditorGUILayout.LabelField("Can't show anything during compilation"); //I don't want to live on this scope anymore! return; } try { EditorGUILayout.ObjectField("Script", CollectCompileDeploy.typenameToObject.ContainsKey("SphereMoving") ? CollectCompileDeploy.typenameToObject["SphereMoving"] : null, typeof(UnityEngine.Object), false); EditorGUILayout.BeginVertical(); foreach (SerializedProperty p in props) { EditorGUILayout.PropertyField(p); EditorGUILayout.Space(); } this.serializedObject.ApplyModifiedProperties(); EditorGUILayout.EndVertical(); } catch { } } }
[MenuItem("Assets/Create/F# script")] public static void CreateFS() { string path = AssetDatabase.GetAssetPath(Selection.activeObject); string addNum = ""; if (Selection.activeInstanceID <= 0) { path="Assets"; } if (path.Contains(".")) { path =Directory.GetParent(path).ToString(); } while (File.Exists(path + "/NewBehaviourScript" + addNum.ToString() + ".fs")) { addNum = addNum == "" ? (1).ToString() : (int.Parse(addNum) + 1).ToString(); } path = path + "/NewBehaviourScript" + addNum.ToString() + ".fs"; StreamWriter sw = File.CreateText(path); sw.WriteLine("//Please, don't try to change namespace"); sw.WriteLine("namespace Assembly_FSharp_vs"); sw.WriteLine("type public " + "NewBehaviourScript" + addNum.ToString() + " () ="); sw.WriteLine(" inherit UnityEngine.MonoBehaviour()"); sw.WriteLine(" [<DefaultValue>] val mutable showdown1 : UnityEngine.Vector3"); sw.WriteLine(" [<DefaultValue>] val mutable showdown2 : UnityEngine.Vector3"); sw.WriteLine(" [<DefaultValue>] val mutable showdown3: int"); sw.WriteLine(" member public this.Start () = UnityEngine.Debug.Log(\"initialized\")"); sw.Flush(); sw.Close(); AssetDatabase.LoadAssetAtPath(path,Type.GetType("UnityEngine.DefaultAsset,UnityEngine")); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); }
//Please, don't try to change namespace namespace Assembly_FSharp_vs type public NewBehaviourScript1 () = inherit UnityEngine.MonoBehaviour() [<DefaultValue>] val mutable showdown1 : UnityEngine.Vector3 [<DefaultValue>] val mutable showdown2 : UnityEngine.Vector3 [<DefaultValue>] val mutable showdown3: int member public this.Start () = UnityEngine.Debug.Log("initialized")
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProductVersion>8.0.30703</ProductVersion> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>{ACFBFD03-C456-E983-5028-ACC6C3ACEA62}</ProjectGuid> <OutputType>Library</OutputType> <RootNamespace>Assembly_FSharp_vs</RootNamespace> <AssemblyName>Assembly_FSharp_vs</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <Name>Assembly-FSharp-vs</Name> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <Tailcalls>false</Tailcalls> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <WarningLevel>3</WarningLevel> <DocumentationFile>bin\Debug\Assembly_FSharp_vs.XML</DocumentationFile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <Tailcalls>true</Tailcalls> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <WarningLevel>3</WarningLevel> <DocumentationFile>bin\Release\Assembly_FSharp_vs.XML</DocumentationFile> </PropertyGroup> <ItemGroup> <Reference Include="mscorlib" /> <Reference Include="FSharp.Core" /> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Numerics" /> <Reference Include="D:/Program Files/Unity3.5/Editor/Data/Managed/UnityEngine.dll" /> </ItemGroup> <ItemGroup> <Compile Include="Assets/NewBehaviourScript.fs" /> <Compile Include="Assets/SphereMoving.fs" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\FSharp\1.0\Microsoft.FSharp.Targets" Condition="!Exists('$(MSBuildBinPath)\Microsoft.Build.Tasks.v4.0.dll')" /> <Import Project="$(MSBuildExtensionsPath32)\..\Microsoft F#\v4.0\Microsoft.FSharp.Targets" Condition=" Exists('$(MSBuildBinPath)\Microsoft.Build.Tasks.v4.0.dll')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> </Target> <Target Name="AfterBuild"> </Target> --> </Project>
UnityEditor.VisualStudioIntegration.SolutionGuidGenerator ( , " SolutionGUIDGenerator Unity3d" ( ) 1 , .
, , ). GUID , . — . , , , ( ) : Project("ACFBFD03-C456-E983-5028-ACC6C3ACEA62") = ".\FSharp-csharp", "Assembly-FSharp-vs.fsproj", "{9512B9D0-6FAE-8F85-778C-B28C5421520D}" EndProject
static string xmlSourceFileDataEnter = "<Compile Include=\""; static string xlmSourceFileDataEnding = "\" />"; [MenuItem("F#/Update solution file")] public static void UpdateSolution() { if(File.Exists("Assembly-FSharp-vs.fsproj")) { File.Delete("Assembly-FSharp-vs.fsproj"); } StreamWriter sw = File.CreateText("Assembly-FSharp-vs.fsproj"); sw.WriteLine(xmlEnterData); foreach (UnityEngine.Object file in typenameToObject.Values) { sw.Write(xmlSourceFileDataEnter); sw.Write(AssetDatabase.GetAssetPath(file)); sw.WriteLine(xlmSourceFileDataEnding); } sw.WriteLine(xmlFinishData); sw.Flush(); sw.Close(); sw.Dispose(); string[] slnfiles = Directory.GetFiles(".","*-csharp.sln"); if (slnfiles != null && slnfiles.Length>0) { StreamReader sr = File.OpenText(slnfiles[0]); List<string> lines = new List<string>(); while (!sr.EndOfStream) { string readenLine = sr.ReadLine(); lines.Add(readenLine); if (readenLine.Contains("Assembly-FSharp-vs.fsproj")) { sr.Close(); sr.Dispose(); return; } } sr.Close(); sr.Dispose(); sw = File.CreateText(slnfiles[0]); List<string>.Enumerator linesEnum = lines.GetEnumerator(); linesEnum.MoveNext(); sw.WriteLine(linesEnum.Current); linesEnum.MoveNext(); sw.WriteLine(linesEnum.Current); string slinname = slnfiles[0].Remove(slnfiles[0].LastIndexOf(".sln")); sw.WriteLine("Project(\"" + UnityEditor.VisualStudioIntegration.SolutionGuidGenerator.GuidForProject("Assembly-FSharp-vs") + "\") = \"" + slinname + "\", \"Assembly-FSharp-vs.fsproj\", \"{"+UnityEditor.VisualStudioIntegration.SolutionGuidGenerator.GuidForSolution(slinname)+"}\""); sw.WriteLine("EndProject"); while (linesEnum.MoveNext()) { sw.WriteLine(linesEnum.Current); } sw.Flush(); sw.Close(); sw.Dispose(); } }
public class UniversalEnumerator : IEnumerator { List<Func<object>> allCodeFrames = new List<Func<object>>(); IEnumerator codeEnum; Func<object> finishAction = null; public Func<object> FinishAction { get { return finishAction; } set { finishAction = value; } } public void Add(Func<object> code) { allCodeFrames.Add(code); } public void _Finalize() { codeEnum = allCodeFrames.GetEnumerator(); } public object Current { get { return codeEnum.Current; } } public bool MoveNext() { bool res = codeEnum.MoveNext(); if (res) { (codeEnum.Current as Func<object>)(); } else { if (FinishAction != null) { return FinishAction()!=null; } } return res; } public void Reset() { codeEnum = null; allCodeFrames.Clear(); FinishAction = null; } }
static UniversalEnumerator currentRoutine; static CollectCompileDeploy() { UnityEditor.EditorApplication.update += new EditorApplication.CallbackFunction(() => { if (currentRoutine != null) { if (!currentRoutine.MoveNext()) { currentRoutine = null; } } }); Initialize(); }
public enum CompilationState { GATHER =1, COMPIL, VALIDATION, SOLUTION, DONE, NONE } public static UniversalEnumerator Recompile() { UniversalEnumerator myEnum = new UniversalEnumerator(); _current = CompilationState.GATHER; CompilationProgressWindow.Init(); myEnum.Add(() => { files.Clear(); ReassingTypes(); return null; }); bool exitall = false; myEnum.Add(() => { if (File.Exists("Assets/Assembly/Assembly-FSharp-vs.dll")) { AssetDatabase.DeleteAsset("Assets/Assembly/Assembly-FSharp-vs.dll"); File.Delete("Assets/Assembly/Assembly-FSharp-vs.dll"); } if (files.Count == 0) { Debug.Log("seems like no any F# file here.terminating"); _current = CompilationState.NONE; exitall = true; } return null; }); System.Type comparamtype = null; System.Type compilertype = null; myEnum.Add(() => { if (exitall) { return null; } UniversalEnumerator bufferedRoutine = currentRoutine; UniversalEnumerator nroutine = new UniversalEnumerator(); IEnumerator asmEnum = System.AppDomain.CurrentDomain.GetAssemblies().GetEnumerator(); while (asmEnum.MoveNext()) { Assembly asm = asmEnum.Current as Assembly; nroutine.Add(() => { if (asm.FullName.Contains("System, V")) { comparamtype = asm.GetType("System.CodeDom.Compiler.CompilerParameters"); } if (asm.FullName.Contains("FSharp.Compiler.CodeDom")) { compilertype = asm.GetType("Microsoft.FSharp.Compiler.CodeDom.FSharpCodeProvider"); } return null; }); } nroutine.FinishAction = () => { currentRoutine = bufferedRoutine; return new object(); }; nroutine._Finalize(); currentRoutine = nroutine; return null; }); myEnum.Add(() => { if (exitall) { return null; } UnityEditor.EditorApplication.LockReloadAssemblies(); try { object _params = System.Activator.CreateInstance(comparamtype, new object[] { new string[] { "System", "System.Core", UenginePath } }); comparamtype.GetProperty("IncludeDebugInformation").SetValue(_params, true, new object[] { }); comparamtype.GetProperty("OutputAssembly").SetValue(_params, @"Assets/Assembly/Assebly-FSharp-vs.dll", new object[] { }); object compiler = System.Activator.CreateInstance(compilertype); List<string> __fls = new List<string>(); foreach(UnityEngine.Object asset in typenameToObject.Values) { __fls.Add(AssetDatabase.GetAssetPath(asset)); } _current = CompilationState.COMPIL; object _output = compilertype.GetMethod("CompileAssemblyFromFile").Invoke(compiler, new object[] { _params, __fls.ToArray() }); compiled = _output.GetType().GetProperty("CompiledAssembly").GetValue(_output, new object[] { }) as Assembly; foreach (object message in _output.GetType().GetProperty("Output").GetValue(_output, new object[] { }) as System.Collections.Specialized.StringCollection) { Debug.Log(message); } foreach (object error in (_output.GetType().GetProperty("Errors").GetValue(_output, new object[] { }) as System.Collections.CollectionBase)) { Debug.LogError(error); } if (compiled != null) { _current = CompilationState.VALIDATION; UniversalEnumerator bufferedRoutine = currentRoutine; UniversalEnumerator nroutine = ValidateInspectors(); nroutine.Add(() => { _current = CompilationState.SOLUTION; UpdateSolution(); _current = CompilationState.DONE; CompilationProgressWindow.Remove(); AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); return null; }); nroutine._Finalize(); nroutine.FinishAction = () => { currentRoutine = bufferedRoutine; return new object(); }; currentRoutine = nroutine; } else { Debug.LogError("compiled assembly is still not visible!"); } } catch { } UnityEditor.EditorApplication.UnlockReloadAssemblies(); return null; }); myEnum._Finalize(); return myEnum; }
UniversalEnumerator bufferedRoutine = currentRoutine; UniversalEnumerator nroutine = new UniversalEnumerator(); nroutine.Add(() => { // . return null; }); nroutine.FinishAction = () => { currentRoutine = bufferedRoutine; return new object(); }; nroutine._Finalize(); currentRoutine = nroutine;
private static CompilationState __current = CompilationState.NONE; public static CompilationState _current { get { return CollectCompileDeploy.__current; } set { CollectCompileDeploy.__current = value; if (CompilationProgressWindow.me != null) { CompilationProgressWindow.me.Repaint(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEditor; using UnityEngine; using System.Runtime.InteropServices; public class CompilationProgressWindow : EditorWindow { public static CompilationProgressWindow me; [StructLayout(LayoutKind.Sequential)] public struct WndRect { public int Left; public int Top; public int Right; public int Bottom; } #if UNITY_EDITOR && UNITY_STANDALONE_WIN [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] static extern bool GetWindowRect(IntPtr hWnd, out WndRect rect); public static Vector2 GetWNDSize() { WndRect r = new WndRect(); GetWindowRect(GetForegroundWindow(), out r); return new Vector2(r.Right - r.Left, r.Bottom - r.Top); ; } #else public static Vector2 GetWNDSize() { return Vector2.zero; } #endif public static void Init() { if (me == null) { Vector2 mainWND = GetWNDSize(); CompilationProgressWindow window = (CompilationProgressWindow)EditorWindow.GetWindow(typeof(CompilationProgressWindow), true, "F# Compilation progress", true); window.position = new Rect(mainWND.x/2-200, mainWND.y / 2 - 50, 400, 100); window.title = "F# compilation progress"; window.Focus(); me = window; } else { me.Focus(); } } void OnGUI() { string msg = ""; switch (CollectCompileDeploy._current) { case CollectCompileDeploy.CompilationState.GATHER: msg = "Gathering data"; break; case CollectCompileDeploy.CompilationState.NONE: this.Close(); break; case CollectCompileDeploy.CompilationState.COMPIL: msg = "Compiling F# assembly"; break; case CollectCompileDeploy.CompilationState.DONE: msg = "Done! Wait for assembly import and enjoy"; break; case CollectCompileDeploy.CompilationState.SOLUTION: msg = "Preparing solution files for usage"; break; case CollectCompileDeploy.CompilationState.VALIDATION: msg = "Validating editor scripts"; break; } EditorGUI.ProgressBar(new Rect(0, 0, 400, 100), ((float)CollectCompileDeploy._current) / 5f, msg); } public void OnLostFocus() { this.Focus(); } public static void Remove() { if (me != null) { me.Close(); } else { } } }
Source: https://habr.com/ru/post/151009/
All Articles