diff --git a/example/assets/games/flight/scripts/runtime_defs.lua b/example/assets/games/flight/scripts/runtime_defs.lua index 1bfce9f..570b2ae 100644 --- a/example/assets/games/flight/scripts/runtime_defs.lua +++ b/example/assets/games/flight/scripts/runtime_defs.lua @@ -552,6 +552,7 @@ ---@class RuntimeImportApi ---@field import fun(moduleName: string): table +---@field log fun(...: any) ---@type RuntimeImportApi runtime = runtime diff --git a/example/assets/games/ludo/scripts/runtime_defs.lua b/example/assets/games/ludo/scripts/runtime_defs.lua index f489a9c..97ce774 100644 --- a/example/assets/games/ludo/scripts/runtime_defs.lua +++ b/example/assets/games/ludo/scripts/runtime_defs.lua @@ -552,6 +552,7 @@ ---@class RuntimeImportApi ---@field import fun(moduleName: string): table +---@field log fun(...: any) ---@type RuntimeImportApi runtime = runtime diff --git a/example/assets/games/showcase/scripts/runtime_defs.lua b/example/assets/games/showcase/scripts/runtime_defs.lua index ee07392..4c9674d 100644 --- a/example/assets/games/showcase/scripts/runtime_defs.lua +++ b/example/assets/games/showcase/scripts/runtime_defs.lua @@ -552,6 +552,7 @@ ---@class RuntimeImportApi ---@field import fun(moduleName: string): table +---@field log fun(...: any) ---@type RuntimeImportApi runtime = runtime diff --git a/example/assets/games/template/scripts/runtime_defs.lua b/example/assets/games/template/scripts/runtime_defs.lua index c5038c9..ff1c21d 100644 --- a/example/assets/games/template/scripts/runtime_defs.lua +++ b/example/assets/games/template/scripts/runtime_defs.lua @@ -552,6 +552,7 @@ ---@class RuntimeImportApi ---@field import fun(moduleName: string): table +---@field log fun(...: any) ---@type RuntimeImportApi runtime = runtime diff --git a/lib/runtime/diagnostics/runtime_diagnostics.dart b/lib/runtime/diagnostics/runtime_diagnostics.dart index 29d2f58..fce29b7 100644 --- a/lib/runtime/diagnostics/runtime_diagnostics.dart +++ b/lib/runtime/diagnostics/runtime_diagnostics.dart @@ -130,6 +130,7 @@ String _formatDebugValue(Object? value) { } enum RuntimeDiagnosticType { + luaLog, luaEventError, diffApplyError, packageActivationError, diff --git a/lib/runtime/game/lua_game_widget.dart b/lib/runtime/game/lua_game_widget.dart index 5bff845..e59315a 100644 --- a/lib/runtime/game/lua_game_widget.dart +++ b/lib/runtime/game/lua_game_widget.dart @@ -1,6 +1,7 @@ import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; +import '../diagnostics/runtime_diagnostics.dart'; import '../packages/game_package_repository.dart'; import '../scripting/lua_dardo_script_engine.dart'; import 'flame_lua_game.dart'; @@ -24,10 +25,13 @@ class LuaGameWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final diagnostics = RuntimeDiagnostics(); return GameWidget( game: FlameLuaGame( - scriptEngine: LuaDardoScriptEngine(), - scriptEngineFactory: LuaDardoScriptEngine.new, + scriptEngine: LuaDardoScriptEngine(diagnostics: diagnostics), + scriptEngineFactory: () => + LuaDardoScriptEngine(diagnostics: diagnostics), + diagnostics: diagnostics, packageRepository: packageRepository ?? (serverUrl == null diff --git a/lib/runtime/scripting/lua_dardo_script_engine.dart b/lib/runtime/scripting/lua_dardo_script_engine.dart index a00ff64..07a41c2 100644 --- a/lib/runtime/scripting/lua_dardo_script_engine.dart +++ b/lib/runtime/scripting/lua_dardo_script_engine.dart @@ -1,11 +1,16 @@ import 'package:lua_dardo_plus/lua.dart'; +import '../diagnostics/runtime_diagnostics.dart'; import '../models/game_diff.dart'; import '../models/runtime_event.dart'; import '../packages/game_package.dart'; import 'script_engine.dart'; class LuaDardoScriptEngine implements ScriptEngine { + LuaDardoScriptEngine({RuntimeDiagnostics? diagnostics}) + : _diagnostics = diagnostics; + + final RuntimeDiagnostics? _diagnostics; late final LuaState _lua; late final Map _moduleScripts; final Set _loadingModules = {}; @@ -104,9 +109,28 @@ class LuaDardoScriptEngine implements ScriptEngine { _lua.pushDartFunction(_importModule); _lua.setField(-2, 'import'); + _lua.pushDartFunction(_log); + _lua.setField(-2, 'log'); + _lua.setGlobal('runtime'); } + int _log(LuaState lua) { + final argumentCount = lua.getTop(); + final messageParts = []; + for (var index = 1; index <= argumentCount; index++) { + messageParts.add(_formatLuaLogValue(lua, index)); + } + final message = messageParts.join(' '); + + _diagnostics?.record( + type: RuntimeDiagnosticType.luaLog, + message: message, + context: {'argumentCount': argumentCount}, + ); + return 0; + } + int _importModule(LuaState lua) { final moduleName = lua.toStr(1); if (moduleName == null || moduleName.isEmpty) { @@ -179,6 +203,25 @@ class LuaDardoScriptEngine implements ScriptEngine { } } + String _formatLuaLogValue(LuaState lua, int index) { + if (lua.isNil(index) || lua.isNone(index)) { + return 'nil'; + } + if (lua.isBoolean(index)) { + return lua.toBoolean(index).toString(); + } + if (lua.isInteger(index)) { + return lua.toInteger(index).toString(); + } + if (lua.isNumber(index)) { + return lua.toNumber(index).toString(); + } + if (lua.isString(index)) { + return lua.toStr(index) ?? ''; + } + return lua.typeName2(index); + } + bool _isSafeModuleName(String value) { return RegExp(r'^[A-Za-z0-9_.-]+$').hasMatch(value) && !value.contains('..') && diff --git a/test/runtime/scripting/lua_dardo_script_engine_test.dart b/test/runtime/scripting/lua_dardo_script_engine_test.dart index 94837ae..fff645b 100644 --- a/test/runtime/scripting/lua_dardo_script_engine_test.dart +++ b/test/runtime/scripting/lua_dardo_script_engine_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flame_lua_runtime/runtime/diagnostics/runtime_diagnostics.dart'; import 'package:flame_lua_runtime/runtime/packages/game_package.dart'; import 'package:flame_lua_runtime/runtime/packages/game_package_manifest.dart'; import 'package:flame_lua_runtime/runtime/models/runtime_event.dart'; @@ -895,6 +896,51 @@ end expect(c.y, 53); }); + test('runtime.log records Lua debug messages in diagnostics', () async { + final package = await _createPackage( + mainScript: ''' +function smoke_test(ctx) + runtime.log("smoke", ctx.runtimeApiVersion) + return true +end + +function init(ctx) + runtime.log("init", true, nil) + return {} +end + +function on_event(event) + runtime.log("event", event.type, event.target) + return {} +end +''', + ); + final diagnostics = RuntimeDiagnostics(); + final engine = LuaDardoScriptEngine(diagnostics: diagnostics); + + await engine.loadPackage(package); + expect(engine.smokeTest({'runtimeApiVersion': 1}), isTrue); + engine.init({'runtimeApiVersion': 1}); + engine.dispatchEvent( + const RuntimeEvent( + type: RuntimeEventType.tap, + target: 'debug_button', + handler: 'debug', + ), + ); + + expect( + diagnostics.entries.map((entry) => entry.type), + everyElement(RuntimeDiagnosticType.luaLog), + ); + expect(diagnostics.entries.map((entry) => entry.message), [ + 'smoke 1', + 'init true nil', + 'event tap debug_button', + ]); + expect(diagnostics.entries.first.context, {'argumentCount': 2}); + }); + test('rejects undeclared module imports', () async { final package = await _createPackage( mainScript: ''' diff --git a/tool/lua_runtime_defs_common.lua b/tool/lua_runtime_defs_common.lua index c5038c9..ff1c21d 100644 --- a/tool/lua_runtime_defs_common.lua +++ b/tool/lua_runtime_defs_common.lua @@ -552,6 +552,7 @@ ---@class RuntimeImportApi ---@field import fun(moduleName: string): table +---@field log fun(...: any) ---@type RuntimeImportApi runtime = runtime