Add runtime networking APIs

This commit is contained in:
gem
2026-06-09 16:09:19 +08:00
parent 4f36d68b74
commit 7b3c5cb0f5
20 changed files with 936 additions and 6 deletions

View File

@@ -4,8 +4,10 @@ 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';
import 'package:flame_lua_runtime/runtime/network/runtime_network_manager.dart';
import 'package:flame_lua_runtime/runtime/protocol/runtime_protocol.dart';
import 'package:flame_lua_runtime/runtime/scripting/lua_dardo_script_engine.dart';
import 'package:flame_lua_runtime/runtime/scripting/runtime_script_services.dart';
import 'package:flutter_test/flutter_test.dart';
Future<GamePackage> _loadExamplePackage(String gameId) async {
@@ -941,6 +943,84 @@ end
expect(diagnostics.entries.first.context, {'argumentCount': 2});
});
test('exposes async HTTP runtime API to Lua', () async {
final package = await _createPackage(
mainScript: '''
function smoke_test(ctx) return true end
function init(ctx)
local id = runtime.http_request({
id = "login",
method = "post",
url = "https://example.com/login",
headers = { Authorization = "Bearer token" },
body = "{}",
timeout = 2,
})
return { commands = { { type = "toast", text = id } } }
end
function on_event(event) return {} end
''',
);
final network = _RecordingNetworkManager();
final engine = LuaDardoScriptEngine();
await engine.loadPackage(
package,
services: RuntimeScriptServices(network: network),
);
final diff = engine.init({'runtimeApiVersion': 1});
expect(diff.commands.single.payload['text'], 'login');
expect(network.httpRequests, hasLength(1));
expect(network.httpRequests.single.id, 'login');
expect(network.httpRequests.single.method, 'POST');
expect(network.httpRequests.single.uri.scheme, 'https');
expect(
network.httpRequests.single.headers['Authorization'],
'Bearer token',
);
expect(network.httpRequests.single.body, '{}');
expect(network.httpRequests.single.timeout, const Duration(seconds: 2));
});
test('exposes WebSocket runtime API to Lua', () async {
final package = await _createPackage(
mainScript: '''
function smoke_test(ctx) return true end
function init(ctx)
local id = runtime.ws_connect({
id = "chat",
url = "wss://example.com/socket",
protocols = { "game.v1" },
})
local sent = runtime.ws_send(id, "hello")
local closed = runtime.ws_close(id)
return {
commands = {
{ type = "toast", text = id .. ":" .. tostring(sent) .. ":" .. tostring(closed) },
},
}
end
function on_event(event) return {} end
''',
);
final network = _RecordingNetworkManager();
final engine = LuaDardoScriptEngine();
await engine.loadPackage(
package,
services: RuntimeScriptServices(network: network),
);
final diff = engine.init({'runtimeApiVersion': 1});
expect(diff.commands.single.payload['text'], 'chat:true:true');
expect(network.wsConnectRequests.single.id, 'chat');
expect(network.wsConnectRequests.single.uri.scheme, 'wss');
expect(network.wsConnectRequests.single.protocols, ['game.v1']);
expect(network.wsMessages, {'chat': 'hello'});
expect(network.closedWebSockets, ['chat']);
});
test('rejects undeclared module imports', () async {
final package = await _createPackage(
mainScript: '''
@@ -972,6 +1052,43 @@ function on_event(event) return {} end
});
}
class _RecordingNetworkManager extends RuntimeNetworkManager {
_RecordingNetworkManager()
: super(
eventSink: (event) {
events.add(event);
},
);
static final events = <RuntimeEvent>[];
final httpRequests = <RuntimeHttpRequest>[];
final wsConnectRequests = <RuntimeWebSocketConnectRequest>[];
final wsMessages = <String, Object?>{};
final closedWebSockets = <String>[];
@override
Future<void> httpRequest(RuntimeHttpRequest request) async {
httpRequests.add(request);
}
@override
void wsConnect(RuntimeWebSocketConnectRequest request) {
wsConnectRequests.add(request);
}
@override
bool wsSend(String id, Object? message) {
wsMessages[id] = message;
return true;
}
@override
bool closeWebSocket(String id, {bool emitClose = true}) {
closedWebSockets.add(id);
return true;
}
}
Future<GamePackage> _createPackage({
required String mainScript,
Map<String, String> modules = const {},