249 lines
6.4 KiB
Dart
249 lines
6.4 KiB
Dart
part of 'command_executor.dart';
|
|
|
|
extension _CommandExecutorSupport on CommandExecutor {
|
|
void _appendCompletionEffect(
|
|
List<Effect> effects,
|
|
RuntimeCommand command,
|
|
String target,
|
|
int targetEpoch,
|
|
RuntimeTask<_CommandResult> task,
|
|
String? scope,
|
|
int? scopeEpoch,
|
|
) {
|
|
effects.add(
|
|
FunctionEffect((_, __) {
|
|
if (!_scopeIsAlive(scope) ||
|
|
!_renderTree.isNodeEpochAlive(target, targetEpoch)) {
|
|
task.cancel();
|
|
return;
|
|
}
|
|
_emitCompletion(command, target, scope, targetEpoch, scopeEpoch);
|
|
task.complete(_CommandResult.completed);
|
|
}, EffectController(duration: 0.01)),
|
|
);
|
|
}
|
|
|
|
void _emitCompletion(
|
|
RuntimeCommand command,
|
|
String target,
|
|
String? scope, [
|
|
int? targetEpoch,
|
|
int? scopeEpoch,
|
|
]) {
|
|
final onComplete = _optionalString(
|
|
command.payload['onComplete'],
|
|
'onComplete',
|
|
);
|
|
if (onComplete == null) {
|
|
return;
|
|
}
|
|
_emitEventIfScopeAlive(
|
|
RuntimeEvent(
|
|
type: RuntimeEventType.animationDone,
|
|
target: target,
|
|
handler: onComplete,
|
|
),
|
|
scope,
|
|
targetEpoch: targetEpoch,
|
|
scopeEpoch: scopeEpoch,
|
|
);
|
|
}
|
|
|
|
void _emitCommandCompletion(RuntimeCommand command, _CommandContext context) {
|
|
final onComplete = _optionalString(
|
|
command.payload['onComplete'],
|
|
'onComplete',
|
|
);
|
|
if (onComplete == null) {
|
|
return;
|
|
}
|
|
_emitEventIfScopeAlive(
|
|
RuntimeEvent(
|
|
type: RuntimeEventType.animationDone,
|
|
target: command.target,
|
|
handler: onComplete,
|
|
),
|
|
_completionScopeFor(command, context),
|
|
scopeEpoch: context.scopeEpoch,
|
|
);
|
|
}
|
|
|
|
void _emitEventIfScopeAlive(
|
|
RuntimeEvent event,
|
|
String? scope, {
|
|
int? targetEpoch,
|
|
int? scopeEpoch,
|
|
}) {
|
|
if (!_scopeIsAlive(scope) || _disposed) {
|
|
return;
|
|
}
|
|
_eventSink(
|
|
event.withLifecycle(
|
|
scope: scope,
|
|
targetEpoch: targetEpoch,
|
|
scopeEpoch: scopeEpoch,
|
|
),
|
|
);
|
|
}
|
|
|
|
Vector2 _requiredVector(RuntimeCommand command) {
|
|
final x = _readDouble(command.payload['x']);
|
|
final y = _readDouble(command.payload['y']);
|
|
if (x == null || y == null) {
|
|
throw FormatException('${command.type}.x/y are required numbers');
|
|
}
|
|
return Vector2(x, y);
|
|
}
|
|
|
|
String _requiredTarget(RuntimeCommand command) {
|
|
final target = command.target;
|
|
if (target == null || target.isEmpty) {
|
|
throw FormatException('${command.type}.target is required');
|
|
}
|
|
return target;
|
|
}
|
|
|
|
String _requiredText(RuntimeCommand command, String field) {
|
|
return _optionalString(command.payload['text'], field)!;
|
|
}
|
|
|
|
double _duration(RuntimeCommand command, {required double defaultValue}) {
|
|
final duration = _readDouble(command.payload['duration']) ?? defaultValue;
|
|
if (duration < 0) {
|
|
throw FormatException('${command.type}.duration must be >= 0');
|
|
}
|
|
return duration;
|
|
}
|
|
|
|
double _requiredDouble(Object? value, String field) {
|
|
final result = _readDouble(value);
|
|
if (result == null) {
|
|
throw FormatException('$field must be a number');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void _registerBgmChannel({required String channel, required String? scope}) {
|
|
_unregisterBgmChannel(channel);
|
|
_ownedBgmChannels.add(channel);
|
|
if (scope == null) {
|
|
return;
|
|
}
|
|
_bgmScopeByChannel[channel] = scope;
|
|
_bgmChannelsByScope.putIfAbsent(scope, () => {}).add(channel);
|
|
}
|
|
|
|
void _unregisterBgmChannel(String channel) {
|
|
_ownedBgmChannels.remove(channel);
|
|
final oldScope = _bgmScopeByChannel.remove(channel);
|
|
if (oldScope == null) {
|
|
return;
|
|
}
|
|
final channels = _bgmChannelsByScope[oldScope];
|
|
channels?.remove(channel);
|
|
if (channels != null && channels.isEmpty) {
|
|
_bgmChannelsByScope.remove(oldScope);
|
|
}
|
|
}
|
|
|
|
String _audioChannel(RuntimeCommand command) {
|
|
return _optionalString(command.payload['channel'], 'channel') ??
|
|
RuntimeAudioChannel.defaultBgm;
|
|
}
|
|
|
|
String _requiredResourceGroup(RuntimeCommand command) {
|
|
return _optionalString(command.payload['group'], '${command.type}.group')!;
|
|
}
|
|
|
|
void _validateCancelCommands(RuntimeCommand command) {
|
|
final id = _optionalString(command.payload['id'], 'cancel_commands.id');
|
|
final group = _optionalString(
|
|
command.payload['group'],
|
|
'cancel_commands.group',
|
|
);
|
|
final scope = _optionalString(
|
|
command.payload['scope'],
|
|
'cancel_commands.scope',
|
|
);
|
|
if (id == null && group == null && scope == null) {
|
|
throw const FormatException(
|
|
'cancel_commands requires id, group or scope',
|
|
);
|
|
}
|
|
}
|
|
|
|
String _requiredAudioResource(RuntimeCommand command) {
|
|
final asset = command.payload['asset'] ?? command.payload['name'];
|
|
return _optionalString(asset, 'play_sound.asset/name')!;
|
|
}
|
|
|
|
String _requiredSpineAnimation(RuntimeCommand command) {
|
|
final value = _optionalString(
|
|
command.payload['animation'],
|
|
'play_spine_animation.animation',
|
|
);
|
|
if (value == null) {
|
|
throw const FormatException(
|
|
'play_spine_animation.animation must be a non-empty string',
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
bool? _optionalBool(Object? value, String field) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is bool) {
|
|
return value;
|
|
}
|
|
throw FormatException('$field must be a boolean');
|
|
}
|
|
|
|
double _optionalVolume(RuntimeCommand command) {
|
|
final value = command.payload['volume'];
|
|
if (value == null) {
|
|
return 1;
|
|
}
|
|
return _requiredNormalizedDouble(value, 'play_sound.volume');
|
|
}
|
|
|
|
double _requiredNormalizedDouble(Object? value, String field) {
|
|
final result = _requiredDouble(value, field);
|
|
if (result < 0 || result > 1) {
|
|
throw FormatException('$field must be between 0 and 1');
|
|
}
|
|
return result;
|
|
}
|
|
|
|
String? _optionalString(Object? value, String field) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is String && value.isNotEmpty) {
|
|
return value;
|
|
}
|
|
throw FormatException('$field must be a non-empty string');
|
|
}
|
|
|
|
int? _optionalInt(Object? value, String field) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is num) {
|
|
return value.toInt();
|
|
}
|
|
throw FormatException('$field must be an integer');
|
|
}
|
|
|
|
double? _readDouble(Object? value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is num) {
|
|
return value.toDouble();
|
|
}
|
|
return null;
|
|
}
|
|
}
|