Initial flame_lua_runtime package
This commit is contained in:
230
lib/runtime/commands/command_executor.dart
Normal file
230
lib/runtime/commands/command_executor.dart
Normal 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 }
|
||||
Reference in New Issue
Block a user