171 lines
5.1 KiB
Dart
171 lines
5.1 KiB
Dart
import 'package:flame_lua_runtime/runtime/diagnostics/runtime_diagnostics.dart';
|
|
import 'package:flame_lua_runtime/runtime/events/runtime_event_dispatcher.dart';
|
|
import 'package:flame_lua_runtime/runtime/lifecycle/runtime_session.dart';
|
|
import 'package:flame_lua_runtime/runtime/models/game_diff.dart';
|
|
import 'package:flame_lua_runtime/runtime/models/runtime_event.dart';
|
|
import 'package:flame_lua_runtime/runtime/packages/game_package.dart';
|
|
import 'package:flame_lua_runtime/runtime/scripting/script_engine.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
group('RuntimeEventDispatcher', () {
|
|
test('dispatches queued events serially', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine();
|
|
final applied = <GameDiff>[];
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => true,
|
|
applyDiff: applied.add,
|
|
);
|
|
|
|
dispatcher
|
|
..enqueue(const RuntimeEvent(type: 'tap', target: 'a'))
|
|
..enqueue(const RuntimeEvent(type: 'tap', target: 'b'));
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(script.events.map((event) => event.target), ['a', 'b']);
|
|
expect(applied, hasLength(2));
|
|
});
|
|
|
|
test('drops events for removed scope', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine();
|
|
var alive = true;
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => alive,
|
|
applyDiff: (_) {},
|
|
);
|
|
|
|
dispatcher.enqueue(const RuntimeEvent(type: 'tap', scope: 'dialog'));
|
|
alive = false;
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(script.events, isEmpty);
|
|
});
|
|
|
|
test('drops events with stale target epoch', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine();
|
|
var currentEpoch = 2;
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => true,
|
|
isNodeEpochAlive: (_, epoch) => epoch == currentEpoch,
|
|
applyDiff: (_) {},
|
|
);
|
|
|
|
dispatcher.enqueue(
|
|
const RuntimeEvent(type: 'tap', target: 'button', targetEpoch: 1),
|
|
);
|
|
currentEpoch = 3;
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(script.events, isEmpty);
|
|
});
|
|
|
|
test('drops queued events after dispose', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine();
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => true,
|
|
applyDiff: (_) {},
|
|
);
|
|
|
|
dispatcher.enqueue(const RuntimeEvent(type: 'tap'));
|
|
dispatcher.dispose();
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(script.events, isEmpty);
|
|
});
|
|
|
|
test('drops queued events after session dispose', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine();
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => true,
|
|
applyDiff: (_) {},
|
|
);
|
|
|
|
dispatcher.enqueue(const RuntimeEvent(type: 'tap'));
|
|
session.dispose();
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(script.events, isEmpty);
|
|
});
|
|
|
|
test('continues draining after a script error', () async {
|
|
final session = _activeSession();
|
|
final script = _FakeScriptEngine()..failNext = true;
|
|
final errors = <Object>[];
|
|
final diagnostics = RuntimeDiagnostics();
|
|
final dispatcher = RuntimeEventDispatcher(
|
|
session: session,
|
|
scriptEngine: script,
|
|
isScopeAlive: (_) => true,
|
|
applyDiff: (_) {},
|
|
diagnostics: diagnostics,
|
|
onError: errors.add,
|
|
);
|
|
|
|
dispatcher
|
|
..enqueue(const RuntimeEvent(type: 'tap', target: 'bad'))
|
|
..enqueue(const RuntimeEvent(type: 'tap', target: 'good'));
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(errors, hasLength(1));
|
|
expect(diagnostics.entries, hasLength(1));
|
|
expect(
|
|
diagnostics.entries.single.type,
|
|
RuntimeDiagnosticType.luaEventError,
|
|
);
|
|
expect(diagnostics.entries.single.context['target'], 'bad');
|
|
expect(script.events.map((event) => event.target), ['bad', 'good']);
|
|
});
|
|
});
|
|
}
|
|
|
|
RuntimeSession _activeSession() {
|
|
final session = RuntimeSession(gameId: 'test')..beginLoading();
|
|
session.activate();
|
|
return session;
|
|
}
|
|
|
|
class _FakeScriptEngine implements ScriptEngine {
|
|
final events = <RuntimeEvent>[];
|
|
bool failNext = false;
|
|
|
|
@override
|
|
Future<void> loadPackage(GamePackage package) async {}
|
|
|
|
@override
|
|
bool smokeTest(Map<String, Object?> context) => true;
|
|
|
|
@override
|
|
GameDiff init(Map<String, Object?> context) => GameDiff.empty;
|
|
|
|
@override
|
|
GameDiff dispatchEvent(RuntimeEvent event) {
|
|
events.add(event);
|
|
if (failNext) {
|
|
failNext = false;
|
|
throw StateError('boom');
|
|
}
|
|
return GameDiff.empty;
|
|
}
|
|
}
|