Initial flame_lua_runtime package

This commit is contained in:
gem
2026-06-07 22:53:58 +08:00
commit 733b2fb798
262 changed files with 28439 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
import 'dart:async' as async;
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/services.dart';
import '../audio/runtime_audio_manager.dart';
import '../lifecycle/runtime_task_registry.dart';
import '../models/game_diff.dart';
import '../models/runtime_command.dart';
import '../models/runtime_event.dart';
import '../models/runtime_node.dart';
import '../protocol/runtime_protocol.dart';
import '../rendering/render_tree_controller.dart';
import '../resources/game_resource_manager.dart';
import '../rendering/runtime_component.dart';
import 'runtime_command_registry.dart';
// These part files keep CommandExecutor as a single private implementation
// unit while grouping command handlers by responsibility. They are not a
// plugin system and should not expose additional public API.
part 'command_target_effects.dart';
part 'command_composite.dart';
part 'command_audio.dart';
part 'command_resources.dart';
part 'command_lifecycle_context.dart';
part 'command_toast.dart';
part 'command_validation.dart';
part 'command_support.dart';
class CommandExecutor {
CommandExecutor({
required RenderTreeController renderTree,
required void Function(RuntimeEvent event) eventSink,
RuntimeAudioManager? audio,
GameResourceManager? resources,
Vector2? overlaySize,
}) : _renderTree = renderTree,
_eventSink = eventSink,
_audio = audio,
_resources = resources,
_overlaySize = overlaySize ?? Vector2(720, 720);
final RenderTreeController _renderTree;
final void Function(RuntimeEvent event) _eventSink;
final RuntimeAudioManager? _audio;
final GameResourceManager? _resources;
final Vector2 _overlaySize;
late final RuntimeTaskRegistry<_CommandResult> _tasks =
RuntimeTaskRegistry<_CommandResult>(
cancelledValue: _CommandResult.cancelled,
);
final RuntimeCommandRegistry _commandRegistry = RuntimeCommandRegistry();
final Set<String> _ownedBgmChannels = {};
final Map<String, Set<String>> _bgmChannelsByScope = {};
final Map<String, String> _bgmScopeByChannel = {};
int _toastSerial = 0;
bool _disposed = false;
void dispose() {
_disposed = true;
_commandRegistry.dispose();
_tasks.dispose();
final channels = _ownedBgmChannels.toList(growable: false);
_ownedBgmChannels.clear();
_bgmChannelsByScope.clear();
_bgmScopeByChannel.clear();
for (final channel in channels) {
async.unawaited(_audio?.stopBgm(channel: channel));
}
}
void cancelScope(String scope) {
_commandRegistry.cancelScope(scope);
_tasks.cancelScope(scope);
final channels = _bgmChannelsByScope.remove(scope) ?? const <String>{};
for (final channel in channels) {
_bgmScopeByChannel.remove(channel);
_ownedBgmChannels.remove(channel);
async.unawaited(_audio?.stopBgm(channel: channel));
}
}
void executeAll(List<RuntimeCommand> commands) {
for (final command in commands) {
execute(command);
}
}
void execute(RuntimeCommand command) {
if (_disposed) {
return;
}
_validate(command);
async.unawaited(_execute(command, const _CommandContext()));
}
Future<_CommandResult> _execute(
RuntimeCommand command,
_CommandContext context,
) async {
if (_disposed) {
return _CommandResult.cancelled;
}
final commandContext = _commandContextFor(command, context);
final handle = _createCommandHandle(command, commandContext);
try {
if (handle?.isCancelled ?? false) {
return _CommandResult.cancelled;
}
final result = await _executeCore(command, commandContext, handle);
if (handle?.isCancelled ?? false) {
return _CommandResult.cancelled;
}
return result;
} finally {
handle?.complete();
}
}
Future<_CommandResult> _executeCore(
RuntimeCommand command,
_CommandContext context,
RuntimeCommandHandle? handle,
) async {
if (_disposed || (handle?.isCancelled ?? false)) {
return _CommandResult.cancelled;
}
switch (command.type) {
case RuntimeCommandType.movePath:
return _movePath(command, context, handle);
case RuntimeCommandType.moveTo:
return _targetEffect(command, context, handle, (component, duration) {
return MoveToEffect(
_requiredVector(command),
EffectController(duration: duration),
);
});
case RuntimeCommandType.fadeTo:
return _targetEffect(command, context, handle, (component, duration) {
final alpha = _requiredNormalizedDouble(
command.payload['alpha'],
'fade_to.alpha',
);
final start = component.renderAlpha;
return FunctionEffect((progress, _) {
final t = _readDouble(progress) ?? 1;
component.setRuntimeAlpha(start + (alpha - start) * t);
}, EffectController(duration: duration));
});
case RuntimeCommandType.scaleTo:
return _targetEffect(command, context, handle, (component, duration) {
final scale = _requiredDouble(
command.payload['scale'],
'scale_to.scale',
);
return ScaleEffect.to(
Vector2.all(scale),
EffectController(duration: duration),
);
});
case RuntimeCommandType.rotateTo:
return _targetEffect(command, context, handle, (component, duration) {
final angle = _requiredDouble(
command.payload['angle'],
'rotate_to.angle',
);
return RotateEffect.to(angle, EffectController(duration: duration));
});
case RuntimeCommandType.removeNode:
return _removeNode(command, context);
case RuntimeCommandType.sequence:
return _sequence(command, context, handle);
case RuntimeCommandType.parallel:
return _parallel(command, context, handle);
case RuntimeCommandType.delay:
return _delay(command, context, handle);
case RuntimeCommandType.toast:
return _toast(command, context, handle);
case RuntimeCommandType.playSound:
return _playSound(command, context, handle);
case RuntimeCommandType.playBgm:
return _playBgm(command, context, handle);
case RuntimeCommandType.pauseBgm:
return _controlBgm(command, context, _BgmControl.pause);
case RuntimeCommandType.resumeBgm:
return _controlBgm(command, context, _BgmControl.resume);
case RuntimeCommandType.stopBgm:
return _controlBgm(command, context, _BgmControl.stop);
case RuntimeCommandType.preloadResources:
return _preloadResources(command, context, handle);
case RuntimeCommandType.evictResources:
return _evictResources(command, context, handle);
case RuntimeCommandType.cancelCommands:
return _cancelCommands(command, context);
case RuntimeCommandType.playSpineAnimation:
return _playSpineAnimation(command, context);
case RuntimeCommandType.copyText:
await Clipboard.setData(
ClipboardData(text: _requiredText(command, 'copy_text.text')),
);
_emitCommandCompletion(command, context);
return _CommandResult.completed;
default:
throw UnsupportedError('Unsupported runtime command: ${command.type}');
}
}
}
class _CommandContext {
const _CommandContext({this.scope, this.scopeEpoch, this.group});
final String? scope;
final int? scopeEpoch;
final String? group;
_CommandContext copyWith({String? scope, int? scopeEpoch, String? group}) {
return _CommandContext(
scope: scope ?? this.scope,
scopeEpoch: scopeEpoch ?? this.scopeEpoch,
group: group ?? this.group,
);
}
}
enum _CommandResult { completed, cancelled }
enum _BgmControl { pause, resume, stop }