You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
385 lines
13 KiB
385 lines
13 KiB
9 years ago
|
using UnityEngine;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System;
|
||
|
using System.Linq;
|
||
|
using System.Diagnostics;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using MoonSharp.Interpreter;
|
||
|
using MoonSharp.Interpreter.Interop;
|
||
|
using MoonSharp.Interpreter.Loaders;
|
||
|
using MoonSharp.RemoteDebugger;
|
||
|
|
||
|
namespace Fungus
|
||
|
{
|
||
9 years ago
|
public class LuaEnvironment : MonoBehaviour
|
||
9 years ago
|
{
|
||
9 years ago
|
/// <summary>
|
||
|
/// Helper class used to extend the initialization behavior of LuaEnvironment.
|
||
|
/// </summary>
|
||
|
public abstract class Initializer : MonoBehaviour
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Called when the LuaEnvironment is initializing.
|
||
|
/// </summary>
|
||
|
public abstract void Initialize();
|
||
|
}
|
||
|
|
||
9 years ago
|
/// <summary>
|
||
|
/// Custom file loader for MoonSharp that loads in all Lua scripts in the project.
|
||
|
/// Scripts must be placed in a Resources/Lua directory.
|
||
|
/// </summary>
|
||
9 years ago
|
protected class LuaScriptLoader : ScriptLoaderBase
|
||
9 years ago
|
{
|
||
|
// Give the script loader access to the list of accessible Lua Modules.
|
||
|
private IEnumerable<TextAsset> luaScripts;
|
||
9 years ago
|
public LuaScriptLoader(IEnumerable<TextAsset> luaScripts)
|
||
9 years ago
|
{
|
||
|
this.luaScripts = luaScripts;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
// Bypasses the standard path resolution logic for require.
|
||
|
/// </summary>
|
||
|
protected override string ResolveModuleName(string modname, string[] paths)
|
||
|
{
|
||
|
return modname;
|
||
|
}
|
||
|
|
||
|
public override object LoadFile(string file, Table globalContext)
|
||
|
{
|
||
|
foreach (TextAsset luaScript in luaScripts)
|
||
|
{
|
||
|
// Case insensitive string compare to allow standard Lua naming conventions in code
|
||
|
if (String.Compare(luaScript.name, file, true) == 0)
|
||
|
{
|
||
|
return luaScript.text;
|
||
|
}
|
||
|
}
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
public override bool ScriptFileExists(string name)
|
||
|
{
|
||
|
foreach (TextAsset luaScript in luaScripts)
|
||
|
{
|
||
|
// Case insensitive string compare to allow standard Lua naming conventions in code
|
||
|
if (String.Compare(luaScript.name, name, true) == 0)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
9 years ago
|
/// <summary>
|
||
9 years ago
|
/// Returns the first Lua Environment found in the scene, or creates one if none exists.
|
||
9 years ago
|
/// This is a slow operation, call it once at startup and cache the returned value.
|
||
|
/// </summary>
|
||
9 years ago
|
public static LuaEnvironment GetLua()
|
||
9 years ago
|
{
|
||
9 years ago
|
LuaEnvironment luaEnvironment = GameObject.FindObjectOfType<LuaEnvironment>();
|
||
|
if (luaEnvironment == null)
|
||
9 years ago
|
{
|
||
9 years ago
|
GameObject prefab = Resources.Load<GameObject>("Prefabs/LuaEnvironment");
|
||
9 years ago
|
if (prefab != null)
|
||
|
{
|
||
|
GameObject go = Instantiate(prefab) as GameObject;
|
||
9 years ago
|
go.name = "LuaEnvironment";
|
||
|
luaEnvironment = go.GetComponent<LuaEnvironment>();
|
||
9 years ago
|
}
|
||
|
}
|
||
9 years ago
|
return luaEnvironment;
|
||
9 years ago
|
}
|
||
|
|
||
9 years ago
|
protected Script interpreter;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the MoonSharp interpreter instance used to run Lua code.
|
||
|
/// </summary>
|
||
|
public Script Interpreter { get { return interpreter; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Launches the remote Lua debugger in your browser and breaks execution at the first executed Lua command.
|
||
|
/// </summary>
|
||
|
[Tooltip("Launches the remote Lua debugger in your browser and breaks execution at the first executed Lua command.")]
|
||
|
public bool remoteDebugger = false;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Instance of remote debugging service when debugging option is enabled.
|
||
|
/// </summary>
|
||
|
protected RemoteDebuggerService remoteDebuggerService;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Flag used to avoid startup dependency issues.
|
||
|
/// </summary>
|
||
|
protected bool initialised = false;
|
||
|
|
||
9 years ago
|
protected virtual void Start()
|
||
9 years ago
|
{
|
||
9 years ago
|
InitEnvironment();
|
||
9 years ago
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialise the Lua interpreter so we can start running Lua code.
|
||
|
/// </summary>
|
||
9 years ago
|
public virtual void InitEnvironment()
|
||
9 years ago
|
{
|
||
|
if (initialised)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Script.DefaultOptions.DebugPrint = (s) => { UnityEngine.Debug.Log(s); };
|
||
|
|
||
|
// In some use cases (e.g. downloadable Lua files) some Lua modules can pose a potential security risk.
|
||
9 years ago
|
// You can restrict which core lua modules are available here if needed. See the MoonSharp documentation for details.
|
||
9 years ago
|
interpreter = new Script(CoreModules.Preset_Complete);
|
||
|
|
||
9 years ago
|
// Load all Lua scripts in the project
|
||
9 years ago
|
InitLuaScriptFiles();
|
||
9 years ago
|
|
||
9 years ago
|
// Initialize any attached initializer components (e.g. LuaUtils)
|
||
|
Initializer[] initializers = GetComponentsInChildren<Initializer>();
|
||
|
foreach (Initializer initializer in initializers)
|
||
9 years ago
|
{
|
||
9 years ago
|
initializer.Initialize();
|
||
9 years ago
|
}
|
||
9 years ago
|
|
||
|
if (remoteDebugger)
|
||
|
{
|
||
|
ActivateRemoteDebugger(interpreter);
|
||
|
}
|
||
|
|
||
|
initialised = true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Register all Lua files in the project so they can be accessed at runtime.
|
||
|
/// </summary>
|
||
9 years ago
|
protected virtual void InitLuaScriptFiles()
|
||
9 years ago
|
{
|
||
|
object[] result = Resources.LoadAll("Lua", typeof(TextAsset));
|
||
9 years ago
|
interpreter.Options.ScriptLoader = new LuaScriptLoader(result.OfType<TextAsset>());
|
||
9 years ago
|
}
|
||
|
|
||
9 years ago
|
/// <summary>
|
||
|
/// Register a type given it's assembly qualified name.
|
||
|
/// </summary>
|
||
9 years ago
|
public static void RegisterType(string typeName, bool extensionType = false)
|
||
9 years ago
|
{
|
||
9 years ago
|
System.Type t = System.Type.GetType(typeName);
|
||
9 years ago
|
if (t == null)
|
||
|
{
|
||
|
UnityEngine.Debug.LogWarning("Type not found: " + typeName);
|
||
|
return;
|
||
|
}
|
||
|
|
||
9 years ago
|
// Registering System.Object breaks MoonSharp's automated conversion of Lists and Dictionaries to Lua tables.
|
||
|
if (t == typeof(System.Object))
|
||
9 years ago
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!UserData.IsTypeRegistered(t))
|
||
|
{
|
||
9 years ago
|
if (extensionType)
|
||
|
{
|
||
|
UserData.RegisterExtensionType(t);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UserData.RegisterType(t);
|
||
|
}
|
||
9 years ago
|
}
|
||
|
}
|
||
9 years ago
|
|
||
|
/// <summary>
|
||
|
/// Load and run a string containing Lua script. May be run as a coroutine.
|
||
|
/// <param name="luaString">The Lua code to be run.</param>
|
||
|
/// <param name="friendlyName">A descriptive name to be used in error reports.</param>
|
||
|
/// <param name="runAsCoroutine">Run the Lua code as a coroutine to support asynchronous operations.</param>
|
||
|
/// <param name="onComplete">Method to callback when the Lua code finishes exection. Supports return parameters.</param>
|
||
|
/// </summary>
|
||
9 years ago
|
public virtual void DoLuaString(string luaString, string friendlyName, bool runAsCoroutine, Action<DynValue> onComplete = null)
|
||
9 years ago
|
{
|
||
9 years ago
|
Closure fn = LoadLuaString(luaString, friendlyName);
|
||
9 years ago
|
|
||
9 years ago
|
if (fn == null)
|
||
9 years ago
|
{
|
||
|
if (onComplete != null)
|
||
|
{
|
||
|
onComplete(null);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Execute the Lua script
|
||
|
if (runAsCoroutine)
|
||
|
{
|
||
9 years ago
|
StartCoroutine(RunLuaCoroutineInternal(fn, luaString, onComplete));
|
||
9 years ago
|
}
|
||
|
else
|
||
|
{
|
||
|
DynValue returnValue = null;
|
||
|
try
|
||
|
{
|
||
9 years ago
|
returnValue = fn.Call();
|
||
9 years ago
|
}
|
||
|
catch (InterpreterException ex)
|
||
|
{
|
||
9 years ago
|
LogException(ex.DecoratedMessage, luaString);
|
||
9 years ago
|
}
|
||
|
|
||
|
if (onComplete != null)
|
||
|
{
|
||
|
onComplete(returnValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
9 years ago
|
/// <summary>
|
||
|
/// Loads and compiles a string containing Lua script, returning a closure (Lua function) which can be executed later.
|
||
|
/// <param name="luaString">The Lua code to be run.</param>
|
||
|
/// <param name="friendlyName">A descriptive name to be used in error reports.</param>
|
||
|
/// </summary>
|
||
|
public virtual Closure LoadLuaString(string luaString, string friendlyName)
|
||
|
{
|
||
|
InitEnvironment();
|
||
|
|
||
|
// Load the Lua script
|
||
|
DynValue res = null;
|
||
|
try
|
||
|
{
|
||
|
res = interpreter.LoadString(luaString, null, friendlyName);
|
||
|
}
|
||
|
catch (InterpreterException ex)
|
||
|
{
|
||
|
LogException(ex.DecoratedMessage, luaString);
|
||
|
}
|
||
|
|
||
|
if (res.Type != DataType.Function)
|
||
|
{
|
||
|
UnityEngine.Debug.LogError("Failed to create Lua function from Lua string");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return res.Function;
|
||
|
}
|
||
|
|
||
9 years ago
|
/// <summary>
|
||
|
/// Starts a Unity coroutine which updates a Lua coroutine each frame.
|
||
|
/// <param name="closure">A MoonSharp closure object representing a function.</param>
|
||
|
/// <param name="debugInfo">Debug text to display if an exception occurs (usually the Lua code that is being executed).</param>
|
||
|
/// <param name="onComplete">A delegate method that is called when the coroutine completes. Includes return parameter.</param>
|
||
|
/// </summary>
|
||
9 years ago
|
public virtual void RunLuaCoroutine(Closure closure, string debugInfo, Action<DynValue> onComplete = null)
|
||
9 years ago
|
{
|
||
|
StartCoroutine(RunLuaCoroutineInternal(closure, debugInfo, onComplete));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// A Unity coroutine method which updates a Lua coroutine each frame.
|
||
|
/// <param name="closure">A MoonSharp closure object representing a function.</param>
|
||
|
/// <param name="debugInfo">Debug text to display if an exception occurs (usually the Lua code that is being executed).</param>
|
||
|
/// <param name="onComplete">A delegate method that is called when the coroutine completes. Includes return parameter.</param>
|
||
|
/// </summary>
|
||
9 years ago
|
protected virtual IEnumerator RunLuaCoroutineInternal(Closure closure, string debugInfo, Action<DynValue> onComplete = null)
|
||
9 years ago
|
{
|
||
|
DynValue co = interpreter.CreateCoroutine(closure);
|
||
|
|
||
|
DynValue returnValue = null;
|
||
|
while (co.Coroutine.State != CoroutineState.Dead)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
returnValue = co.Coroutine.Resume();
|
||
|
}
|
||
|
catch (InterpreterException ex)
|
||
|
{
|
||
9 years ago
|
LogException(ex.DecoratedMessage, debugInfo);
|
||
9 years ago
|
}
|
||
|
|
||
|
yield return null;
|
||
|
}
|
||
|
|
||
|
if (onComplete != null)
|
||
|
{
|
||
|
onComplete(returnValue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Start a Unity coroutine from a Lua call.
|
||
|
/// </summary>
|
||
9 years ago
|
public virtual Task RunUnityCoroutine(IEnumerator coroutine)
|
||
9 years ago
|
{
|
||
|
if (coroutine == null)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// We use the Task class so we can poll the coroutine to check if it has finished.
|
||
|
// Standard Unity coroutines don't support this check.
|
||
|
return new Task(RunUnityCoroutineImpl(coroutine));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Starts a standard Unity coroutine.
|
||
9 years ago
|
/// The coroutine is managed by the LuaEnvironment monobehavior, so you can call StopAllCoroutines to
|
||
9 years ago
|
/// stop all active coroutines later.
|
||
|
/// </summary>
|
||
|
protected virtual IEnumerator RunUnityCoroutineImpl(IEnumerator coroutine)
|
||
|
{
|
||
|
if (coroutine == null)
|
||
|
{
|
||
|
UnityEngine.Debug.LogWarning("Coroutine must not be null");
|
||
|
yield break;
|
||
|
}
|
||
|
|
||
|
yield return StartCoroutine(coroutine);
|
||
|
}
|
||
|
|
||
|
protected virtual void ActivateRemoteDebugger(Script script)
|
||
|
{
|
||
|
if (remoteDebuggerService == null)
|
||
|
{
|
||
|
remoteDebuggerService = new RemoteDebuggerService();
|
||
|
|
||
|
// the last boolean is to specify if the script is free to run
|
||
|
// after attachment, defaults to false
|
||
|
remoteDebuggerService.Attach(script, gameObject.name, false);
|
||
|
}
|
||
|
|
||
|
// start the web-browser at the correct url. Replace this or just
|
||
|
// pass the url to the user in some way.
|
||
|
Process.Start(remoteDebuggerService.HttpUrlStringLocalHost);
|
||
|
}
|
||
9 years ago
|
|
||
|
/// <summary>
|
||
|
/// Writes a MoonSharp exception to the debug log in a helpful format.
|
||
|
/// </summary>
|
||
|
/// <param name="decoratedMessage">Decorated message from a MoonSharp exception</param>
|
||
|
/// <param name="debugInfo">Debug info, usually the Lua script that was running.</param>
|
||
|
public static void LogException(string decoratedMessage, string debugInfo)
|
||
|
{
|
||
|
string output = decoratedMessage + "\n";
|
||
|
|
||
|
char[] separators = { '\r', '\n' };
|
||
|
string[] lines = debugInfo.Split(separators, StringSplitOptions.None);
|
||
|
|
||
|
// Show line numbers for script listing
|
||
|
int count = 1;
|
||
|
foreach (string line in lines)
|
||
|
{
|
||
|
output += count.ToString() + ": " + line + "\n";
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
UnityEngine.Debug.LogError(output);
|
||
|
}
|
||
9 years ago
|
}
|
||
|
|
||
|
}
|