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
{
public class LuaEnvironment : MonoBehaviour
{
///
/// Helper class used to extend the initialization behavior of LuaEnvironment.
///
public abstract class Initializer : MonoBehaviour
{
///
/// Called when the LuaEnvironment is initializing.
///
public abstract void Initialize();
}
///
/// Custom file loader for MoonSharp that loads in all Lua scripts in the project.
/// Scripts must be placed in a Resources/Lua directory.
///
protected class LuaScriptLoader : ScriptLoaderBase
{
// Give the script loader access to the list of accessible Lua Modules.
private IEnumerable luaScripts;
public LuaScriptLoader(IEnumerable luaScripts)
{
this.luaScripts = luaScripts;
}
///
// Bypasses the standard path resolution logic for require.
///
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;
}
}
///
/// Returns the first Lua Environment found in the scene, or creates one if none exists.
/// This is a slow operation, call it once at startup and cache the returned value.
///
public static LuaEnvironment GetLua()
{
LuaEnvironment luaEnvironment = GameObject.FindObjectOfType();
if (luaEnvironment == null)
{
GameObject prefab = Resources.Load("Prefabs/LuaEnvironment");
if (prefab != null)
{
GameObject go = Instantiate(prefab) as GameObject;
go.name = "LuaEnvironment";
luaEnvironment = go.GetComponent();
}
}
return luaEnvironment;
}
protected Script interpreter;
///
/// Returns the MoonSharp interpreter instance used to run Lua code.
///
public Script Interpreter { get { return interpreter; } }
///
/// Launches the remote Lua debugger in your browser and breaks execution at the first executed Lua command.
///
[Tooltip("Launches the remote Lua debugger in your browser and breaks execution at the first executed Lua command. Standalone platform only.")]
public bool remoteDebugger = false;
///
/// Instance of remote debugging service when debugging option is enabled.
///
protected RemoteDebuggerService remoteDebuggerService;
///
/// Flag used to avoid startup dependency issues.
///
protected bool initialised = false;
protected virtual void Start()
{
InitEnvironment();
}
///
/// Initialise the Lua interpreter so we can start running Lua code.
///
public virtual void InitEnvironment()
{
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.
// You can restrict which core lua modules are available here if needed. See the MoonSharp documentation for details.
interpreter = new Script(CoreModules.Preset_Complete);
// Load all Lua scripts in the project
InitLuaScriptFiles();
// Initialize any attached initializer components (e.g. LuaUtils)
Initializer[] initializers = GetComponentsInChildren();
foreach (Initializer initializer in initializers)
{
initializer.Initialize();
}
if (remoteDebugger)
{
ActivateRemoteDebugger(interpreter);
}
initialised = true;
}
///
/// Register all Lua files in the project so they can be accessed at runtime.
///
protected virtual void InitLuaScriptFiles()
{
object[] result = Resources.LoadAll("Lua", typeof(TextAsset));
interpreter.Options.ScriptLoader = new LuaScriptLoader(result.OfType());
}
///
/// Register a type given it's assembly qualified name.
///
public static void RegisterType(string typeName, bool extensionType = false)
{
System.Type t = System.Type.GetType(typeName);
if (t == null)
{
UnityEngine.Debug.LogWarning("Type not found: " + typeName);
return;
}
// Registering System.Object breaks MoonSharp's automated conversion of Lists and Dictionaries to Lua tables.
if (t == typeof(System.Object))
{
return;
}
if (!UserData.IsTypeRegistered(t))
{
if (extensionType)
{
UserData.RegisterExtensionType(t);
}
else
{
UserData.RegisterType(t);
}
}
}
///
/// Loads and compiles a string containing Lua script, returning a closure (Lua function) which can be executed later.
/// The Lua code to be run.
/// A descriptive name to be used in error reports.
///
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;
}
///
/// Load and run a previously compiled Lua script. May be run as a coroutine.
/// A previously compiled Lua function.
/// The Lua code to display in case of an error.
/// Run the Lua code as a coroutine to support asynchronous operations.
/// Method to callback when the Lua code finishes exection. Supports return parameters.
///
public virtual void RunLuaFunction(Closure fn, string luaString, bool runAsCoroutine, Action onComplete = null)
{
if (fn == null)
{
if (onComplete != null)
{
onComplete(null);
}
return;
}
// Execute the Lua script
if (runAsCoroutine)
{
StartCoroutine(RunLuaCoroutine(fn, luaString, onComplete));
}
else
{
DynValue returnValue = null;
try
{
returnValue = fn.Call();
}
catch (InterpreterException ex)
{
LogException(ex.DecoratedMessage, luaString);
}
if (onComplete != null)
{
onComplete(returnValue);
}
}
}
///
/// Load and run a string containing Lua script. May be run as a coroutine.
/// The Lua code to be run.
/// A descriptive name to be used in error reports.
/// Run the Lua code as a coroutine to support asynchronous operations.
/// Method to callback when the Lua code finishes exection. Supports return parameters.
///
public virtual void DoLuaString(string luaString, string friendlyName, bool runAsCoroutine, Action onComplete = null)
{
Closure fn = LoadLuaString(luaString, friendlyName);
RunLuaFunction(fn, luaString, runAsCoroutine, onComplete);
}
///
/// A Unity coroutine method which updates a Lua coroutine each frame.
/// A MoonSharp closure object representing a function.
/// Debug text to display if an exception occurs (usually the Lua code that is being executed).
/// A delegate method that is called when the coroutine completes. Includes return parameter.
///
protected virtual IEnumerator RunLuaCoroutine(Closure closure, string debugInfo, Action onComplete = null)
{
DynValue co = interpreter.CreateCoroutine(closure);
DynValue returnValue = null;
while (co.Coroutine.State != CoroutineState.Dead)
{
try
{
returnValue = co.Coroutine.Resume();
}
catch (InterpreterException ex)
{
LogException(ex.DecoratedMessage, debugInfo);
}
yield return null;
}
if (onComplete != null)
{
onComplete(returnValue);
}
}
///
/// Start a Unity coroutine from a Lua call.
///
public virtual Task RunUnityCoroutine(IEnumerator coroutine)
{
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));
}
///
/// Starts a standard Unity coroutine.
/// The coroutine is managed by the LuaEnvironment monobehavior, so you can call StopAllCoroutines to
/// stop all active coroutines later.
///
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 UNITY_STANDALONE
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);
#endif
}
///
/// Writes a MoonSharp exception to the debug log in a helpful format.
///
/// Decorated message from a MoonSharp exception
/// Debug info, usually the Lua script that was running.
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);
}
}
}