979 lines
24 KiB
Dart
979 lines
24 KiB
Dart
/// 增量保存模块
|
||
///
|
||
/// 实现操作日志(Command Pattern),支持:
|
||
/// - 快照 + 增量日志混合存储
|
||
/// - 撤销/重做功能
|
||
/// - 断点恢复
|
||
///
|
||
/// @version 0.1.0
|
||
/// @date 2026-03-07
|
||
|
||
import 'dart:convert';
|
||
import 'dart:typed_data';
|
||
|
||
// ============================================================================
|
||
// 操作类型枚举
|
||
// ============================================================================
|
||
|
||
/// 操作类型
|
||
enum OperationType {
|
||
componentAdd,
|
||
componentMove,
|
||
componentRotate,
|
||
componentDelete,
|
||
netAdd,
|
||
netConnect,
|
||
netDelete,
|
||
traceAdd,
|
||
traceDelete,
|
||
viaAdd,
|
||
propertyChange,
|
||
snapshot,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 命令接口
|
||
// ============================================================================
|
||
|
||
/// 命令接口 (Command Pattern)
|
||
abstract class Command {
|
||
/// 执行命令
|
||
void execute();
|
||
|
||
/// 撤销命令
|
||
void undo();
|
||
|
||
/// 获取操作类型
|
||
OperationType get type;
|
||
|
||
/// 获取时间戳
|
||
DateTime get timestamp;
|
||
|
||
/// 序列化为 Map
|
||
Map<String, dynamic> toJson();
|
||
|
||
/// 从 Map 反序列化
|
||
factory Command.fromJson(Map<String, dynamic> json);
|
||
}
|
||
|
||
// ============================================================================
|
||
// 具体命令实现
|
||
// ============================================================================
|
||
|
||
/// 添加元件命令
|
||
class AddComponentCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.componentAdd;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final Map<String, dynamic> component;
|
||
final String? parentId;
|
||
|
||
// 撤销时需要的信息
|
||
String? _createdId;
|
||
|
||
AddComponentCommand({
|
||
required this.component,
|
||
this.parentId,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 实际执行由 Editor 处理
|
||
_createdId = component['id'];
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 删除创建的元件
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'componentAdd',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'component': component,
|
||
'parentId': parentId,
|
||
'createdId': _createdId,
|
||
};
|
||
}
|
||
|
||
factory Command.fromJson(Map<String, dynamic> json) {
|
||
switch (json['type']) {
|
||
case 'componentAdd':
|
||
return AddComponentCommand(
|
||
component: json['component'] as Map<String, dynamic>,
|
||
parentId: json['parentId'] as String?,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
case 'componentMove':
|
||
return MoveComponentCommand.fromJson(json);
|
||
case 'componentRotate':
|
||
return RotateComponentCommand.fromJson(json);
|
||
case 'componentDelete':
|
||
return DeleteComponentCommand.fromJson(json);
|
||
case 'netAdd':
|
||
return AddNetCommand.fromJson(json);
|
||
case 'propertyChange':
|
||
return PropertyChangeCommand.fromJson(json);
|
||
case 'snapshot':
|
||
return SnapshotCommand.fromJson(json);
|
||
default:
|
||
throw FormatException('Unknown command type: ${json['type']}');
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 移动元件命令
|
||
class MoveComponentCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.componentMove;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final String componentId;
|
||
final num oldX;
|
||
final num oldY;
|
||
final num newX;
|
||
final num newY;
|
||
|
||
MoveComponentCommand({
|
||
required this.componentId,
|
||
required this.oldX,
|
||
required this.oldY,
|
||
required this.newX,
|
||
required this.newY,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 移动到新位置 (已经在执行时应用)
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 移回旧位置
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'componentMove',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'componentId': componentId,
|
||
'oldX': oldX,
|
||
'oldY': oldY,
|
||
'newX': newX,
|
||
'newY': newY,
|
||
};
|
||
}
|
||
|
||
factory MoveComponentCommand.fromJson(Map<String, dynamic> json) {
|
||
return MoveComponentCommand(
|
||
componentId: json['componentId'] as String,
|
||
oldX: json['oldX'] as num,
|
||
oldY: json['oldY'] as num,
|
||
newX: json['newX'] as num,
|
||
newY: json['newY'] as num,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 旋转元件命令
|
||
class RotateComponentCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.componentRotate;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final String componentId;
|
||
final int oldRotation;
|
||
final int newRotation;
|
||
|
||
RotateComponentCommand({
|
||
required this.componentId,
|
||
required this.oldRotation,
|
||
required this.newRotation,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 旋转到新角度
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 旋转回旧角度
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'componentRotate',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'componentId': componentId,
|
||
'oldRotation': oldRotation,
|
||
'newRotation': newRotation,
|
||
};
|
||
}
|
||
|
||
factory RotateComponentCommand.fromJson(Map<String, dynamic> json) {
|
||
return RotateComponentCommand(
|
||
componentId: json['componentId'] as String,
|
||
oldRotation: json['oldRotation'] as int,
|
||
newRotation: json['newRotation'] as int,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 删除元件命令
|
||
class DeleteComponentCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.componentDelete;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final String componentId;
|
||
final Map<String, dynamic> component;
|
||
|
||
DeleteComponentCommand({
|
||
required this.componentId,
|
||
required this.component,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 删除元件
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 恢复元件
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'componentDelete',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'componentId': componentId,
|
||
'component': component,
|
||
};
|
||
}
|
||
|
||
factory DeleteComponentCommand.fromJson(Map<String, dynamic> json) {
|
||
return DeleteComponentCommand(
|
||
componentId: json['componentId'] as String,
|
||
component: json['component'] as Map<String, dynamic>,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 添加网络命令
|
||
class AddNetCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.netAdd;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final Map<String, dynamic> net;
|
||
|
||
AddNetCommand({
|
||
required this.net,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 添加网络
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 删除网络
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'netAdd',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'net': net,
|
||
};
|
||
}
|
||
|
||
factory AddNetCommand.fromJson(Map<String, dynamic> json) {
|
||
return AddNetCommand(
|
||
net: json['net'] as Map<String, dynamic>,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 属性变更命令
|
||
class PropertyChangeCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.propertyChange;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final String objectId;
|
||
final String propertyName;
|
||
final dynamic oldValue;
|
||
final dynamic newValue;
|
||
|
||
PropertyChangeCommand({
|
||
required this.objectId,
|
||
required this.propertyName,
|
||
required this.oldValue,
|
||
required this.newValue,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 应用新值
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 恢复旧值
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'propertyChange',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'objectId': objectId,
|
||
'propertyName': propertyName,
|
||
'oldValue': oldValue,
|
||
'newValue': newValue,
|
||
};
|
||
}
|
||
|
||
factory PropertyChangeCommand.fromJson(Map<String, dynamic> json) {
|
||
return PropertyChangeCommand(
|
||
objectId: json['objectId'] as String,
|
||
propertyName: json['propertyName'] as String,
|
||
oldValue: json['oldValue'],
|
||
newValue: json['newValue'],
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 快照命令
|
||
class SnapshotCommand implements Command {
|
||
@override
|
||
final OperationType type = OperationType.snapshot;
|
||
@override
|
||
final DateTime timestamp;
|
||
|
||
final Map<String, dynamic> fullState;
|
||
|
||
SnapshotCommand({
|
||
required this.fullState,
|
||
DateTime? timestamp,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
@override
|
||
void execute() {
|
||
// 保存完整状态
|
||
}
|
||
|
||
@override
|
||
void undo() {
|
||
// 快照不支持撤销
|
||
}
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'type': 'snapshot',
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
'fullState': fullState,
|
||
};
|
||
}
|
||
|
||
factory SnapshotCommand.fromJson(Map<String, dynamic> json) {
|
||
return SnapshotCommand(
|
||
fullState: json['fullState'] as Map<String, dynamic>,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 操作历史记录
|
||
// ============================================================================
|
||
|
||
/// 操作历史记录管理器
|
||
class OperationHistory {
|
||
final List<Command> _undoStack = [];
|
||
final List<Command> _redoStack = [];
|
||
|
||
/// 最大历史记录数 (移动端建议:50-100)
|
||
final int maxStackSize;
|
||
|
||
/// 快照间隔 (每 N 次操作生成一个快照)
|
||
final int snapshotInterval;
|
||
|
||
/// 当前操作计数 (用于快照生成)
|
||
int _operationCount = 0;
|
||
|
||
OperationHistory({
|
||
this.maxStackSize = 50,
|
||
this.snapshotInterval = 100,
|
||
});
|
||
|
||
/// 添加操作到历史记录
|
||
void push(Command command) {
|
||
// 执行命令
|
||
command.execute();
|
||
|
||
// 添加到撤销栈
|
||
_undoStack.add(command);
|
||
_operationCount++;
|
||
|
||
// 清空重做栈 (新的操作使重做无效)
|
||
_redoStack.clear();
|
||
|
||
// 检查是否需要生成快照
|
||
if (_operationCount % snapshotInterval == 0) {
|
||
_generateSnapshot();
|
||
}
|
||
|
||
// 限制栈大小
|
||
if (_undoStack.length > maxStackSize) {
|
||
_undoStack.removeAt(0);
|
||
}
|
||
}
|
||
|
||
/// 撤销操作
|
||
Command? undo() {
|
||
if (_undoStack.isEmpty) {
|
||
return null;
|
||
}
|
||
|
||
final command = _undoStack.removeLast();
|
||
command.undo();
|
||
_redoStack.add(command);
|
||
|
||
return command;
|
||
}
|
||
|
||
/// 重做操作
|
||
Command? redo() {
|
||
if (_redoStack.isEmpty) {
|
||
return null;
|
||
}
|
||
|
||
final command = _redoStack.removeLast();
|
||
command.execute();
|
||
_undoStack.add(command);
|
||
|
||
return command;
|
||
}
|
||
|
||
/// 是否可以撤销
|
||
bool get canUndo => _undoStack.isNotEmpty;
|
||
|
||
/// 是否可以重做
|
||
bool get canRedo => _redoStack.isNotEmpty;
|
||
|
||
/// 获取撤销栈大小
|
||
int get undoStackSize => _undoStack.length;
|
||
|
||
/// 获取重做栈大小
|
||
int get redoStackSize => _redoStack.length;
|
||
|
||
/// 清除历史记录
|
||
void clear() {
|
||
_undoStack.clear();
|
||
_redoStack.clear();
|
||
_operationCount = 0;
|
||
}
|
||
|
||
/// 生成快照
|
||
void _generateSnapshot() {
|
||
// 快照由外部提供完整状态
|
||
// 这里只生成标记
|
||
}
|
||
|
||
/// 从快照恢复
|
||
void restoreFromSnapshot(Map<String, dynamic> snapshot) {
|
||
clear();
|
||
// 恢复状态由外部处理
|
||
}
|
||
|
||
/// 序列化为 JSON
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'undoStack': _undoStack.map((c) => c.toJson()).toList(),
|
||
'redoStack': _redoStack.map((c) => c.toJson()).toList(),
|
||
'operationCount': _operationCount,
|
||
};
|
||
}
|
||
|
||
/// 从 JSON 反序列化
|
||
factory OperationHistory.fromJson(Map<String, dynamic> json) {
|
||
final history = OperationHistory(
|
||
maxStackSize: json['maxStackSize'] as int? ?? 50,
|
||
snapshotInterval: json['snapshotInterval'] as int? ?? 100,
|
||
);
|
||
|
||
final undoList = json['undoStack'] as List? ?? [];
|
||
for (final cmdJson in undoList) {
|
||
history._undoStack.add(Command.fromJson(cmdJson as Map<String, dynamic>));
|
||
}
|
||
|
||
final redoList = json['redoStack'] as List? ?? [];
|
||
for (final cmdJson in redoList) {
|
||
history._redoStack.add(Command.fromJson(cmdJson as Map<String, dynamic>));
|
||
}
|
||
|
||
history._operationCount = json['operationCount'] as int? ?? 0;
|
||
|
||
return history;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 增量保存管理器
|
||
// ============================================================================
|
||
|
||
/// 增量保存管理器
|
||
///
|
||
/// 管理快照和增量日志的混合存储
|
||
class IncrementalSaveManager {
|
||
/// 操作历史记录
|
||
final OperationHistory history;
|
||
|
||
/// 当前设计状态
|
||
Map<String, dynamic>? _currentState;
|
||
|
||
/// 最后一个快照
|
||
Map<String, dynamic>? _lastSnapshot;
|
||
|
||
/// 快照后的操作日志
|
||
final List<Command> _deltaLog = [];
|
||
|
||
/// 自动保存间隔 (毫秒)
|
||
final int autoSaveInterval;
|
||
|
||
/// 是否自动保存
|
||
bool _autoSaveEnabled = false;
|
||
|
||
IncrementalSaveManager({
|
||
OperationHistory? history,
|
||
this.autoSaveInterval = 30000, // 30 秒
|
||
}) : history = history ?? OperationHistory();
|
||
|
||
/// 设置当前状态
|
||
void setCurrentState(Map<String, dynamic> state) {
|
||
_currentState = state;
|
||
}
|
||
|
||
/// 记录操作
|
||
void recordOperation(Command command) {
|
||
history.push(command);
|
||
_deltaLog.add(command);
|
||
|
||
// 标记为脏数据
|
||
if (_currentState != null) {
|
||
_currentState!['metadata'] ??= {};
|
||
_currentState!['metadata']['isDirty'] = true;
|
||
}
|
||
}
|
||
|
||
/// 创建快照
|
||
void createSnapshot(Map<String, dynamic> fullState) {
|
||
_lastSnapshot = Map<String, dynamic>.from(fullState);
|
||
_deltaLog.clear();
|
||
|
||
// 记录快照命令
|
||
history.push(SnapshotCommand(fullState: fullState));
|
||
|
||
// 清除脏标记
|
||
fullState['metadata'] ??= {};
|
||
fullState['metadata']['isDirty'] = false;
|
||
fullState['metadata']['lastSavedAt'] = DateTime.now().millisecondsSinceEpoch;
|
||
}
|
||
|
||
/// 保存 (快照 + 增量)
|
||
IncrementalSaveData save() {
|
||
return IncrementalSaveData(
|
||
snapshot: _lastSnapshot,
|
||
deltaLog: _deltaLog.map((c) => c.toJson()).toList(),
|
||
timestamp: DateTime.now(),
|
||
);
|
||
}
|
||
|
||
/// 从保存数据恢复
|
||
Map<String, dynamic>? restore(IncrementalSaveData saveData) {
|
||
if (saveData.snapshot != null) {
|
||
_currentState = Map<String, dynamic>.from(saveData.snapshot!);
|
||
}
|
||
|
||
// 应用增量操作
|
||
for (final cmdJson in saveData.deltaLog) {
|
||
final command = Command.fromJson(cmdJson);
|
||
command.execute();
|
||
_applyCommandToState(command);
|
||
}
|
||
|
||
return _currentState;
|
||
}
|
||
|
||
/// 应用命令到状态
|
||
void _applyCommandToState(Command command) {
|
||
// 根据命令类型更新状态
|
||
switch (command.type) {
|
||
case OperationType.componentAdd:
|
||
_applyAddComponent(command as AddComponentCommand);
|
||
break;
|
||
case OperationType.componentMove:
|
||
_applyMoveComponent(command as MoveComponentCommand);
|
||
break;
|
||
case OperationType.componentRotate:
|
||
_applyRotateComponent(command as RotateComponentCommand);
|
||
break;
|
||
case OperationType.componentDelete:
|
||
_applyDeleteComponent(command as DeleteComponentCommand);
|
||
break;
|
||
case OperationType.netAdd:
|
||
_applyAddNet(command as AddNetCommand);
|
||
break;
|
||
case OperationType.propertyChange:
|
||
_applyPropertyChange(command as PropertyChangeCommand);
|
||
break;
|
||
case OperationType.snapshot:
|
||
_applySnapshot(command as SnapshotCommand);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void _applyAddComponent(AddComponentCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
final components = tables['components'] as List?;
|
||
if (components != null) {
|
||
components.add(command.component);
|
||
}
|
||
}
|
||
|
||
void _applyMoveComponent(MoveComponentCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
final components = tables['components'] as List?;
|
||
if (components != null) {
|
||
for (final comp in components) {
|
||
if (comp is Map && comp['id'] == command.componentId) {
|
||
comp['position'] ??= {};
|
||
comp['position']['x'] = command.newX;
|
||
comp['position']['y'] = command.newY;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _applyRotateComponent(RotateComponentCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
final components = tables['components'] as List?;
|
||
if (components != null) {
|
||
for (final comp in components) {
|
||
if (comp is Map && comp['id'] == command.componentId) {
|
||
comp['position'] ??= {};
|
||
comp['position']['rotation'] = command.newRotation;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _applyDeleteComponent(DeleteComponentCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
final components = tables['components'] as List?;
|
||
if (components != null) {
|
||
components.removeWhere((comp) =>
|
||
comp is Map && comp['id'] == command.componentId
|
||
);
|
||
}
|
||
}
|
||
|
||
void _applyAddNet(AddNetCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
final nets = tables['nets'] as List?;
|
||
if (nets != null) {
|
||
nets.add(command.net);
|
||
}
|
||
}
|
||
|
||
void _applyPropertyChange(PropertyChangeCommand command) {
|
||
if (_currentState == null) return;
|
||
|
||
// 查找对象并更新属性
|
||
final tables = _currentState!['tables'] as Map<String, dynamic>?;
|
||
if (tables == null) return;
|
||
|
||
for (final table in tables.values) {
|
||
if (table is List) {
|
||
for (final obj in table) {
|
||
if (obj is Map && obj['id'] == command.objectId) {
|
||
obj[command.propertyName] = command.newValue;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void _applySnapshot(SnapshotCommand command) {
|
||
_currentState = Map<String, dynamic>.from(command.fullState);
|
||
_lastSnapshot = Map<String, dynamic>.from(command.fullState);
|
||
}
|
||
|
||
/// 启用自动保存
|
||
void enableAutoSave() {
|
||
_autoSaveEnabled = true;
|
||
}
|
||
|
||
/// 禁用自动保存
|
||
void disableAutoSave() {
|
||
_autoSaveEnabled = false;
|
||
}
|
||
|
||
/// 获取当前状态
|
||
Map<String, dynamic>? get currentState => _currentState;
|
||
|
||
/// 获取最后快照
|
||
Map<String, dynamic>? get lastSnapshot => _lastSnapshot;
|
||
|
||
/// 是否有未保存的更改
|
||
bool get isDirty => _deltaLog.isNotEmpty;
|
||
|
||
/// 获取增量日志大小
|
||
int get deltaLogSize => _deltaLog.length;
|
||
}
|
||
|
||
// ============================================================================
|
||
// 保存数据
|
||
// ============================================================================
|
||
|
||
/// 增量保存数据
|
||
class IncrementalSaveData {
|
||
/// 完整快照 (可能为 null,如果是纯增量保存)
|
||
final Map<String, dynamic>? snapshot;
|
||
|
||
/// 增量操作日志
|
||
final List<Map<String, dynamic>> deltaLog;
|
||
|
||
/// 保存时间戳
|
||
final DateTime timestamp;
|
||
|
||
IncrementalSaveData({
|
||
this.snapshot,
|
||
this.deltaLog = const [],
|
||
required this.timestamp,
|
||
});
|
||
|
||
/// 序列化为 JSON 字符串
|
||
String toJsonString() {
|
||
return jsonEncode({
|
||
'snapshot': snapshot,
|
||
'deltaLog': deltaLog,
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
});
|
||
}
|
||
|
||
/// 从 JSON 字符串反序列化
|
||
factory IncrementalSaveData.fromJsonString(String jsonString) {
|
||
final json = jsonDecode(jsonString) as Map<String, dynamic>;
|
||
return IncrementalSaveData(
|
||
snapshot: json['snapshot'] as Map<String, dynamic>?,
|
||
deltaLog: (json['deltaLog'] as List? ?? [])
|
||
.map((e) => e as Map<String, dynamic>)
|
||
.toList(),
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
|
||
/// 序列化为字节 (用于文件存储)
|
||
Uint8List toBytes() {
|
||
final jsonString = toJsonString();
|
||
return Uint8List.fromList(utf8.encode(jsonString));
|
||
}
|
||
|
||
/// 从字节反序列化
|
||
factory IncrementalSaveData.fromBytes(Uint8List bytes) {
|
||
final jsonString = utf8.decode(bytes);
|
||
return IncrementalSaveData.fromJsonString(jsonString);
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 断点恢复
|
||
// ============================================================================
|
||
|
||
/// 断点恢复管理器
|
||
class CheckpointManager {
|
||
/// 检查点列表
|
||
final List<Checkpoint> _checkpoints = [];
|
||
|
||
/// 最大检查点数
|
||
final int maxCheckpoints;
|
||
|
||
CheckpointManager({this.maxCheckpoints = 10});
|
||
|
||
/// 创建检查点
|
||
void createCheckpoint(
|
||
String name,
|
||
Map<String, dynamic> state,
|
||
String reason,
|
||
) {
|
||
final checkpoint = Checkpoint(
|
||
name: name,
|
||
state: Map<String, dynamic>.from(state),
|
||
reason: reason,
|
||
timestamp: DateTime.now(),
|
||
);
|
||
|
||
_checkpoints.add(checkpoint);
|
||
|
||
// 限制检查点数量
|
||
if (_checkpoints.length > maxCheckpoints) {
|
||
_checkpoints.removeAt(0);
|
||
}
|
||
}
|
||
|
||
/// 获取最新检查点
|
||
Checkpoint? getLatestCheckpoint() {
|
||
if (_checkpoints.isEmpty) {
|
||
return null;
|
||
}
|
||
return _checkpoints.last;
|
||
}
|
||
|
||
/// 获取指定名称的检查点
|
||
Checkpoint? getCheckpoint(String name) {
|
||
for (final checkpoint in _checkpoints) {
|
||
if (checkpoint.name == name) {
|
||
return checkpoint;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// 从检查点恢复
|
||
Map<String, dynamic>? restoreFromCheckpoint(String name) {
|
||
final checkpoint = getCheckpoint(name);
|
||
return checkpoint?.state;
|
||
}
|
||
|
||
/// 从最新检查点恢复
|
||
Map<String, dynamic>? restoreFromLatest() {
|
||
final checkpoint = getLatestCheckpoint();
|
||
return checkpoint?.state;
|
||
}
|
||
|
||
/// 清除所有检查点
|
||
void clear() {
|
||
_checkpoints.clear();
|
||
}
|
||
|
||
/// 获取检查点列表
|
||
List<Checkpoint> get checkpoints => List.unmodifiable(_checkpoints);
|
||
}
|
||
|
||
/// 检查点
|
||
class Checkpoint {
|
||
final String name;
|
||
final Map<String, dynamic> state;
|
||
final String reason;
|
||
final DateTime timestamp;
|
||
|
||
Checkpoint({
|
||
required this.name,
|
||
required this.state,
|
||
required this.reason,
|
||
required this.timestamp,
|
||
});
|
||
|
||
/// 序列化为 JSON
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'name': name,
|
||
'state': state,
|
||
'reason': reason,
|
||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||
};
|
||
}
|
||
|
||
/// 从 JSON 反序列化
|
||
factory Checkpoint.fromJson(Map<String, dynamic> json) {
|
||
return Checkpoint(
|
||
name: json['name'] as String,
|
||
state: json['state'] as Map<String, dynamic>,
|
||
reason: json['reason'] as String,
|
||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||
);
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 公共 API
|
||
// ============================================================================
|
||
|
||
/// 创建增量保存管理器
|
||
IncrementalSaveManager createIncrementalSaveManager({
|
||
int maxHistorySize = 50,
|
||
int snapshotInterval = 100,
|
||
int autoSaveInterval = 30000,
|
||
}) {
|
||
final history = OperationHistory(
|
||
maxStackSize: maxHistorySize,
|
||
snapshotInterval: snapshotInterval,
|
||
);
|
||
|
||
return IncrementalSaveManager(
|
||
history: history,
|
||
autoSaveInterval: autoSaveInterval,
|
||
);
|
||
}
|
||
|
||
/// 创建断点恢复管理器
|
||
CheckpointManager createCheckpointManager({int maxCheckpoints = 10}) {
|
||
return CheckpointManager(maxCheckpoints: maxCheckpoints);
|
||
}
|