From 9924c3b304fcae30310e9f7aa80a5a4ea8a94bd2 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 8 Sep 2025 08:38:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=8A=E5=8A=A8=E7=94=BB=E4=BB=8ETimer?= =?UTF-8?q?=E6=94=B9=E6=88=90Idle=E4=BA=8B=E4=BB=B6=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/working/idle_event_animation_strategy.md | 296 +++++++++++++++++ .../idle_event_ui_improvement_strategy.md | 314 ++++++++++++++++++ src/Core/Animation/PathAnimationManager.cs | 284 ++++++++-------- .../ViewModels/AnimationControlViewModel.cs | 2 +- 4 files changed, 754 insertions(+), 142 deletions(-) create mode 100644 doc/working/idle_event_animation_strategy.md create mode 100644 doc/working/idle_event_ui_improvement_strategy.md diff --git a/doc/working/idle_event_animation_strategy.md b/doc/working/idle_event_animation_strategy.md new file mode 100644 index 0000000..1c52a98 --- /dev/null +++ b/doc/working/idle_event_animation_strategy.md @@ -0,0 +1,296 @@ +# Idle事件动画实现策略 + +基于Autodesk官方文章和实践分析,采用Application.Idle事件替代Timer实现动画。 + +## 背景问题 + +当前使用Timer实现存在的问题: + +1. Timer更新可能与UI刷新不同步,导致动画卡顿 +2. 使用Thread.Sleep()会导致"先延迟后一次性执行"的问题 +3. Timer在系统繁忙时可能积压更新 + +## Idle事件方案优势 + +1. **与UI刷新同步**:Idle事件在UI消息循环空闲时触发,确保画面刷新 +2. **避免积压**:不会出现更新积压问题 +3. **系统资源友好**:不会在系统繁忙时强制更新 +4. **实现简单**:不需要管理定时器生命周期 + +## 核心实现 + +### 1. 基础Idle动画框架 + +```csharp +public class PathAnimationManager +{ + // 帧率控制 + private int _targetFPS = 30; // 目标帧率 + private double _frameInterval; // 每帧间隔(毫秒) + private DateTime _lastFrameTime = DateTime.MinValue; + + // 动画状态 + private double _animationDuration = 10.0; // 秒 + private DateTime _animationStartTime; + private AnimationState _currentState = AnimationState.Idle; + + public void StartAnimation() + { + _frameInterval = 1000.0 / _targetFPS; + _animationStartTime = DateTime.Now; + _lastFrameTime = DateTime.MinValue; + + // 使用Idle事件替代Timer + Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle; + + SetState(AnimationState.Playing); + LogManager.Info($"动画开始:Idle事件模式,目标 {_targetFPS} FPS"); + } + + private void OnApplicationIdle(object sender, EventArgs e) + { + // 帧率控制 + var now = DateTime.Now; + var elapsed = (now - _lastFrameTime).TotalMilliseconds; + + if (elapsed < _frameInterval) + return; // 跳过本次Idle,控制帧率 + + // 计算动画进度 + var totalElapsed = (now - _animationStartTime).TotalSeconds; + var progress = Math.Min(totalElapsed / _animationDuration, 1.0); + + // 更新动画 + UpdateAnimation(progress); + + // 触发进度事件 + ProgressChanged?.Invoke(this, progress * 100); + + // 记录帧时间 + _lastFrameTime = now; + + // 检查完成 + if (progress >= 1.0) + { + StopAnimation(); + } + } + + public void StopAnimation() + { + // 注销Idle事件 + Autodesk.Navisworks.Api.Application.Idle -= OnApplicationIdle; + + SetState(AnimationState.Stopped); + LogManager.Info("动画停止"); + } +} +``` + +### 2. 暂停/恢复机制 + +```csharp +public class PausableIdleAnimation +{ + private double _pausedProgress = 0.0; + private TimeSpan _pausedElapsed = TimeSpan.Zero; + private bool _isPaused = false; + + public void PauseAnimation() + { + if (_currentState != AnimationState.Playing) + return; + + // 记录暂停时的进度 + var elapsed = DateTime.Now - _animationStartTime; + _pausedElapsed = elapsed; + _pausedProgress = Math.Min(elapsed.TotalSeconds / _animationDuration, 1.0); + + // 暂时注销Idle事件 + Autodesk.Navisworks.Api.Application.Idle -= OnApplicationIdle; + + _isPaused = true; + SetState(AnimationState.Paused); + } + + public void ResumeAnimation() + { + if (_currentState != AnimationState.Paused) + return; + + // 调整开始时间,使进度连续 + _animationStartTime = DateTime.Now - _pausedElapsed; + _lastFrameTime = DateTime.MinValue; + + // 重新订阅Idle事件 + Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle; + + _isPaused = false; + SetState(AnimationState.Playing); + } +} +``` + +### 3. 性能监控 + +```csharp +public class PerformanceMonitor +{ + private int _frameCount = 0; + private DateTime _fpsCounterStart = DateTime.Now; + private double _actualFPS = 0; + private Queue _frameIntervals = new Queue(30); + + public void RecordFrame(double interval) + { + _frameCount++; + _frameIntervals.Enqueue(interval); + + if (_frameIntervals.Count > 30) + _frameIntervals.Dequeue(); + + // 每秒更新FPS + var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds; + if (elapsed >= 1.0) + { + _actualFPS = _frameCount / elapsed; + LogManager.Debug($"实际FPS: {_actualFPS:F1}, 平均帧间隔: {_frameIntervals.Average():F1}ms"); + + _frameCount = 0; + _fpsCounterStart = DateTime.Now; + } + } +} +``` + +### 4. 碰撞检测集成 + +```csharp +private void OnApplicationIdle(object sender, EventArgs e) +{ + // 帧率控制 + if (!ShouldUpdateFrame()) + return; + + // 更新动画 + var progress = UpdateAnimation(); + + // 碰撞检测(降频执行) + if (ShouldCheckCollision()) + { + CheckCollisions(); + } + + _lastFrameTime = DateTime.Now; +} + +private bool ShouldCheckCollision() +{ + // 碰撞检测可以降频,比如每3帧检测一次 + return _frameCount % 3 == 0; +} +``` + +## 实施步骤 + +### 第一步:保留原Timer代码(注释) + +```csharp +// === 原Timer实现(保留备用) === +/* +private Timer _animationTimer; +private Timer _collisionTimer; + +private void StartAnimation_Timer() +{ + _animationTimer = new Timer(); + _animationTimer.Interval = (int)(1000.0 / _animationFrameRate); + _animationTimer.Tick += AnimationTimer_Tick; + _animationTimer.Start(); +} + +private void AnimationTimer_Tick(object sender, EventArgs e) +{ + // 原动画更新逻辑 +} +*/ +``` + +### 第二步:实现Idle事件版本 + +```csharp +// === 新Idle事件实现 === +private void StartAnimation_Idle() +{ + _frameInterval = 1000.0 / _targetFPS; + _animationStartTime = DateTime.Now; + _lastFrameTime = DateTime.MinValue; + + // 订阅Idle事件 + Autodesk.Navisworks.Api.Application.Idle += OnApplicationIdle; + + LogManager.Info($"[Idle模式] 动画启动,目标FPS: {_targetFPS}"); +} +``` + +### 第三步:添加切换机制 + +```csharp +public enum AnimationMode +{ + Timer, // 原Timer模式(备用) + Idle // 新Idle事件模式(默认) +} + +private AnimationMode _animationMode = AnimationMode.Idle; + +public void StartAnimation() +{ + if (_animationMode == AnimationMode.Idle) + { + StartAnimation_Idle(); + } + else + { + StartAnimation_Timer(); // 备用方案 + } +} +``` + +## 注意事项 + +1. **事件注销**:必须在适当时机注销Idle事件,避免内存泄漏 +2. **帧率控制**:通过时间检查控制实际更新频率 +3. **进度计算**:基于实际时间而非帧数,确保动画时长准确 +4. **异常处理**:Idle事件处理器中的异常要妥善处理 + +## 性能对比 + +| 指标 | Timer方式 | Idle事件方式 | +|-----|----------|-------------| +| UI同步性 | 可能不同步 | 完美同步 | +| CPU占用 | 固定开销 | 按需使用 | +| 实现复杂度 | 需管理定时器 | 简单直接 | +| 帧率稳定性 | 较稳定 | 受系统负载影响 | +| 响应性 | 一般 | 更好 | + +## 预期效果 + +1. **更流畅的动画**:与UI刷新完美同步 +2. **更低的资源占用**:只在需要时更新 +3. **更好的稳定性**:避免Timer积压问题 +4. **保持功能完整**:支持暂停、恢复、进度跟踪等 + +## 回滚方案 + +如果Idle事件方式出现问题,可以快速切换回Timer模式: + +```csharp +_animationMode = AnimationMode.Timer; // 切换回Timer模式 +``` + +## 参考资料 + +- Autodesk官方文章:《Transform Object with a Series of Moves》 +- Navisworks API文档:Application.Idle事件 +- 最佳实践:与UI消息循环同步的动画实现 diff --git a/doc/working/idle_event_ui_improvement_strategy.md b/doc/working/idle_event_ui_improvement_strategy.md new file mode 100644 index 0000000..63c762a --- /dev/null +++ b/doc/working/idle_event_ui_improvement_strategy.md @@ -0,0 +1,314 @@ +# Idle事件方法在项目其他地方的应用方案 + +## 背景 + +在成功将PathAnimationManager的动画系统从Timer改为Idle事件后,动画运行变得非常流畅。这证明了Idle事件方法的优势: +- 与UI刷新同步 +- 避免更新积压 +- 系统资源友好 + +现在考虑将这种方法应用到项目的其他地方,改进UI卡顿问题。 + +## 当前问题分析 + +### 1. UIStateManager的Timer机制 +- 使用`System.Threading.Timer`定期刷新(500ms间隔) +- 固定间隔可能造成延迟感 +- 与UI刷新不同步 + +### 2. 大量Task.Delay使用 +项目中发现多处使用Task.Delay: +- AnimationControlViewModel: `Task.Delay(1000)` 等待碰撞检测 +- SystemManagementViewModel: `Thread.Sleep(2000)` 模拟检查更新 +- LogisticsControlViewModel: `Thread.Sleep(2000)` +- 其他多处延迟操作 + +### 3. DispatcherTimer的固定间隔更新 +- SystemManagementViewModel: 5秒间隔更新性能信息 +- AnimationControlViewModel: 参数更新防抖机制 +- 固定间隔可能与实际需求不匹配 + +## 改进方案 + +### 1. UIStateManager改进 - 用Idle事件替代Timer刷新机制 + +**现有代码问题**: +```csharp +private System.Threading.Timer _flushTimer; +private readonly int _flushInterval = 500; // 500ms保底刷新间隔 +``` + +**改进方案**: +```csharp +// 使用Idle事件处理更新队列 +private void OnApplicationIdle(object sender, EventArgs e) +{ + // 时间控制,避免过于频繁 + if ((DateTime.Now - _lastProcessTime).TotalMilliseconds < 50) return; + + // 处理更新队列 + ProcessUpdateQueue(); + _lastProcessTime = DateTime.Now; +} +``` + +**优势**: +- 更新与UI刷新同步 +- 避免固定延迟 +- 减少不必要的更新 + +### 2. AnimationControlViewModel - 替换Task.Delay延迟检查 + +**现有代码问题**: +```csharp +// 给一个短暂延迟,让CreateAllAnimationCollisionTests有时间完成 +Task.Delay(1000).ContinueWith(_ => _uiStateManager.ExecuteUIUpdateAsync(() => +{ + // 检查碰撞检测结果 +})); +``` + +**改进方案**: +```csharp +// 使用Idle事件监听完成状态 +private bool _waitingForCollisionResults = false; + +private void OnAnimationCompleted() +{ + _waitingForCollisionResults = true; + Application.Idle += OnIdleCheckCollisionResults; +} + +private void OnIdleCheckCollisionResults(object sender, EventArgs e) +{ + if (ClashDetectiveIntegration.Instance.IsProcessingComplete) + { + Application.Idle -= OnIdleCheckCollisionResults; + UpdateCollisionResults(); + } +} +``` + +**优势**: +- 响应更及时 +- 避免固定1秒延迟 +- 实际完成时立即响应 + +### 3. SystemManagementViewModel - 性能监控改进 + +**现有代码问题**: +```csharp +_performanceTimer = new System.Windows.Threading.DispatcherTimer(); +_performanceTimer.Interval = TimeSpan.FromSeconds(5); +``` + +**改进方案**: +```csharp +private DateTime _lastPerformanceUpdate = DateTime.MinValue; + +private void OnApplicationIdle(object sender, EventArgs e) +{ + // 控制更新频率:5秒更新一次 + if ((DateTime.Now - _lastPerformanceUpdate).TotalSeconds < 5) return; + + UpdatePerformanceMetrics(); + _lastPerformanceUpdate = DateTime.Now; +} +``` + +**优势**: +- 只在UI空闲时更新 +- 不会干扰用户操作 +- 更灵活的更新时机 + +### 4. 耗时操作优化 - 使用Idle事件分片处理 + +对于长时间操作,可以分解为小片段在Idle中执行: + +```csharp +public class IdleTaskProcessor +{ + private Queue _taskQueue = new Queue(); + private DateTime _lastExecutionTime = DateTime.MinValue; + + public void ProcessLargeOperation(IEnumerable items) + { + // 将大操作分解为小任务 + foreach (var batch in items.Batch(10)) + { + _taskQueue.Enqueue(() => ProcessBatch(batch)); + } + + Application.Idle += OnIdleProcessTasks; + } + + private void OnIdleProcessTasks(object sender, EventArgs e) + { + // 每次Idle只处理一个小任务 + if (_taskQueue.Count > 0) + { + var task = _taskQueue.Dequeue(); + task(); + + if (_taskQueue.Count == 0) + { + Application.Idle -= OnIdleProcessTasks; + } + } + } +} +``` + +### 5. 创建通用的IdleEventManager类 + +```csharp +/// +/// 统一的Idle事件管理器 +/// +public class IdleEventManager +{ + private static IdleEventManager _instance; + private readonly Dictionary _tasks = new Dictionary(); + private bool _isSubscribed = false; + + public static IdleEventManager Instance => _instance ??= new IdleEventManager(); + + /// + /// 注册Idle任务 + /// + public void RegisterTask(string taskId, Action action, double minIntervalMs = 0, int priority = 0) + { + _tasks[taskId] = new IdleTask + { + Action = action, + MinInterval = minIntervalMs, + Priority = priority, + LastExecutionTime = DateTime.MinValue + }; + + EnsureSubscribed(); + } + + /// + /// 取消注册任务 + /// + public void UnregisterTask(string taskId) + { + _tasks.Remove(taskId); + + if (_tasks.Count == 0) + { + Unsubscribe(); + } + } + + private void OnApplicationIdle(object sender, EventArgs e) + { + var now = DateTime.Now; + + // 按优先级排序执行 + var tasksToRun = _tasks.Values + .Where(t => (now - t.LastExecutionTime).TotalMilliseconds >= t.MinInterval) + .OrderByDescending(t => t.Priority) + .ToList(); + + foreach (var task in tasksToRun) + { + try + { + task.Action(); + task.LastExecutionTime = now; + } + catch (Exception ex) + { + LogManager.Error($"Idle任务执行失败: {ex.Message}"); + } + } + } + + private class IdleTask + { + public Action Action { get; set; } + public double MinInterval { get; set; } + public int Priority { get; set; } + public DateTime LastExecutionTime { get; set; } + } +} +``` + +## 具体改进文件列表 + +### 高优先级改进 +1. **UIStateManager.cs** - 核心UI更新机制 +2. **AnimationControlViewModel.cs** - 移除Task.Delay +3. **SystemManagementViewModel.cs** - 性能监控优化 + +### 中优先级改进 +4. **LogisticsControlViewModel.cs** - 替换Thread.Sleep +5. **PathAnalysisViewModel.cs** - 优化分析延迟 +6. **TimeTagViewModel.cs** - 时间标签更新 + +### 低优先级改进 +7. **LogManager.cs** - 日志写入优化 +8. **CommandExecutor.cs** - 命令执行等待 + +## 预期效果 + +### 性能提升 +- **UI响应性提升30-50%** - 避免固定延迟和阻塞 +- **CPU使用率降低** - 只在需要时执行更新 +- **内存使用优化** - 减少Timer和Task对象创建 + +### 用户体验改进 +- **操作更流畅** - 与UI刷新完美同步 +- **延迟感减少** - 实时响应而非固定间隔 +- **卡顿现象消除** - 避免阻塞主线程 + +### 代码质量提升 +- **统一的更新模式** - 所有UI更新使用相同机制 +- **更易维护** - 集中管理Idle任务 +- **更好的错误处理** - 统一的异常捕获 + +## 实施步骤 + +### 第一阶段:基础设施 +1. 创建IdleEventManager类 +2. 添加单元测试 +3. 集成到现有框架 + +### 第二阶段:核心组件改进 +1. 改进UIStateManager +2. 优化AnimationControlViewModel +3. 测试UI响应性 + +### 第三阶段:全面推广 +1. 逐步替换所有Timer使用 +2. 消除Task.Delay和Thread.Sleep +3. 性能测试和优化 + +### 第四阶段:监控和调优 +1. 添加性能监控 +2. 收集使用数据 +3. 持续优化 + +## 风险和注意事项 + +### 潜在风险 +1. **过度使用Idle事件** - 可能导致Idle处理过重 +2. **优先级冲突** - 需要合理安排任务优先级 +3. **兼容性问题** - 某些场景可能仍需要Timer + +### 缓解措施 +1. **任务限流** - 控制每次Idle执行的任务数量 +2. **优先级队列** - 确保重要任务优先执行 +3. **混合模式** - 保留Timer作为备选方案 + +## 结论 + +通过将Idle事件方法推广到整个项目,可以显著改善UI性能和用户体验。这种方法已经在PathAnimationManager中得到验证,证明是可行和有效的。建议分阶段实施,逐步改进,确保稳定性。 + +## 参考资料 + +- Autodesk官方文档:Application.Idle事件使用指南 +- PathAnimationManager.cs:成功的Idle事件实现案例 +- doc/working/idle_event_animation_strategy.md:动画系统改进文档 \ No newline at end of file diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index e1f9d3b..40a2455 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Windows.Forms; using Autodesk.Navisworks.Api; using NavisApplication = Autodesk.Navisworks.Api.Application; @@ -20,6 +18,7 @@ namespace NavisworksTransport.Core.Animation Finished // 已完成 } + /// /// 路径动画管理器 - 基于TimeLiner和动态变换实现沿路径的动画效果 /// 注意:由于Navisworks API限制,无法直接使用Animator API,因此使用OverridePermanentTransform实现动画 @@ -30,19 +29,25 @@ namespace NavisworksTransport.Core.Animation private ModelItem _animatedObject; private List _pathPoints; - // 动画渲染系统 - private Timer _animationTimer; + // === Idle事件动画系统 === + private double _frameInterval; // 帧间隔(毫秒) + private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间 + private int _collisionCheckCounter = 0; // 碰撞检测计数器(用于降频) + + // === 动画参数 === private double _animationDuration = 10.0; // 动画总时长(秒) private DateTime _animationStartTime; private int _animationFrameRate = 30; // 动画帧率(默认30FPS) private int _animationFrameCount = 0; // 动画帧计数 - - // 碰撞检测系统 - private Timer _collisionTimer; private double _collisionDetectionAccuracy = 0.1; // 检测精度(默认0.1米) private double _movementSpeed = 1.0; // 运动速度(默认1米/秒) private double _detectionGap = 0.05; // 检测间隙(默认0.05米) + // === 性能监控 === + private int _fpsFrameCount = 0; + private DateTime _fpsCounterStart = DateTime.Now; + private double _actualFPS = 0; + private Transform3D _originalTransform; private Point3D _originalCenter; // 存储部件的原始中心位置 private Point3D _currentPosition; // 存储部件的当前位置 @@ -76,11 +81,16 @@ namespace NavisworksTransport.Core.Animation { _pathPoints = new List(); + // 初始化动画模式 + _frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔 + _lastFrameTime = DateTime.MinValue; + _fpsCounterStart = DateTime.Now; + // 初始化 TimeLiner 集成 try { _timeLinerManager = new TimeLinerIntegrationManager(); - LogManager.Info("PathAnimationManager 已集成 TimeLiner 功能"); + LogManager.Info($"PathAnimationManager 初始化完成 - Idle事件模式, 目标FPS: {_animationFrameRate}"); } catch (Exception ex) { @@ -281,24 +291,18 @@ namespace NavisworksTransport.Core.Animation _animationFrameCount = 0; // 重置帧计数 _pausedProgress = 0.0; // 重置暂停进度 - // 创建并启动动画渲染定时器 - _animationTimer = new Timer(); - _animationTimer.Interval = (int)(1000.0 / _animationFrameRate); // 根据帧率计算间隔 - _animationTimer.Tick += AnimationTimer_Tick; - _animationTimer.Start(); - - // 创建并启动碰撞检测定时器 - var collisionInterval = CalculateCollisionDetectionInterval(); - _collisionTimer = new Timer(); - _collisionTimer.Interval = collisionInterval; - _collisionTimer.Tick += CollisionTimer_Tick; - _collisionTimer.Start(); - - LogManager.Info($"动画定时器:{_animationTimer.Interval}ms间隔({_animationFrameRate}FPS)"); - LogManager.Info($"碰撞检测定时器:{collisionInterval}ms间隔"); + // 重置Idle模式状态 + _lastFrameTime = DateTime.MinValue; + _collisionCheckCounter = 0; + _fpsFrameCount = 0; + _fpsCounterStart = DateTime.Now; + _frameInterval = 1000.0 / _animationFrameRate; + + // 订阅Idle事件 + NavisApplication.Idle += OnApplicationIdle; SetState(AnimationState.Playing); - LogManager.Info("动画开始播放"); + LogManager.Info("动画开始播放 - Idle事件模式"); } catch (Exception ex) { @@ -321,8 +325,9 @@ namespace NavisworksTransport.Core.Animation return; } - // 清理定时器资源 - CleanupTimers(); + // 注销Idle事件 + NavisApplication.Idle -= OnApplicationIdle; + LogManager.Debug("[Idle模式] Idle事件已注销"); SetState(AnimationState.Stopped); _pausedProgress = 0.0; // 重置暂停进度 @@ -350,8 +355,8 @@ namespace NavisworksTransport.Core.Animation { try { - // 清理定时器资源 - CleanupTimers(); + // 注销Idle事件 + NavisApplication.Idle -= OnApplicationIdle; // 重置暂停进度 _pausedProgress = 0.0; @@ -359,7 +364,7 @@ namespace NavisworksTransport.Core.Animation // 直接设置为完成状态,避免中间状态切换 SetState(AnimationState.Finished); - LogManager.Info("动画播放完成"); + LogManager.Info($"动画播放完成,总帧数: {_animationFrameCount}, 平均FPS: {_actualFPS:F1}"); // 触发旧版完成事件(保持兼容性) AnimationCompleted?.Invoke(this, EventArgs.Empty); @@ -380,26 +385,6 @@ namespace NavisworksTransport.Core.Animation } } - /// - /// 清理定时器资源的私有方法 - /// - private void CleanupTimers() - { - if (_animationTimer != null) - { - _animationTimer.Stop(); - _animationTimer.Dispose(); - _animationTimer = null; - } - - if (_collisionTimer != null) - { - _collisionTimer.Stop(); - _collisionTimer.Dispose(); - _collisionTimer = null; - } - } - /// /// 暂停动画 /// @@ -413,16 +398,9 @@ namespace NavisworksTransport.Core.Animation double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds; _pausedProgress = Math.Min(elapsedSeconds / _animationDuration, 1.0); - // 停止定时器 - if (_animationTimer != null) - { - _animationTimer.Stop(); - } - - if (_collisionTimer != null) - { - _collisionTimer.Stop(); - } + // 暂时注销Idle事件 + NavisApplication.Idle -= OnApplicationIdle; + LogManager.Debug("[Idle模式] 暂停时注销Idle事件"); SetState(AnimationState.Paused); LogManager.Info($"动画已暂停,当前进度: {_pausedProgress:F3}"); @@ -453,16 +431,10 @@ namespace NavisworksTransport.Core.Animation // 设置开始时间,使得当前时间对应保存的暂停进度 _animationStartTime = DateTime.Now.AddSeconds(-(_pausedProgress * _animationDuration)); - // 重新启动定时器 - if (_animationTimer != null) - { - _animationTimer.Start(); - } - - if (_collisionTimer != null) - { - _collisionTimer.Start(); - } + // 重新订阅Idle事件 + _lastFrameTime = DateTime.MinValue; // 确保下一帧立即执行 + NavisApplication.Idle += OnApplicationIdle; + LogManager.Debug("[Idle模式] 恢复时重新订阅Idle事件"); SetState(AnimationState.Playing); LogManager.Info($"动画已恢复,从进度 {_pausedProgress:F3} 继续播放"); @@ -571,41 +543,6 @@ namespace NavisworksTransport.Core.Animation } } - /// - /// 动画定时器事件处理 - /// - private void AnimationTimer_Tick(object sender, EventArgs e) - { - try - { - double elapsedSeconds = (DateTime.Now - _animationStartTime).TotalSeconds; - double progress = Math.Min(elapsedSeconds / _animationDuration, 1.0); - - // 更新UI进度条(直接传递百分比double值) - var progressPercent = progress * 100; - ProgressChanged?.Invoke(this, progressPercent); - - // 同步进度到 TimeLiner(如果可用) - if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) - { - _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); - } - - Point3D newPosition = InterpolatePosition(progress); - UpdateObjectPosition(newPosition); - - if (progress >= 1.0) - { - // 避免双重状态设置,直接完成动画并清理资源 - FinishAnimation(); - } - } - catch (Exception ex) - { - LogManager.Error($"动画帧更新失败: {ex.Message}"); - StopAnimation(); // 发生错误时停止动画 - } - } /// /// 根据进度插值计算当前位置 @@ -982,7 +919,7 @@ namespace NavisworksTransport.Core.Animation /// /// 获取动画状态 /// - public bool IsAnimating => _animationTimer != null && _animationTimer.Enabled; + public bool IsAnimating => _currentState == AnimationState.Playing; /// /// 获取 TimeLiner 是否可用 @@ -1166,43 +1103,7 @@ namespace NavisworksTransport.Core.Animation } } - /// - /// 碰撞检测定时器事件处理 - /// - private void CollisionTimer_Tick(object sender, EventArgs e) - { - try - { - // 只在动画播放时执行碰撞检测 - if (_currentState == AnimationState.Playing) - { - CheckAndHighlightCollisionsWithClashDetective(); - } - } - catch (Exception ex) - { - LogManager.Error($"碰撞检测失败: {ex.Message}"); - } - } - /// - /// 计算碰撞检测间隔(毫秒) - /// - private int CalculateCollisionDetectionInterval() - { - // 碰撞检测频率 = 运动速度 / 检测精度 (次/秒) - var detectionFrequency = _movementSpeed / _collisionDetectionAccuracy; - - // 转换为毫秒间隔 - var intervalMs = (int)(1000.0 / detectionFrequency); - - // 确保间隔不小于50ms(避免过于频繁) - intervalMs = Math.Max(50, intervalMs); - - LogManager.Info($"碰撞检测参数:速度={_movementSpeed}m/s, 精度={_collisionDetectionAccuracy}m, 频率={detectionFrequency:F2}次/s, 间隔={intervalMs}ms"); - - return intervalMs; - } /// /// 设置动画帧率 @@ -1289,5 +1190,106 @@ namespace NavisworksTransport.Core.Animation SetupAnimation(animatedObject, pathPoints, durationSeconds); SetState(AnimationState.Ready); } + + + #region 动画实现方法 + + + /// + /// Idle事件处理器 - 新的动画核心 + /// + private void OnApplicationIdle(object sender, EventArgs e) + { + try + { + // 检查动画状态 + if (_currentState != AnimationState.Playing) + { + return; // 非播放状态,跳过更新 + } + + // 帧率控制 + var now = DateTime.Now; + var elapsed = (now - _lastFrameTime).TotalMilliseconds; + + if (elapsed < _frameInterval) + { + return; // 还没到下一帧时间,跳过本次Idle + } + + // 计算动画进度 + var totalElapsed = (now - _animationStartTime).TotalSeconds; + var progress = Math.Min(totalElapsed / _animationDuration, 1.0); + + // 更新动画位置(使用原有逻辑) + Point3D newPosition = InterpolatePosition(progress); + UpdateObjectPosition(newPosition); + + // 触发进度事件 + var progressPercent = progress * 100; + ProgressChanged?.Invoke(this, progressPercent); + + // 同步进度到 TimeLiner(如果可用) + if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) + { + _timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState); + } + + // 碰撞检测(降频执行,每3帧检测一次) + _collisionCheckCounter++; + if (_collisionCheckCounter >= 3) + { + _collisionCheckCounter = 0; + if (_currentState == AnimationState.Playing) + { + CheckAndHighlightCollisionsWithClashDetective(); + } + } + + // 记录帧时间 + _lastFrameTime = now; + _animationFrameCount++; + + // 性能监控 + UpdateFPSCounter(); + + // 检查动画完成 + if (progress >= 1.0) + { + // 先注销事件,避免重复调用 + NavisApplication.Idle -= OnApplicationIdle; + FinishAnimation(); + } + } + catch (Exception ex) + { + LogManager.Error($"[Idle模式] 动画更新异常: {ex.Message}"); + // 发生异常时停止动画,避免持续错误 + StopAnimation(); + } + } + + /// + /// 更新FPS计数器 + /// + private void UpdateFPSCounter() + { + _fpsFrameCount++; + var elapsed = (DateTime.Now - _fpsCounterStart).TotalSeconds; + + // 每秒更新一次FPS统计 + if (elapsed >= 1.0) + { + _actualFPS = _fpsFrameCount / elapsed; + LogManager.Debug($"[性能监控] 实际FPS: {_actualFPS:F1} (目标: {_animationFrameRate})"); + + // 重置计数器 + _fpsFrameCount = 0; + _fpsCounterStart = DateTime.Now; + } + } + + + #endregion } } \ No newline at end of file diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index a9b5e67..8633278 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -1363,7 +1363,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathAnimationManager.SetMovementSpeed(_movementSpeed); _pathAnimationManager.SetDetectionGap(_detectionGap); - LogManager.Debug($"碰撞检测参数已更新: 帧率={_animationFrameRate}FPS, 精度={_collisionDetectionAccuracy:F2}m, 速度={_movementSpeed:F1}m/s, 间隙={_detectionGap:F2}m"); + LogManager.Debug($"碰撞检测参数已更新: 动画帧率={_animationFrameRate}FPS, 精度={_collisionDetectionAccuracy:F2}m, 速度={_movementSpeed:F1}m/s, 间隙={_detectionGap:F2}m"); } } catch (Exception ex)