├── Documentation.meta ├── LuaVM.cs.meta ├── LuaAPIBase.cs.meta ├── LuaAttributes.cs.meta ├── Documentation ├── LuaDocGenerator.cs.meta └── LuaDocGenerator.cs ├── .gitignore ├── LICENSE ├── LuaAttributes.cs ├── LuaAPIBase.cs ├── README.md └── LuaVM.cs /Documentation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f1d5d03c2d6347718f28717b3e0bc68 3 | folderAsset: yes 4 | timeCreated: 1500644749 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /LuaVM.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0568b3618328543a18ba0783a6562e8c 3 | timeCreated: 1500644733 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /LuaAPIBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed27cd522b96747978643987e0c57770 3 | timeCreated: 1500644733 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /LuaAttributes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ceb294d14a73461ea862712195cfcb8 3 | timeCreated: 1500644733 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Documentation/LuaDocGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1bae86bac4ada49b5961c48ccd81d635 3 | timeCreated: 1500644753 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Visual Studio 2015 cache directory 9 | /.vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | 26 | # Unity3D generated meta files 27 | *.pidb.meta 28 | 29 | # Unity3D Generated File On Crash Reports 30 | sysinfo.txt 31 | 32 | # Builds 33 | *.apk 34 | *.unitypackage 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Harry 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LuaAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | /// 4 | /// Used to document lua apis 5 | /// 6 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 7 | public class LuaApi : Attribute 8 | { 9 | public string luaName = string.Empty; 10 | public string description = string.Empty; 11 | public string notes = string.Empty; 12 | } 13 | 14 | /// 15 | /// Used to document lua functions 16 | /// 17 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 18 | public class LuaApiFunction : Attribute 19 | { 20 | public string name = string.Empty; 21 | public string description = string.Empty; 22 | public string returns = string.Empty; 23 | public string notes = string.Empty; 24 | public string warning = string.Empty; 25 | public string success = string.Empty; 26 | public string codeExample = string.Empty; 27 | } 28 | 29 | /// 30 | /// Used to document lua variables 31 | /// 32 | [AttributeUsage(AttributeTargets.Field)] 33 | public class LuaApiVariable : Attribute 34 | { 35 | public string name = string.Empty; 36 | public string description = string.Empty; 37 | } 38 | 39 | /// 40 | /// Used for generating lua counterparts to C# enums as well as document them 41 | /// 42 | [AttributeUsage(AttributeTargets.Enum)] 43 | public class LuaApiEnum : Attribute 44 | { 45 | public string name = string.Empty; 46 | public string description = string.Empty; 47 | } 48 | 49 | /// 50 | /// Used to document enum values 51 | /// 52 | [AttributeUsage(AttributeTargets.Field)] 53 | public class LuaApiEnumValue : Attribute 54 | { 55 | public string description = string.Empty; 56 | public bool hidden = false; 57 | } 58 | -------------------------------------------------------------------------------- /LuaAPIBase.cs: -------------------------------------------------------------------------------- 1 | using MoonSharp.Interpreter; 2 | 3 | /// 4 | /// Base class for all lua api systems 5 | /// 6 | public abstract class LuaAPIBase 7 | { 8 | /// 9 | /// The table containing the API functions and variables 10 | /// 11 | protected Table m_ApiTable; 12 | 13 | /// 14 | /// The luaVM the api is attached to 15 | /// 16 | protected LuaVM m_ParentVM; 17 | 18 | /// 19 | /// The name of the Lua API, this will be used for the api name in Lua 20 | /// E.G 21 | /// 22 | /// C#: 23 | /// m_APIName = "ExampleAPI"; 24 | /// 25 | /// Lua: 26 | /// ExampleAPI.ExampleFunction() 27 | /// 28 | private readonly string m_APIName; 29 | 30 | /// 31 | /// Derived types must provide their name 32 | /// 33 | /// API name. 34 | protected LuaAPIBase(string APIName) 35 | { 36 | m_APIName = APIName; 37 | } 38 | 39 | /// 40 | /// Derived types must create a function that fills in the api table 41 | /// 42 | protected abstract void InitialiseAPITable(); 43 | 44 | /// 45 | /// Adds the API to lua instance. 46 | /// 47 | /// Lua instance. 48 | public void AddAPIToLuaInstance(LuaVM luaInstance) 49 | { 50 | // Set our parent 51 | m_ParentVM = luaInstance; 52 | 53 | // Make a new table 54 | Table apiTable = m_ParentVM.AddGlobalTable (m_APIName); 55 | if (apiTable != null) 56 | { 57 | // Set the api table 58 | m_ApiTable = apiTable; 59 | 60 | // Hand over to the API derived type to fill in the table 61 | InitialiseAPITable(); 62 | } 63 | else 64 | { 65 | Logger.Log (Channel.Lua, "Failed to Initilise API {0}", m_APIName); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-Lua 2 | A wrapper around MoonSharp that allows easy development of moddable Unity games 3 | 4 | # Installation 5 | * Create "Assets/Plugins/Lua" in your Unity project 6 | * Download and place the contents of this repository to the newly created folder 7 | 8 | # Dependancies 9 | [MoonSharp](http://www.moonsharp.org/) & [Unity-Logger](https://github.com/Semaeopus/Unity-Logger) 10 | 11 | # Code Breakdown 12 | 13 | This codebase allows Unity developers to easily create game specific modding apis for their Unity games! 14 | It was created during the development of [Off Grid](http://www.offgridthegame.com) 15 | 16 | ## Executing Lua Code 17 | LuaVM is the main Lua interface, it's the class you use to run Lua scripts or individual Lua command strings. 18 | 19 | ### Executing a string 20 | 21 | ```C# 22 | const string luaCode = @" 23 | -- Lua Code 24 | num = 1 + 1 25 | print(num) 26 | "; 27 | 28 | LuaVM vm = new LuaVM(); 29 | vm.ExecuteString(luaCode); // Prints 2 30 | ``` 31 | 32 | ### Executing a script 33 | 34 | Most of the time you'll want to load and execute Lua code from a script file written to disk, here's an example of how that's achieved. 35 | 36 | Contents of fruits.lua 37 | ```lua 38 | fruits = { 39 | "apple", 40 | "banana", 41 | } 42 | 43 | GetRandomFruit = function() 44 | return fruits[math.random(1, #fruits)] 45 | end 46 | ``` 47 | 48 | Here's some code that loads the fruits table from the script, as well as getting a reference to the GetRandomFruit and calling it. 49 | ```C# 50 | LuaVM vm = new LuaVM(); 51 | vm.ExecuteScript("/path/to/fruits.lua"); 52 | 53 | // Get table to iterate 54 | Table fruitTable = vm.GetGlobalTable("fruits"); 55 | foreach (DynValue fruit in fruitTable.Values) 56 | { 57 | Debug.Log(fruit.String); // Prints "apple" then "banana" 58 | } 59 | 60 | // Or get a lua function and call it 61 | DynValue fruitFunction = vm.GetGlobal("GetRandomFruit"); 62 | Debug.Log(vm.Call(fruitFunction).String); // Prints return of GetRandomFruit 63 | ``` 64 | 65 | ## Creating an API 66 | Creating an api with this framework is incredibly simple, let's say we're making a game where the player can interact with supermarkets. 67 | We of course want this game to be moddable, so let's write up information about the super market in Lua, things like stock and name for a start. 68 | 69 | Here's an example of a very simple Lua api to let the players get random items that can go in their shops stocklist. 70 | 71 | ```C# 72 | [LuaApi( 73 | luaName = "SuperMarket", 74 | description = "This is a test lua api")] 75 | public class SuperMarketAPI : LuaAPIBase 76 | { 77 | private readonly List m_Veggies = new List 78 | { 79 | "Aubergine", 80 | "Broccoli", 81 | "Cauliflower", 82 | "Carrot", 83 | "Kale", 84 | }; 85 | 86 | private readonly List m_Fruits = new List 87 | { 88 | "Strawberry", 89 | "Grape", 90 | "Lychee", 91 | "Melon", 92 | "Apple", 93 | }; 94 | 95 | public SuperMarketAPI() 96 | : base("SuperMarket") 97 | { 98 | } 99 | 100 | protected override void InitialiseAPITable() 101 | { 102 | m_ApiTable["GetRandomVeg"] = (System.Func) (Lua_GetRandomVeggies); 103 | m_ApiTable["GetRandomFruit"] = (System.Func) (Lua_GetRandomFruits); 104 | m_ApiTable["MaxStock"] = MaxStock; 105 | } 106 | 107 | [LuaApiEnumValue(description = "The max stock any shop should contain")] 108 | private const int MaxStock = 10; 109 | 110 | [LuaApiFunction( 111 | name = "GetRandomVeg", 112 | description = "Returns a random vegetable that can be stocked by an in-game shop" 113 | )] 114 | private string Lua_GetRandomVeggies() 115 | { 116 | int randomIndex = Random.Range(0, m_Veggies.Count - 1); 117 | return m_Veggies[randomIndex]; 118 | } 119 | 120 | [LuaApiFunction( 121 | name = "GetRandomFruit", 122 | description = "Returns a random fruit that can be stocked by an in-game shop" 123 | )] 124 | private string Lua_GetRandomFruits() 125 | { 126 | int randomIndex = Random.Range(0, m_Fruits.Count - 1); 127 | return m_Fruits[randomIndex]; 128 | } 129 | } 130 | 131 | ``` 132 | Lua apis become available to LuaVM instances by default, the first time a LuaVM is created reflection is used to cache all types that derive from LuaAPIBase. 133 | Here's a Lua script that uses the brand new SuperMarket api: 134 | 135 | ```lua 136 | Shop = { 137 | Name = "Dumpling's Super Store", 138 | Stock = {}, 139 | } 140 | 141 | -- Generate the stock items 142 | for i= 1, SuperMarket.MaxStock do 143 | table.insert(Shop.Stock, SuperMarket.GetRandomVeg()) 144 | table.insert(Shop.Stock, SuperMarket.GetRandomFruit()) 145 | end 146 | ``` 147 | 148 | Corrisponding C# code 149 | 150 | ```C# 151 | LuaVM vm = new LuaVM(); 152 | vm.ExecuteScript("/path/to/DumplingsStore.lua"); 153 | 154 | // Get the shops name 155 | string shopName = vm.GetGlobal("Shop", "Name").String; 156 | Debug.Log(shopName); // Prints "Dumpling's Super Store" 157 | 158 | // Get Items in stock 159 | Table fruitTable = vm.GetGlobalTable("Shop", "Stock"); 160 | foreach (DynValue item in fruitTable.Values) 161 | { 162 | Debug.Log(item.String); 163 | } 164 | 165 | ``` 166 | 167 | So there's a really simple example of how you can add arbitrary apis to your game for use in Lua. 168 | LuaApiBase uses the string passed into its constructor as the true name of the Lua api, in this example that's "SuperMarket". 169 | It then allows the derived type to fill in m_ApiTable, note how above this is really a MoonSharp wrapper around a Lua table, meaning that it's not just functions. 170 | Above we've used a MaxStock int which whilst it's currently a const, could be set by calling into a gameplay system. 171 | 172 | ### Exposing Enums 173 | One thing that can be a pain when dealing with lua is having to use raw ints rather than enums, using this framework fixes this issue by allowing the automatic generation of Lua versions of your enums. 174 | Let's say in our game the player can adopt pets, and we want modders to be able to create new pets with different personalities and abilities. 175 | Here's how you could go about exposing an enum type to your modders in order to know how to render the correct model/sprite. 176 | 177 | ```C# 178 | [LuaApiEnum( 179 | name = "PetType", 180 | description = "Defines what type a pet is")] 181 | public enum PetType 182 | { 183 | [LuaApiEnumValue( 184 | description = "Aloof and occasionally affectionate")] 185 | Cat, 186 | 187 | [LuaApiEnumValue( 188 | description = "A loyal best friend")] 189 | Dog, 190 | 191 | [LuaApiEnumValue( 192 | description = "Slow and a bit snappy")] 193 | Turtle, 194 | 195 | [LuaApiEnumValue( 196 | description = "Cute, small and loves grain")] 197 | Hamster, 198 | 199 | // Not ready for modders yet! 200 | [LuaApiEnumValue(hidden = true)] 201 | Dragon, 202 | } 203 | ``` 204 | 205 | In a similar vein to how Lua apis are automatically detected by LuaVM, any enum with the LuaApiEnum is automatically exposed to all lua scripts run from LuaVM. 206 | So modders can now use it like so: 207 | 208 | ```Lua 209 | Pet = { 210 | Name = "Rex", 211 | Type = PetType.Dog, 212 | attack = function(target) 213 | -- Attack Logic 214 | end 215 | } 216 | ``` 217 | 218 | ** Note: ** It's worth noting that enum values can be hidden from exposure by the LuaApiEnumValue attributes hidden value, just as we've done with the Dragon type above. 219 | 220 | ## Options 221 | The LuaVM constructor optionally takes in an instance of the VMSettings flag, this allows the user to attach only attach apis, enums, both or none at all. 222 | If you know you're not going to need any of the attachments, it's more performant to use ```new LuaVM(VMSettings.None)``` 223 | 224 | # Documentation Creation 225 | One of this frameworks most handy features is its automatic documentation creation. 226 | Document creation is triggered by using the built in Unity menu items: 227 | 228 | ![](https://i.imgur.com/AKUEhMS.png) 229 | 230 | Currently the framework supports the creation of the following documentation: 231 | * MediaWiki pages per api 232 | * Visual Studio Code snippets for auto complete 233 | * Atom Snippets for auto complete 234 | 235 | These are all created by using the attributes attached to your apis, api function, api variables and enums. 236 | All the available attributes are used in the code snippets above, however here's a quick reference: 237 | 238 | | Attribute | Use | 239 | |:---------:|---| 240 | | LuaApi | Above LuaApiBase derived type | 241 | | LuaApiFunction | Above functions that are part of the Lua api table | 242 | | LuaApiVariable | Above variables that are part of the Lua api table | 243 | | LuaApiEnum | Above enums that you want to be attached to LuaVMs | 244 | | LuaApiEnumValue | Above enums values that you'd like to describe or hide | 245 | 246 | Adding more documentation formats is very easy, why not try adding another doucmentation type yourself in LuaDocGenerator.cs! 247 | -------------------------------------------------------------------------------- /LuaVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MoonSharp.Interpreter; 4 | using MoonSharp.VsCodeDebugger; 5 | using MoonSharp.Interpreter.Loaders; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | /// 10 | /// Wrapper around the Moonsharp API. 11 | /// Implements heavy error checking and logging for end users 12 | /// 13 | public class LuaVM 14 | { 15 | /// 16 | /// The Moonsharp script object 17 | /// 18 | private readonly Script m_LuaScript = null; 19 | private static Type[] m_APITypeList = null; 20 | private LuaAPIBase[] m_APIList = null; 21 | private static DynValue m_EnumTables = null; 22 | 23 | /// 24 | /// Settings to control the behaviour of the VM 25 | /// 26 | [Flags] 27 | public enum VMSettings : uint 28 | { 29 | /// 30 | /// No custom code will be attached 31 | /// 32 | None = 0, 33 | 34 | /// 35 | /// We'll attach anything deriving from LuaAPIBase 36 | /// 37 | AttachAPIs = 1 << 0, 38 | 39 | /// 40 | /// We'll attach any enum with the LuaApiEnum attribute 41 | /// 42 | AttachEnums = 1 << 1, 43 | 44 | /// 45 | /// Attach everything we know about 46 | /// 47 | AttachAll = ~0u, 48 | } 49 | 50 | /// 51 | /// The Moonsharp remote debugger service 52 | /// 53 | private static MoonSharpVsCodeDebugServer s_RemoteDebugger = null; 54 | 55 | /// 56 | /// Default to attaching all apis and enums 57 | /// 58 | public LuaVM() 59 | : this(VMSettings.AttachAll) 60 | { 61 | } 62 | 63 | /// 64 | /// Default settings are a soft sandbox and setting up the file system script loader 65 | /// 66 | public LuaVM(VMSettings vmSettings) 67 | { 68 | m_LuaScript = 69 | new Script(CoreModules.Preset_SoftSandbox) 70 | { 71 | Options = 72 | { 73 | ScriptLoader = new FileSystemScriptLoader(), 74 | DebugPrint = log => Logger.Log (Channel.LuaNative, log), 75 | } 76 | }; 77 | 78 | if ((vmSettings & VMSettings.AttachAPIs) == VMSettings.AttachAPIs) 79 | { 80 | AttachAPIS(); 81 | } 82 | 83 | if ((vmSettings & VMSettings.AttachEnums) == VMSettings.AttachEnums) 84 | { 85 | AttachLuaEnums(); 86 | } 87 | } 88 | 89 | /// 90 | /// Add a new global Lua table and if successful return a reference to it 91 | /// 92 | /// 93 | /// 94 | public Table AddGlobalTable(string tableName) 95 | { 96 | Table table = null; 97 | 98 | if(SetGlobal(tableName, DynValue.NewTable(m_LuaScript))) 99 | { 100 | table = GetGlobal(tableName).Table; 101 | } 102 | else 103 | { 104 | Logger.Log(Channel.Lua, Priority.FatalError, "Failed to add global Lua table {0}", tableName); 105 | } 106 | 107 | return table; 108 | } 109 | 110 | /// 111 | /// Attempts to set a global variable with key and value 112 | /// 113 | /// 114 | /// 115 | /// true if value was set successfully 116 | public bool SetGlobal(string key, object value) 117 | { 118 | bool didSet = false; 119 | try 120 | { 121 | m_LuaScript.Globals[key] = value; 122 | didSet = true; 123 | } 124 | catch (InterpreterException ex) 125 | { 126 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua SetGlobal error: {0}", ex.DecoratedMessage); 127 | } 128 | return didSet; 129 | } 130 | 131 | /// 132 | /// Attempts to retrive a value from the Lua globals 133 | /// 134 | /// 135 | /// null if failure occurs, else the requested value as a DynValue 136 | public DynValue GetGlobal(string key) 137 | { 138 | DynValue result = DynValue.Nil; 139 | try 140 | { 141 | result = m_LuaScript.Globals.Get(key); 142 | } 143 | catch 144 | { 145 | Logger.Log(Channel.Lua, Priority.FatalError, "Failed to get Lua global {0}", key); 146 | } 147 | 148 | return result; 149 | } 150 | 151 | /// 152 | /// Attempts to retrive a value from the Lua globals, allowing the user to pass parent and children names in 153 | /// 154 | /// The global. 155 | /// Keys. 156 | public DynValue GetGlobal(params object[] keys) 157 | { 158 | DynValue result = DynValue.Nil; 159 | try 160 | { 161 | result = m_LuaScript.Globals.Get(keys); 162 | } 163 | catch 164 | { 165 | Logger.Log(Channel.Lua, Priority.FatalError, "Failed to get Lua global at '{0}'", 166 | string.Join(", ", Array.ConvertAll(keys, input => input.ToString()))); 167 | } 168 | 169 | return result; 170 | } 171 | 172 | /// 173 | /// Attempts to retrive a table from the Lua globals 174 | /// 175 | /// The global table. 176 | /// Key. 177 | public Table GetGlobalTable(string key) 178 | { 179 | Table result = null; 180 | DynValue tableDyn = GetGlobal (key); 181 | if (tableDyn != null) 182 | { 183 | if(tableDyn.Type == DataType.Table) 184 | { 185 | result = tableDyn.Table; 186 | } 187 | else 188 | { 189 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua global {0} is not type table, has type {1}", key, tableDyn.Type.ToString()); 190 | } 191 | } 192 | return result; 193 | } 194 | 195 | /// 196 | /// Attempts to retrive a table from the Lua globals, allowing the user to pass parent and children names in 197 | /// 198 | /// The global table. 199 | /// Key. 200 | public Table GetGlobalTable(params object[] keys) 201 | { 202 | Table result = null; 203 | DynValue tableDyn = GetGlobal (keys); 204 | if (tableDyn != null) 205 | { 206 | if(tableDyn.Type == DataType.Table) 207 | { 208 | result = tableDyn.Table; 209 | } 210 | else 211 | { 212 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua global {0} is not type table, has type {1}", keys, tableDyn.Type.ToString()); 213 | } 214 | } 215 | return result; 216 | } 217 | 218 | /// 219 | /// Return the global table for this vm 220 | /// 221 | /// 222 | public Table GetGlobalsTable() 223 | { 224 | return m_LuaScript.Globals; 225 | } 226 | 227 | /// 228 | /// Attempts to run the lua command passed in 229 | /// 230 | /// 231 | /// Null if an error occured otherwise will return the result of the executed lua code 232 | public DynValue ExecuteString(string command) 233 | { 234 | DynValue result = DynValue.Nil; 235 | 236 | try 237 | { 238 | result = m_LuaScript.DoString(command); 239 | } 240 | catch (InterpreterException ex) 241 | { 242 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage); 243 | } 244 | 245 | return result; 246 | } 247 | 248 | /// 249 | /// Attempts to run the lua script passed in 250 | /// 251 | /// 252 | /// Null if an error occured otherwise will return the result of the executed lua code 253 | public DynValue ExecuteScript(string filePath) 254 | { 255 | DynValue result = DynValue.Nil; 256 | 257 | try 258 | { 259 | result = m_LuaScript.DoFile(filePath); 260 | } 261 | catch (InterpreterException ex) 262 | { 263 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteScript error: {0}", ex.DecoratedMessage); 264 | } 265 | catch (Exception ex) 266 | { 267 | Logger.Log(Channel.Lua, Priority.FatalError, "System ExecuteScript error: {0}", ex.Message); 268 | } 269 | 270 | return result; 271 | } 272 | 273 | /// 274 | /// Attempts to load a lua script 275 | /// 276 | /// 277 | /// Null if an error occured otherwise will return the DynValue of the script. This can be passed to Call() 278 | public DynValue LoadScript(string filePath) 279 | { 280 | DynValue result = DynValue.Nil; 281 | 282 | try 283 | { 284 | result = m_LuaScript.LoadFile(filePath); 285 | } 286 | catch (InterpreterException ex) 287 | { 288 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage); 289 | } 290 | catch (Exception ex) 291 | { 292 | Logger.Log(Channel.Lua, Priority.FatalError, "System ExecuteString error: {0}", ex.Message); 293 | } 294 | 295 | return result; 296 | } 297 | 298 | /// 299 | /// Attemps to load a string containing lua code 300 | /// 301 | /// 302 | /// Null if an error occured otherwise will return the DynValue of the script. This can be passed to Call() 303 | public DynValue LoadString(string luaString) 304 | { 305 | DynValue result = DynValue.Nil; 306 | 307 | try 308 | { 309 | result = m_LuaScript.LoadString(luaString); 310 | } 311 | catch (InterpreterException ex) 312 | { 313 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage); 314 | } 315 | catch (Exception ex) 316 | { 317 | Logger.Log(Channel.Lua, Priority.FatalError, "System ExecuteString error: {0}", ex.Message); 318 | } 319 | 320 | return result; 321 | } 322 | 323 | /// 324 | /// Call a lua function via DynValue 325 | /// 326 | /// 327 | /// 328 | /// Null if call fails of function is invalid, else the result of the function 329 | public DynValue Call(DynValue luaFunc, params object[] args) 330 | { 331 | DynValue result = DynValue.Nil; 332 | 333 | if (luaFunc.IsNotNil() && luaFunc.Type == DataType.Function) 334 | { 335 | try 336 | { 337 | result = m_LuaScript.Call(luaFunc, args); 338 | } 339 | catch (ScriptRuntimeException ex) 340 | { 341 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua Call error: {0}", ex.DecoratedMessage); 342 | } 343 | } 344 | else 345 | { 346 | Logger.Log(Channel.Lua, Priority.FatalError, "Invalid lua function passed to LuaVM::Call"); 347 | } 348 | 349 | return result; 350 | } 351 | 352 | /// 353 | /// Call a lua function via name 354 | /// 355 | /// Function name. 356 | /// Arguments. 357 | public DynValue Call(string functionName, params object[] args) 358 | { 359 | DynValue result = DynValue.Nil; 360 | 361 | if (!string.IsNullOrEmpty(functionName)) 362 | { 363 | DynValue func = GetGlobal(functionName); 364 | if (func.Type == DataType.Function) 365 | { 366 | try 367 | { 368 | result = m_LuaScript.Call(func, args); 369 | } 370 | catch (InterpreterException ex) 371 | { 372 | Logger.Log(Channel.Lua, Priority.FatalError, "Lua error calling function {0}: {1}", functionName, ex.DecoratedMessage); 373 | } 374 | } 375 | else 376 | { 377 | Logger.Log(Channel.Lua, Priority.FatalError, "Failed to find lua function '{0}'", functionName); 378 | } 379 | } 380 | return result; 381 | } 382 | 383 | /// 384 | /// Starts the remote debugger and opens the interface in the users browser 385 | /// 386 | public static void StartRemoteDebugger() 387 | { 388 | if (s_RemoteDebugger == null) 389 | { 390 | s_RemoteDebugger = new MoonSharpVsCodeDebugServer(); 391 | s_RemoteDebugger.Start (); 392 | } 393 | } 394 | 395 | /// 396 | /// Attaches to the remove debugger service 397 | /// 398 | public void AttachDebugger() 399 | { 400 | if(s_RemoteDebugger != null) 401 | { 402 | s_RemoteDebugger.AttachToScript (m_LuaScript, "Lua script"); 403 | } 404 | else 405 | { 406 | Logger.Log(Channel.Lua, Priority.Error, "Tried to attach script to debugger before debugger was started"); 407 | } 408 | } 409 | 410 | /// 411 | /// Return the current script object 412 | /// 413 | public Script GetScriptObject() 414 | { 415 | return m_LuaScript; 416 | } 417 | 418 | /// 419 | /// Create static api list and attach to this VM 420 | /// 421 | private void AttachAPIS() 422 | { 423 | if (m_APITypeList == null) 424 | { 425 | List apiTypeList = new List(); 426 | foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 427 | { 428 | apiTypeList.AddRange(assembly.GetTypes() 429 | .Where(type => type.IsSubclassOf(typeof(LuaAPIBase)))); 430 | } 431 | 432 | m_APITypeList = apiTypeList.ToArray(); 433 | } 434 | 435 | m_APIList = new LuaAPIBase[m_APITypeList.Length]; 436 | 437 | for (int i = 0; i < m_APITypeList.Length; ++i) 438 | { 439 | m_APIList[i] = Activator.CreateInstance(m_APITypeList[i]) as LuaAPIBase; 440 | } 441 | 442 | // Iterate apis and tell them to update this lua vm 443 | foreach (LuaAPIBase api in m_APIList) 444 | { 445 | api.AddAPIToLuaInstance(this); 446 | } 447 | } 448 | 449 | /// 450 | /// Create the reusable enum prime table and attach to this VM 451 | /// 452 | private void AttachLuaEnums() 453 | { 454 | if (m_EnumTables == null) 455 | { 456 | // Create a new prime table 457 | // Prime tables can be shared between scripts 458 | m_EnumTables = DynValue.NewPrimeTable(); 459 | 460 | // Get all enums with the lua attribute 461 | List luaEnumList = new List(); 462 | foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 463 | { 464 | luaEnumList.AddRange((assembly.GetTypes() 465 | .Where(luaEnumType => Attribute.IsDefined(luaEnumType, typeof(LuaApiEnum))))); 466 | } 467 | 468 | foreach (Type enumType in luaEnumList) 469 | { 470 | // Get the attribute 471 | LuaApiEnum apiEnumAttrib = (LuaApiEnum) enumType.GetCustomAttributes(typeof(LuaApiEnum), false)[0]; 472 | 473 | // Create the table for this enum and get a reference to it 474 | m_EnumTables.Table.Set(apiEnumAttrib.name, DynValue.NewPrimeTable()); 475 | Table enumTable = m_EnumTables.Table.Get(apiEnumAttrib.name).Table; 476 | 477 | // Foreach value in the enum list 478 | foreach (var enumValue in Enum.GetValues(enumType)) 479 | { 480 | var memberInfo = enumType.GetMember(enumValue.ToString()); 481 | var attribute = memberInfo[0].GetCustomAttributes(typeof(LuaApiEnumValue), false); 482 | 483 | // Double check they've not been flagged as hidden 484 | if (attribute.Length > 0 && ((LuaApiEnumValue) attribute[0]).hidden) 485 | { 486 | continue; 487 | } 488 | 489 | enumTable.Set(enumValue.ToString(), DynValue.NewNumber((int) enumValue)); 490 | } 491 | } 492 | } 493 | 494 | // Iterate through the enum cache and copy the values into our globals 495 | foreach (var enumPair in m_EnumTables.Table.Pairs) 496 | { 497 | m_LuaScript.Globals.Set(enumPair.Key, enumPair.Value); 498 | } 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /Documentation/LuaDocGenerator.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using UnityEditor; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using System.Linq; 7 | using System.IO; 8 | using System.Text; 9 | using UnityEngine; 10 | 11 | /// 12 | /// Handles generating Lua documentation for varying sources via reflection 13 | /// 14 | public static class LuaDocGenerator 15 | { 16 | /// 17 | /// Info about a Lua API 18 | /// 19 | private class LuaApiInfo 20 | { 21 | public LuaApi Attribute; 22 | public Type Type; 23 | 24 | public readonly List functions = new List(); 25 | public readonly List variables = new List(); 26 | } 27 | 28 | /// 29 | /// Info about a Lua function 30 | /// 31 | private class LuaFunction_Info 32 | { 33 | public LuaApiFunction Attribute; 34 | public MethodInfo MethodInfo; 35 | } 36 | 37 | /// 38 | /// Info about a Lua variable 39 | /// 40 | private class LuaVariableInfo 41 | { 42 | public LuaApiVariable Attribute; 43 | public FieldInfo FieldInfo; 44 | } 45 | 46 | /// 47 | /// Info about a Lua enum 48 | /// 49 | private class LuaEnumInfo 50 | { 51 | public LuaApiEnum Attribute; 52 | public Type ApiType; 53 | public readonly List values = new List(); 54 | } 55 | 56 | /// 57 | /// Info about a Lua enum value 58 | /// 59 | private class LuaEnumValueInfo 60 | { 61 | public LuaApiEnumValue Attribute; 62 | public string StringValue; 63 | } 64 | 65 | /// 66 | /// Grabs information about all the Lua Apis in the code base 67 | /// Å 68 | /// 69 | private static List GetAllApiInfo() 70 | { 71 | List apiTypeList = new List(); 72 | foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 73 | { 74 | apiTypeList.AddRange(assembly.GetTypes() 75 | .Where(type => type.IsSubclassOf(typeof(LuaAPIBase)))); 76 | } 77 | 78 | List apiInfoArray = new List(apiTypeList.Count); 79 | 80 | // Iterate all types deriving from LuaApiBase 81 | foreach (Type apiType in apiTypeList) 82 | { 83 | LuaApi[] apiAttribs = apiType.GetCustomAttributes(typeof(LuaApi), false) as LuaApi[]; 84 | if (apiAttribs != null && apiAttribs.Length > 0) 85 | { 86 | LuaApiInfo apiInfo = new LuaApiInfo 87 | { 88 | Type = apiType, 89 | Attribute = apiAttribs[0] 90 | }; 91 | 92 | // Iterate all methods 93 | foreach(MethodInfo methodType in apiType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)) 94 | { 95 | // Grab the LuaApiFunction if it exists 96 | LuaApiFunction[] functionAttribs = methodType.GetCustomAttributes(typeof(LuaApiFunction), false) as LuaApiFunction[]; 97 | 98 | if (functionAttribs != null && functionAttribs.Length > 0) 99 | { 100 | apiInfo.functions.Add(new LuaFunction_Info 101 | { 102 | Attribute = functionAttribs[0], 103 | MethodInfo = methodType, 104 | }); 105 | } 106 | } 107 | 108 | // Iterate all variables 109 | foreach(FieldInfo fieldInfo in apiType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | 110 | BindingFlags.Public | BindingFlags.Static)) 111 | { 112 | LuaApiVariable[] varaibleAttribs = 113 | fieldInfo.GetCustomAttributes(typeof(LuaApiVariable), false) as LuaApiVariable[]; 114 | 115 | if (varaibleAttribs != null && varaibleAttribs.Length > 0) 116 | { 117 | apiInfo.variables.Add(new LuaVariableInfo{ 118 | Attribute = varaibleAttribs[0], 119 | FieldInfo = fieldInfo, 120 | }); 121 | } 122 | } 123 | 124 | apiInfoArray.Add(apiInfo); 125 | } 126 | } 127 | 128 | return apiInfoArray; 129 | } 130 | 131 | /// 132 | /// Grabs all information about enums that will be bound to Lua 133 | /// 134 | /// 135 | private static List GetAllEnums() 136 | { 137 | List luaEnumList = new List(); 138 | foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 139 | { 140 | luaEnumList.AddRange((assembly.GetTypes() 141 | .Where(luaEnumType => Attribute.IsDefined(luaEnumType, typeof(LuaApiEnum))))); 142 | } 143 | 144 | List result = new List(luaEnumList.Count); 145 | 146 | foreach (Type enumType in luaEnumList) 147 | { 148 | LuaEnumInfo enumInfo = new LuaEnumInfo 149 | { 150 | ApiType = enumType, 151 | Attribute = (LuaApiEnum) enumType.GetCustomAttributes(typeof(LuaApiEnum), false)[0], 152 | }; 153 | 154 | foreach (var enumVal in Enum.GetValues(enumType)) 155 | { 156 | var valueInfo = enumType.GetMember(enumVal.ToString()); 157 | var attribs = valueInfo[0].GetCustomAttributes(typeof(LuaApiEnumValue), false); 158 | enumInfo.values.Add(new LuaEnumValueInfo 159 | { 160 | Attribute = (LuaApiEnumValue)(attribs.Length > 0 ? attribs[0] : null), 161 | StringValue = enumVal.ToString(), 162 | }); 163 | } 164 | 165 | result.Add(enumInfo); 166 | } 167 | 168 | return result; 169 | } 170 | 171 | /// 172 | /// Generate MediaWiki pages for all Lua apis and enums 173 | /// 174 | [MenuItem("Lua/Docs/Generate MediaWiki Docs")] 175 | private static void GenerateMediaWikiDocs() 176 | { 177 | Logger.Log (Channel.Lua, "Beginning Lua doc generation for MediaWiki"); 178 | 179 | string documentationPath = 180 | EditorUtility.OpenFolderPanel("Choose a location to place wiki files", string.Empty, string.Empty); 181 | 182 | if (!string.IsNullOrEmpty(documentationPath)) 183 | { 184 | // Iterate all types deriving from LuaApiBase 185 | foreach (LuaApiInfo api in GetAllApiInfo()) 186 | { 187 | // Write out the header for the api type 188 | LuaApi luaApiDetails = api.Attribute; 189 | 190 | string apiDocPath = string.Format("{0}/{1}.mediawiki", documentationPath, luaApiDetails.luaName); 191 | 192 | StreamWriter documentation = File.CreateText(apiDocPath); 193 | 194 | Logger.Log(Channel.Lua, "Generating documentation for api: {0}", luaApiDetails.luaName); 195 | 196 | documentation.WriteLine("= {0} =", luaApiDetails.luaName); 197 | 198 | if (!string.IsNullOrEmpty(luaApiDetails.description)) 199 | { 200 | documentation.WriteLine("== Description =="); 201 | documentation.WriteLine(luaApiDetails.description); 202 | } 203 | if (!string.IsNullOrEmpty(luaApiDetails.notes)) 204 | { 205 | documentation.WriteLine("== Notes =="); 206 | documentation.WriteLine(luaApiDetails.notes); 207 | } 208 | 209 | 210 | documentation.WriteLine("== Functions =="); 211 | 212 | // Iterate all methods 213 | foreach (LuaFunction_Info method in api.functions) 214 | { 215 | // Grab the LuaApiFunction if it exists 216 | LuaApiFunction luaMethodDetails = method.Attribute; 217 | 218 | Logger.Log(Channel.Lua, "\t {0}", luaMethodDetails.name); 219 | 220 | documentation.WriteLine("=== {0} ===", luaMethodDetails.name); 221 | 222 | string functionSig = GetCleanFunctionSignature(method.MethodInfo, luaMethodDetails, luaApiDetails); 223 | 224 | documentation.WriteLine("{0}", functionSig); 225 | 226 | bool hasParams = method.MethodInfo.GetParameters().Length != 0; 227 | 228 | if (hasParams) 229 | { 230 | documentation.WriteLine("'''Expected parameter types'''"); 231 | documentation.WriteLine("{| class=\"wikitable\""); 232 | } 233 | 234 | // Expected param types 235 | foreach (ParameterInfo param in method.MethodInfo.GetParameters()) 236 | { 237 | documentation.WriteLine("|-"); 238 | documentation.WriteLine("| {0} || {1}", param.Name, GetCleanTypeName(param.ParameterType)); 239 | } 240 | 241 | if (hasParams) 242 | { 243 | documentation.WriteLine("|}"); 244 | } 245 | 246 | documentation.WriteLine("'''Description''': {0}\n", luaMethodDetails.description); 247 | 248 | // Use custom return description if exists, else generate one 249 | documentation.WriteLine("'''Returns''': {0}\n", 250 | !string.IsNullOrEmpty(luaMethodDetails.returns) 251 | ? luaMethodDetails.returns 252 | : GetCleanTypeName(method.MethodInfo.ReturnType)); 253 | 254 | if (!string.IsNullOrEmpty(luaMethodDetails.notes)) 255 | { 256 | documentation.WriteLine("'''Notes''': {0}", 257 | luaMethodDetails.notes); 258 | } 259 | 260 | if (!string.IsNullOrEmpty(luaMethodDetails.warning)) 261 | { 262 | documentation.WriteLine("'''Warning''': {0}", 263 | luaMethodDetails.warning); 264 | } 265 | 266 | if (!string.IsNullOrEmpty(luaMethodDetails.success)) 267 | { 268 | documentation.WriteLine("'''Tip''': {0}", 269 | luaMethodDetails.success); 270 | } 271 | } 272 | 273 | bool wroteTitle = false; 274 | foreach (LuaVariableInfo fieldInfo in api.variables) 275 | { 276 | if (!wroteTitle) 277 | { 278 | documentation.WriteLine("== Variables =="); 279 | wroteTitle = true; 280 | } 281 | 282 | LuaApiVariable luaVariableDetails = fieldInfo.Attribute; 283 | 284 | documentation.WriteLine("=== {0} ===", luaVariableDetails.name); 285 | 286 | string varibleSig = string.Format("{0}.{1}", luaApiDetails.luaName, luaVariableDetails.name); 287 | documentation.WriteLine("{0}", varibleSig); 288 | 289 | documentation.WriteLine("'''Description''': {0}\n", luaVariableDetails.description); 290 | } 291 | 292 | // Add time stamp 293 | documentation.WriteLine("\n\n'''Docs last hacked together on''': {0:dd/MM/yyyy H:mm}", DateTime.Now); 294 | 295 | documentation.Close(); 296 | } 297 | 298 | // TODO Enum docs 299 | 300 | string enumDocPath = string.Format("{0}/Constants.mediawiki", documentationPath); 301 | 302 | StreamWriter enumDocumentation = File.CreateText(enumDocPath); 303 | 304 | enumDocumentation.WriteLine("= Constants ="); 305 | 306 | enumDocumentation.WriteLine("== Enums =="); 307 | foreach (LuaEnumInfo enumInfo in GetAllEnums()) 308 | { 309 | enumDocumentation.WriteLine("=== {0} ===", enumInfo.Attribute.name); 310 | if (!string.IsNullOrEmpty(enumInfo.Attribute.description)) 311 | { 312 | enumDocumentation.WriteLine(enumInfo.Attribute.description); 313 | } 314 | 315 | enumDocumentation.WriteLine("{| class=\"wikitable\""); 316 | enumDocumentation.WriteLine("|-"); 317 | enumDocumentation.WriteLine("! Usage !! Description"); 318 | 319 | foreach (LuaEnumValueInfo value in enumInfo.values) 320 | { 321 | if (value.Attribute != null && value.Attribute.hidden) 322 | { 323 | continue; 324 | } 325 | enumDocumentation.WriteLine("|-"); 326 | enumDocumentation.WriteLine("| {0}.{1} || {2}", 327 | enumInfo.Attribute.name, 328 | value.StringValue, 329 | value.Attribute != null ? value.Attribute.description : string.Empty); 330 | } 331 | 332 | enumDocumentation.WriteLine("|}"); 333 | } 334 | 335 | enumDocumentation.Close(); 336 | } 337 | 338 | Logger.Log (Channel.Lua, "Completed Lua doc generation"); 339 | } 340 | 341 | private class VSCodeSnippet 342 | { 343 | public string prefix; 344 | public string[] body; 345 | public string description; 346 | } 347 | 348 | [MenuItem("Lua/Docs/Generate VSCode Snippets")] 349 | public static void GenerateVSCodeSnippets() 350 | { 351 | StringBuilder snippets = new StringBuilder(); 352 | VSCodeSnippet snippet = new VSCodeSnippet {body = new string[1]}; 353 | 354 | foreach (LuaApiInfo api in GetAllApiInfo()) 355 | { 356 | // Write out the header for the api type 357 | LuaApi luaApiDetails = api.Attribute; 358 | 359 | // Iterate all methods 360 | foreach (LuaFunction_Info method in api.functions) 361 | { 362 | // Grab the LuaApiFunction if it exists 363 | LuaApiFunction luaMethodDetails = method.Attribute; 364 | 365 | snippet.prefix = string.Format("{0}.{1}", luaApiDetails.luaName, luaMethodDetails.name); 366 | 367 | string paramString = string.Empty; 368 | 369 | var paramArray = method.MethodInfo.GetParameters(); 370 | for (int i = 0; i < paramArray.Length; i++) 371 | { 372 | var param = paramArray[i]; 373 | 374 | paramString += string.Format("${{{0}:{1}}}, ", i, param.Name); 375 | } 376 | 377 | if (!string.IsNullOrEmpty(paramString)) 378 | { 379 | paramString = paramString.Remove(paramString.Length - 2, 2); 380 | } 381 | 382 | snippet.body[0] = string.Format("{0}({1})", snippet.prefix, paramString); 383 | 384 | snippet.description = luaMethodDetails.description; 385 | 386 | string finalBlock = string.Format("\"{0}\": {1},", snippet.prefix, JsonUtility.ToJson(snippet, true)); 387 | 388 | snippets.AppendLine(finalBlock); 389 | } 390 | 391 | foreach (LuaVariableInfo fieldInfo in api.variables) 392 | { 393 | LuaApiVariable luaVariableDetails = fieldInfo.Attribute; 394 | 395 | snippet.prefix = string.Format("{0}.{1}", luaApiDetails.luaName, luaVariableDetails.name); 396 | snippet.body[0] = snippet.prefix; 397 | snippet.description = luaVariableDetails.description; 398 | 399 | string finalBlock = string.Format("\"{0}\" : {1},", snippet.prefix, JsonUtility.ToJson(snippet, true)); 400 | 401 | snippets.AppendLine(finalBlock); 402 | } 403 | } 404 | 405 | EditorGUIUtility.systemCopyBuffer = snippets.ToString(); 406 | } 407 | 408 | private class AtomSnippet 409 | { 410 | public string Prefix; 411 | public string Body; 412 | public string Description; 413 | public string DescriptionMoreUrl; 414 | 415 | private static string ToLiteral(string input) 416 | { 417 | input = input.Replace("'", @"\'"); 418 | input = input.Replace("\"", @""""); 419 | return input; 420 | } 421 | 422 | public string ConstructCSONString() 423 | { 424 | return string.Format(@" 425 | '{0}': 426 | 'prefix': '{0}' 427 | 'body': '{1}' 428 | 'description' : '{2}' 429 | 'rightLabelHTML': 'Lua' 430 | 'descriptionMoreURL' : '{3}'", 431 | Prefix, 432 | ToLiteral(Body), 433 | ToLiteral(Description), 434 | DescriptionMoreUrl); 435 | 436 | } 437 | } 438 | 439 | [MenuItem("Lua/Docs/Generate Atom Snippets")] 440 | public static void GenerateAtomSnippets() 441 | { 442 | StringBuilder snippets = new StringBuilder(); 443 | AtomSnippet snippet = new AtomSnippet(); 444 | 445 | snippets.AppendLine("'.source.lua':"); 446 | 447 | // Iterate all types deriving from LuaApiBase 448 | foreach (LuaApiInfo api in GetAllApiInfo()) 449 | { 450 | // Write out the header for the api type 451 | LuaApi luaApiDetails = api.Attribute; 452 | 453 | // Iterate all methods 454 | foreach (LuaFunction_Info method in api.functions) 455 | { 456 | // Grab the LuaApiFunction if it exists 457 | LuaApiFunction luaMethodDetails = method.Attribute; 458 | 459 | snippet.Prefix = string.Format("{0}.{1}", luaApiDetails.luaName, luaMethodDetails.name); 460 | 461 | string paramString = string.Empty; 462 | 463 | var paramArray = method.MethodInfo.GetParameters(); 464 | for (int i = 0; i < paramArray.Length; i++) 465 | { 466 | var param = paramArray[i]; 467 | 468 | paramString += string.Format("${{{0}:{1}}}, ", i, param.Name); 469 | } 470 | 471 | if (!string.IsNullOrEmpty(paramString)) 472 | { 473 | paramString = paramString.Remove(paramString.Length - 2, 2); 474 | } 475 | 476 | snippet.Body = string.Format("{0}({1})", snippet.Prefix, paramString); 477 | 478 | snippet.Description = luaMethodDetails.description; 479 | 480 | snippets.AppendLine(snippet.ConstructCSONString()); 481 | } 482 | 483 | foreach (LuaVariableInfo fieldInfo in api.variables) 484 | { 485 | LuaApiVariable luaVariableDetails = fieldInfo.Attribute; 486 | 487 | snippet.Prefix = string.Format("{0}.{1}", luaApiDetails.luaName, luaVariableDetails.name); 488 | snippet.Body = snippet.Prefix; 489 | snippet.Description = luaVariableDetails.description; 490 | 491 | snippets.AppendLine(snippet.ConstructCSONString()); 492 | } 493 | } 494 | 495 | foreach (LuaEnumInfo enumInfo in GetAllEnums()) 496 | { 497 | foreach (LuaEnumValueInfo enumValueInfo in enumInfo.values) 498 | { 499 | if (enumValueInfo.Attribute != null && enumValueInfo.Attribute.hidden) 500 | { 501 | continue; 502 | } 503 | 504 | snippet.Prefix = string.Format("{0}.{1}", enumInfo.Attribute.name, enumValueInfo.StringValue); 505 | snippet.Body = snippet.Prefix; 506 | snippet.Description = enumValueInfo.Attribute != null ? enumValueInfo.Attribute.description : string.Empty; 507 | 508 | snippet.DescriptionMoreUrl = string.Empty; 509 | 510 | snippets.AppendLine(snippet.ConstructCSONString()); 511 | } 512 | } 513 | 514 | EditorGUIUtility.systemCopyBuffer = snippets.ToString(); 515 | } 516 | 517 | /// 518 | /// Gets a clean function signature 519 | /// 520 | /// The clean function signature. 521 | /// Method. 522 | /// Function Attribute. 523 | /// API Attribute. 524 | private static string GetCleanFunctionSignature(MethodInfo method, LuaApiFunction functionAttrib, LuaApi apiAttrib) 525 | { 526 | string apiName = apiAttrib.luaName; 527 | string functionName = functionAttrib.name; 528 | string paramsString = string.Empty; 529 | 530 | foreach (ParameterInfo param in method.GetParameters()) 531 | { 532 | paramsString += string.Format ("{0}, ", param.Name); 533 | } 534 | 535 | // Clean up last comma and space 536 | if (!string.IsNullOrEmpty (paramsString)) 537 | { 538 | paramsString = paramsString.Remove (paramsString.Length - 2, 2); 539 | } 540 | 541 | return string.Format ("{0}.{1}({2})", apiName, functionName, paramsString); 542 | } 543 | 544 | /// 545 | /// Gets a documentation friendly name for a type. 546 | /// 547 | /// The clean type name. 548 | /// Type. 549 | private static string GetCleanTypeName(Type type) 550 | { 551 | string result; 552 | 553 | // Ideally this would be a switch, but c# won't allow system.type in a switch 554 | if (type == typeof(void)) 555 | { 556 | result = "Nothing"; 557 | } 558 | else if (type == typeof(float?)) 559 | { 560 | result = "number (optional)"; 561 | } 562 | else if (type == typeof(float[])) 563 | { 564 | result = "Table of numbers"; 565 | } 566 | else if (type == typeof(int) || 567 | type == typeof(uint) || 568 | type == typeof(float)) 569 | { 570 | result = "number"; 571 | } 572 | else if (type == typeof(string)) 573 | { 574 | result = "string"; 575 | } 576 | else if (type == typeof(bool)) 577 | { 578 | result = "bool"; 579 | } 580 | else if (type == typeof(MoonSharp.Interpreter.DynValue)) 581 | { 582 | result = "Lua Type"; 583 | } 584 | else if (type == typeof(MoonSharp.Interpreter.Table)) 585 | { 586 | result = "Lua Table"; 587 | } 588 | else if (type == typeof(bool?)) 589 | { 590 | result = "bool (optional)"; 591 | } 592 | else 593 | { 594 | Logger.Log (Channel.Lua, Priority.Error, "Failed to convert type {0} to cleaner name", type.ToString ()); 595 | result = type.ToString (); 596 | } 597 | 598 | return result; 599 | } 600 | 601 | } 602 | #endif // UNITY_EDITOR --------------------------------------------------------------------------------