174 lines
4.5 KiB
Dart
174 lines
4.5 KiB
Dart
import 'runtime_command.dart';
|
|
import 'runtime_node.dart';
|
|
|
|
import '../protocol/runtime_protocol.dart';
|
|
|
|
class NodeUpdate {
|
|
const NodeUpdate({required this.id, required this.props});
|
|
|
|
final String id;
|
|
final Map<String, Object?> props;
|
|
|
|
static NodeUpdate fromMap(Map<String, Object?> map) {
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
map,
|
|
allowed: RuntimeProtocolSchema.nodeUpdateFields,
|
|
context: 'NodeUpdate',
|
|
);
|
|
final id = map[RuntimeProtocolField.id];
|
|
if (id is! String || id.isEmpty) {
|
|
throw const FormatException('NodeUpdate.id must be a string');
|
|
}
|
|
|
|
final props = map[RuntimeProtocolField.props];
|
|
if (props is! Map) {
|
|
throw const FormatException('NodeUpdate.props must be a map');
|
|
}
|
|
|
|
final typedProps = Map<String, Object?>.from(props);
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
typedProps,
|
|
allowed: RuntimeProtocolSchema.nodePropsFields,
|
|
context: 'RuntimeNode.props',
|
|
);
|
|
|
|
return NodeUpdate(id: id, props: typedProps);
|
|
}
|
|
}
|
|
|
|
class NodeRemove {
|
|
const NodeRemove({required this.id});
|
|
|
|
final String id;
|
|
|
|
static NodeRemove fromValue(Object? value) {
|
|
if (value is String && value.isNotEmpty) {
|
|
return NodeRemove(id: value);
|
|
}
|
|
if (value is Map) {
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
value,
|
|
allowed: RuntimeProtocolSchema.nodeRemoveFields,
|
|
context: 'NodeRemove',
|
|
);
|
|
final id = value[RuntimeProtocolField.id];
|
|
if (id is String && id.isNotEmpty) {
|
|
return NodeRemove(id: id);
|
|
}
|
|
}
|
|
throw const FormatException('NodeRemove must be an id string or {id}');
|
|
}
|
|
}
|
|
|
|
class NodeDiff {
|
|
const NodeDiff({
|
|
this.creates = const [],
|
|
this.updates = const [],
|
|
this.removes = const [],
|
|
});
|
|
|
|
final List<RuntimeNode> creates;
|
|
final List<NodeUpdate> updates;
|
|
final List<NodeRemove> removes;
|
|
|
|
static NodeDiff empty = const NodeDiff();
|
|
|
|
static NodeDiff fromMap(Object? value) {
|
|
if (value == null) {
|
|
return NodeDiff.empty;
|
|
}
|
|
if (value is! Map) {
|
|
throw const FormatException('NodeDiff must be a map');
|
|
}
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
value,
|
|
allowed: RuntimeProtocolSchema.nodeDiffFields,
|
|
context: 'NodeDiff',
|
|
);
|
|
|
|
return NodeDiff(
|
|
creates: _readList(
|
|
value[RuntimeProtocolField.creates],
|
|
(item) => RuntimeNode.fromMap(Map<String, Object?>.from(item as Map)),
|
|
),
|
|
updates: _readList(
|
|
value[RuntimeProtocolField.updates],
|
|
(item) => NodeUpdate.fromMap(Map<String, Object?>.from(item as Map)),
|
|
),
|
|
removes: _readList(
|
|
value[RuntimeProtocolField.removes],
|
|
NodeRemove.fromValue,
|
|
),
|
|
);
|
|
}
|
|
|
|
static List<T> _readList<T>(Object? value, T Function(Object? value) mapper) {
|
|
if (value == null) {
|
|
return const [];
|
|
}
|
|
if (value is List) {
|
|
return value.map(mapper).toList(growable: false);
|
|
}
|
|
if (value is Map && value.isEmpty) {
|
|
return const [];
|
|
}
|
|
if (value is Map && value.keys.every(_isPositiveIntegerKey)) {
|
|
final entries = value.entries.toList()
|
|
..sort(
|
|
(a, b) => int.parse(
|
|
a.key.toString(),
|
|
).compareTo(int.parse(b.key.toString())),
|
|
);
|
|
return entries
|
|
.map((entry) => mapper(entry.value))
|
|
.toList(growable: false);
|
|
}
|
|
throw const FormatException('Diff field must be a list');
|
|
}
|
|
|
|
static bool _isPositiveIntegerKey(Object? key) {
|
|
final value = int.tryParse(key.toString());
|
|
return value != null && value > 0;
|
|
}
|
|
}
|
|
|
|
class GameDiff {
|
|
const GameDiff({
|
|
required this.render,
|
|
required this.ui,
|
|
required this.commands,
|
|
});
|
|
|
|
final NodeDiff render;
|
|
final NodeDiff ui;
|
|
final List<RuntimeCommand> commands;
|
|
|
|
static const empty = GameDiff(
|
|
render: NodeDiff(),
|
|
ui: NodeDiff(),
|
|
commands: [],
|
|
);
|
|
|
|
static GameDiff fromMap(Map<String, Object?> map) {
|
|
RuntimeProtocolSchema.ensureKnownKeys(
|
|
map,
|
|
allowed: RuntimeProtocolSchema.gameDiffFields,
|
|
context: 'GameDiff',
|
|
);
|
|
final commandsValue = map[RuntimeProtocolField.commands];
|
|
final commands = commandsValue == null
|
|
? const <RuntimeCommand>[]
|
|
: NodeDiff._readList(
|
|
commandsValue,
|
|
(item) =>
|
|
RuntimeCommand.fromMap(Map<String, Object?>.from(item as Map)),
|
|
);
|
|
|
|
return GameDiff(
|
|
render: NodeDiff.fromMap(map[RuntimeProtocolField.render]),
|
|
ui: NodeDiff.fromMap(map[RuntimeProtocolField.ui]),
|
|
commands: commands,
|
|
);
|
|
}
|
|
}
|