forked from Semaeopus/Unity-Lua
-
Notifications
You must be signed in to change notification settings - Fork 1
/
LuaVM.cs
593 lines (520 loc) · 21.2 KB
/
LuaVM.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
using System;
using System.Collections.Generic;
using MoonSharp.Interpreter;
using MoonSharp.VsCodeDebugger;
using MoonSharp.Interpreter.Loaders;
using System.Linq;
using System.Reflection;
using System.Text;
using MoonSharp.Interpreter.Debugging;
using UnityEditor;
using UnityEngine;
/// <summary>
/// Wrapper around the Moonsharp API.
/// Implements heavy error checking and logging for end users
/// </summary>
public class LuaVM {
/// <summary>
/// The Moonsharp script object
/// </summary>
private readonly Script m_LuaScript = null;
private static Type[] m_APITypeList = null;
private LuaAPIBase[] m_APIList = null;
private static DynValue m_EnumTables = null;
/// <summary>
/// Settings to control the behaviour of the VM
/// </summary>
[Flags]
public enum VMSettings : uint {
/// <summary>
/// No custom code will be attached
/// </summary>
None = 0,
/// <summary>
/// We'll attach anything deriving from LuaAPIBase
/// </summary>
AttachAPIs = 1 << 0,
/// <summary>
/// We'll attach any enum with the LuaApiEnum attribute
/// </summary>
AttachEnums = 1 << 1,
/// <summary>
/// Attach everything we know about
/// </summary>
AttachAll = ~0u,
}
/// <summary>
/// The Moonsharp remote debugger service
/// </summary>
private static MoonSharpVsCodeDebugServer s_RemoteDebugger = null;
/// <summary>
/// Default to attaching all apis and enums
/// </summary>
public LuaVM(CoreModules initialModulesSetup)
: this(VMSettings.AttachAll, initialModulesSetup, new FileSystemScriptLoader()) { }
public LuaVM(CoreModules initialModulesSetup, IScriptLoader scriptLoader)
: this(VMSettings.AttachAll, initialModulesSetup, scriptLoader) { }
/// <summary>
/// Default settings are a soft sandbox and setting up the file system script loader
/// </summary>
public LuaVM(VMSettings vmSettings, CoreModules initialModulesSetup, IScriptLoader scriptLoader) {
m_LuaScript =
new Script(initialModulesSetup) {
Options = {
ScriptLoader = scriptLoader,//new FileSystemScriptLoader(),
DebugPrint = log => LuaLogger.Log(Channel.LuaNative, log),
}
};
if ((vmSettings & VMSettings.AttachAPIs) == VMSettings.AttachAPIs) {
AttachAPIS();
}
if ((vmSettings & VMSettings.AttachEnums) == VMSettings.AttachEnums) {
AttachLuaEnums();
}
}
/// <summary>
/// Add a new global Lua table and if successful return a reference to it
/// </summary>
/// <param name="tableName"></param>
/// <returns></returns>
public Table AddGlobalTable(string tableName) {
Table table = null;
if (SetGlobal(tableName, DynValue.NewTable(m_LuaScript))) {
table = GetGlobal(tableName).Table;
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Failed to add global Lua table {0}", tableName);
}
return table;
}
/// <summary>
/// Attempts to set a global variable with key and value
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns>true if value was set successfully</returns>
public bool SetGlobal(string key, object value) {
bool didSet = false;
try {
m_LuaScript.Globals[key] = value;
didSet = true;
}
catch (InterpreterException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua SetGlobal error: {0}", ex.DecoratedMessage);
}
return didSet;
}
/// <summary>
/// Attempts to retrive a value from the Lua globals
/// </summary>
/// <param name="key"></param>
/// <returns>null if failure occurs, else the requested value as a DynValue</returns>
public DynValue GetGlobal(string key) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.Globals.Get(key);
}
catch {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Failed to get Lua global {0}", key);
}
return result;
}
/// <summary>
/// Attempts to retrive a value from the Lua globals, allowing the user to pass parent and children names in
/// </summary>
/// <returns>The global.</returns>
/// <param name="keys">Keys.</param>
public DynValue GetGlobal(params object[] keys) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.Globals.Get(keys);
}
catch {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Failed to get Lua global at '{0}'",
string.Join(", ", Array.ConvertAll(keys, input => input.ToString())));
}
return result;
}
/// <summary>
/// Attempts to retrive a table from the Lua globals
/// </summary>
/// <returns>The global table.</returns>
/// <param name="key">Key.</param>
public Table GetGlobalTable(string key) {
Table result = null;
DynValue tableDyn = GetGlobal(key);
if (tableDyn != null) {
if (tableDyn.Type == DataType.Table) {
result = tableDyn.Table;
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua global {0} is not type table, has type {1}", key, tableDyn.Type.ToString());
}
}
return result;
}
public Table GetGlobalTableOrNull(string key) {
Table result = null;
DynValue tableDyn = GetGlobal(key);
if (tableDyn != null) {
if (tableDyn.Type == DataType.Table) {
result = tableDyn.Table;
}
else if (tableDyn == DynValue.Nil) {
return null;
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua global {0} is not type table, has type {1}", key, tableDyn.Type.ToString());
}
}
return result;
}
public Table GetOrAddGlobalTable(string key) {
Table table = GetGlobalTableOrNull(key);
return table ?? AddGlobalTable(key);
}
/// <summary>
/// Attempts to retrive a table from the Lua globals, allowing the user to pass parent and children names in
/// </summary>
/// <returns>The global table.</returns>
/// <param name="keys">Key.</param>
public Table GetGlobalTable(params object[] keys) {
Table result = null;
DynValue tableDyn = GetGlobal(keys);
if (tableDyn != null) {
if (tableDyn.Type == DataType.Table) {
result = tableDyn.Table;
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua global {0} is not type table, has type {1}", keys, tableDyn.Type.ToString());
}
}
return result;
}
/// <summary>
/// Return the global table for this vm
/// </summary>
/// <returns></returns>
public Table GetGlobalsTable() {
return m_LuaScript.Globals;
}
/// <summary>
/// Attempts to run the lua command passed in
/// </summary>
/// <param name="command"></param>
/// <returns>Null if an error occured otherwise will return the result of the executed lua code</returns>
public DynValue ExecuteString(string command) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.DoString(command);
}
catch (InterpreterException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage);
}
return result;
}
/// <summary>
/// Attempts to run the lua script passed in
/// </summary>
/// <param name="filePath"></param>
/// <returns>Null if an error occured otherwise will return the result of the executed lua code</returns>
public DynValue ExecuteScript(string filePath) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.DoFile(filePath);
}
catch (InterpreterException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteScript error: {0}", ex.DecoratedMessage);
}
catch (Exception ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "System ExecuteScript error: {0}", ex.Message);
Debug.LogException(ex);
}
return result;
}
/// <summary>
/// Attempts to load a lua script
/// </summary>
/// <param name="filePath"></param>
/// <returns>Null if an error occured otherwise will return the DynValue of the script. This can be passed to Call()</returns>
public DynValue LoadScript(string filePath) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.LoadFile(filePath);
}
catch (InterpreterException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage);
}
catch (Exception ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "System ExecuteString error: {0}", ex.Message);
}
return result;
}
/// <summary>
/// Attemps to load a string containing lua code
/// </summary>
/// <param name="luaString"></param>
/// <returns>Null if an error occured otherwise will return the DynValue of the script. This can be passed to Call()</returns>
public DynValue LoadString(string luaString, Table globalTable = null, string codeFriendlyName = null) {
DynValue result = DynValue.Nil;
try {
result = m_LuaScript.LoadString(luaString, globalTable, codeFriendlyName);
}
catch (InterpreterException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Lua ExecuteString error: {0}", ex.DecoratedMessage);
}
catch (Exception ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "System ExecuteString error: {0}", ex.Message);
}
return result;
}
/// <summary>
/// Attemps to load a string containing lua code
/// </summary>
/// <param name="luaString"></param>
/// <returns>Null if an error occured otherwise will return the DynValue of the script. This can be passed to Call()</returns>
public DynValue LoadString(string luaString, string codeFriendlyName) {
return LoadString(luaString, m_LuaScript.Globals, codeFriendlyName);
}
/// <summary>
/// Call a lua function via DynValue
/// </summary>
/// <param name="luaFunc"></param>
/// <param name="args"></param>
/// <returns>Null if call fails of function is invalid, else the result of the function</returns>
public DynValue Call(DynValue luaFunc, params object[] args) {
DynValue result = DynValue.Nil;
if (luaFunc.IsNotNil() && (luaFunc.Type == DataType.Function || luaFunc.Type == DataType.ClrFunction)) {
try {
result = m_LuaScript.Call(luaFunc, args);
}
catch (ScriptRuntimeException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "[ScriptRuntimeException] Lua Call error: [EXCEPTION] Message: {0}\n CallStack:{1}\n\n Stack trace:\n{2}\n\n [SOURCE] Message: {3}\n Stack trace:\n{4}", ex.DecoratedMessage, StringifyStacktrace(ex.CallStack), ex.StackTrace, ex.InnerException?.Message, ex.InnerException?.StackTrace);
throw;
}
catch (CSharpException ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "[CSharpException] Lua Call error: [EXCEPTION] Message: {0}\n CallStack:{1}\n\n Stack trace:\n{2}\n\n [SOURCE] Message: {3}\n Stack trace:\n{4}", ex.DecoratedMessage, StringifyStacktrace(ex.CallStack), ex.StackTrace, ex.InnerException?.Message, ex.InnerException?.StackTrace);
throw;
}
catch (Exception ex) {
LuaLogger.Log(Channel.Lua, Priority.FatalError, $"[{ex.GetType()}] Lua Call error: [EXCEPTION] Message: {0}\n Stack trace:\n{1}\n\n [SOURCE] Message: {2}\n Stack trace:\n{3}", ex.Message, ex.StackTrace, ex.InnerException?.Message, ex.InnerException?.StackTrace);
throw;
}
}
else {
string description;
if (luaFunc.Type == DataType.Table) {
description = $"{luaFunc.Type} / {luaFunc.ToDebugPrintString()} / {PrintDebug(luaFunc.Table)}";
}
else {
description = $"{luaFunc.Type} / {luaFunc.ToDebugPrintString()}";
}
LuaLogger.Log(Channel.Lua, Priority.FatalError, $"Invalid lua function passed to LuaVM::Call: [{description}]");
}
return result;
}
public static string PrintDebug(Table table) {
var maybePr = table.OwnerScript.Globals.Get("pr");
if (maybePr.Type == DataType.Table) {
return maybePr.Table.Get("table").Function.Call(table).String;
}
else {
return "[..pr.table() is not available..]";
}
}
string StringifyStacktrace(IList<WatchItem> callstack) {
if (callstack == null) {
return "<callstack is null!>";
}
StringBuilder sb = new StringBuilder();
foreach (WatchItem wi in callstack)
{
string name;
if (wi.Name == null)
if (wi.RetAddress < 0)
name = "main chunk";
else
name = "?";
else
name = "function '" + wi.Name + "'";
string loc = wi.Location != null ? wi.Location.FormatLocation(m_LuaScript) : "[clr]";
sb.AppendFormat("\t{0}: in {1}\n", loc, name);
}
return sb.ToString();
}
/// <summary>
/// Call a lua function via name
/// </summary>
/// <param name="functionName">Function name.</param>
/// <param name="args">Arguments.</param>
public DynValue Call(string functionName, params object[] args) {
DynValue result = DynValue.Nil;
if (!string.IsNullOrEmpty(functionName)) {
DynValue func = GetGlobal(functionName);
if (func.Type == DataType.Function) {
try {
//Debug.Log($"<><><><><> ECS = {m_LuaScript.Globals["ecs"]}");
result = m_LuaScript.Call(func, args);
}
catch (InterpreterException ex) {
LogException(functionName, ex);
throw;
}
//catch (Exception ex) {
//Logger.Log(Channel.Lua, Priority.FatalError, "Exception calling function '{0}': {1}", functionName, ex.);
//}
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError, "Failed to find lua function '{0}'", functionName);
}
}
return result;
}
public void LogException(string functionName, InterpreterException ex) {
if (ex.InnerException != null) {
var inner = ex.InnerException;
if (inner.InnerException != null) {
LuaLogger.Log(Channel.Lua, Priority.FatalError,
"Lua error A calling function '{0}':\n\n" +
"message = {1}\n" +
"decorated = {2}\n\n" +
"--------- CLR 1 ---------\n\n" +
"inner message = {3}\n\n" +
"inner stack trace:\n{4}\n\n" +
"--------- CLR 2 ---------\n\n" +
"inner message 2x = {5}\n\n" +
"inner stack trace 2x:\n{6}\n\n" +
" --------- LUA ---------\n" +
"{7}",
functionName,
ex.Message,
ex.DecoratedMessage,
/* -- clr 1 -- */
ex.InnerException?.Message,
ex.InnerException?.StackTrace,
/* -- clr 2 -- */
ex.InnerException?.InnerException?.Message,
ex.InnerException?.InnerException?.StackTrace,
StringifyStacktrace(ex.CallStack));
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError,
"Lua error B calling function '{0}':\n\n" +
"message = {1}\n" +
"decorated = {2}\n\n" +
"--------- CLR ---------\n\n" +
"inner message = {3}\n\n" +
"inner stack trace:\n{4}\n\n" +
" --------- LUA ---------\n" +
"{5}",
functionName,
ex.Message,
ex.DecoratedMessage,
ex.InnerException?.Message,
ex.InnerException?.StackTrace,
StringifyStacktrace(ex.CallStack));
}
}
else {
LuaLogger.Log(Channel.Lua, Priority.FatalError,
"Lua error C calling function '{0}':\n\n" +
"message = {1}\n" +
"decorated = {2}\n\n" +
"--------- CLR ---------\n\n" +
"message = {3}\n\n" +
"stack trace:\n{4}\n\n" +
" --------- LUA ---------\n" +
"{5}",
functionName,
ex.Message,
ex.DecoratedMessage,
ex.Message,
ex.StackTrace,
StringifyStacktrace(ex.CallStack));
}
}
/// <summary>
/// Starts the remote debugger and opens the interface in the users browser
/// </summary>
public static void StartRemoteDebugger() {
if (s_RemoteDebugger == null) {
s_RemoteDebugger = new MoonSharpVsCodeDebugServer();
s_RemoteDebugger.Start();
}
}
/// <summary>
/// Attaches to the remove debugger service
/// </summary>
public void AttachDebugger() {
if (s_RemoteDebugger != null) {
s_RemoteDebugger.AttachToScript(m_LuaScript, "Lua script");
}
else {
LuaLogger.Log(Channel.Lua, Priority.Error, "Tried to attach script to debugger before debugger was started");
}
}
/// <summary>
/// Return the current script object
/// </summary>
public Script GetScriptObject() {
return m_LuaScript;
}
/// <summary>
/// Create static api list and attach to this VM
/// </summary>
private void AttachAPIS() {
if (m_APITypeList == null) {
List<Type> apiTypeList = new List<Type>();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
apiTypeList.AddRange(assembly.GetTypes()
.Where(type => type.IsSubclassOf(typeof(LuaAPIBase))));
}
m_APITypeList = apiTypeList.ToArray();
}
m_APIList = new LuaAPIBase[m_APITypeList.Length];
for (int i = 0; i < m_APITypeList.Length; ++i) {
m_APIList[i] = Activator.CreateInstance(m_APITypeList[i]) as LuaAPIBase;
}
// Iterate apis and tell them to update this lua vm
foreach (LuaAPIBase api in m_APIList) {
api.AddAPIToLuaInstance(this);
}
}
/// <summary>
/// Create the reusable enum prime table and attach to this VM
/// </summary>
private void AttachLuaEnums() {
if (m_EnumTables == null) {
// Create a new prime table
// Prime tables can be shared between scripts
m_EnumTables = DynValue.NewPrimeTable();
// Get all enums with the lua attribute
List<Type> luaEnumList = new List<Type>();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
luaEnumList.AddRange((assembly.GetTypes()
.Where(luaEnumType => Attribute.IsDefined(luaEnumType, typeof(LuaApiEnum)))));
}
foreach (Type enumType in luaEnumList) {
// Get the attribute
LuaApiEnum apiEnumAttrib = (LuaApiEnum) enumType.GetCustomAttributes(typeof(LuaApiEnum), false)[0];
// Create the table for this enum and get a reference to it
m_EnumTables.Table.Set(apiEnumAttrib.name, DynValue.NewPrimeTable());
Table enumTable = m_EnumTables.Table.Get(apiEnumAttrib.name).Table;
// Foreach value in the enum list
foreach (var enumValue in Enum.GetValues(enumType)) {
var memberInfo = enumType.GetMember(enumValue.ToString());
var attribute = memberInfo[0].GetCustomAttributes(typeof(LuaApiEnumValue), false);
// Double check they've not been flagged as hidden
if (attribute.Length > 0 && ((LuaApiEnumValue) attribute[0]).hidden) {
continue;
}
enumTable.Set(enumValue.ToString(), DynValue.NewNumber((int) enumValue));
}
}
}
// Iterate through the enum cache and copy the values into our globals
foreach (var enumPair in m_EnumTables.Table.Pairs) {
m_LuaScript.Globals.Set(enumPair.Key, enumPair.Value);
}
}
}