diff --git a/MainViewModel.cs b/MainViewModel.cs index c1f4563..6c39140 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -18,6 +18,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Xceed.Wpf.Toolkit.PropertyGrid; using CtrEditor.ObjetosSim.Extraccion_Datos; using ClosedXML.Excel; +using System.Timers; // Para el nuevo timer de simulación más preciso using CtrEditor.PopUps; using System.Windows.Data; using CommunityToolkit.Mvvm.Input; @@ -44,13 +45,22 @@ namespace CtrEditor private int simSampleCount; private int plcSampleCount; + // Sistema adaptativo para timing de PLC + private double lastPlcExecutionTime = 0; + private double maxPlcExecutionTime = 0; + private int plcTimingAdaptationCounter = 0; + private const int PLC_ADAPTATION_SAMPLES = 10; // Evaluar cada 10 muestras + private const double MIN_PLC_INTERVAL = 10; // Mínimo intervalo PLC (ms) + private const double MAX_PLC_INTERVAL = 100; // Máximo intervalo PLC (ms) + private const double SIM_PRIORITY_TIME = 10; // Tiempo reservado para simulación (ms) + private float TiempoDesdeStartSimulacion; private bool Debug_SimulacionCreado = false; public SimulationManagerBEPU simulationManager = new SimulationManagerBEPU(); - private readonly DispatcherTimer _timerSimulacion; - private readonly DispatcherTimer _timerPLCUpdate; + private readonly System.Timers.Timer _timerSimulacion; // Cambiado a System.Timers.Timer para mejor precisión + private readonly System.Timers.Timer _timerPLCUpdate; // Cambiado a System.Timers.Timer para mejor precisión private readonly DispatcherTimer _timerDisplayUpdate; private readonly DispatcherTimer _timer3DUpdate; // Nuevo timer para actualización 3D cuando simulación está detenida @@ -380,17 +390,21 @@ namespace CtrEditor // Inicializa el PLCViewModel PLCViewModel = new PLCViewModel(); - _timerPLCUpdate = new DispatcherTimer(); - _timerPLCUpdate.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms - _timerPLCUpdate.Tick += OnRefreshEvent; + _timerPLCUpdate = new System.Timers.Timer(); + _timerPLCUpdate.Interval = 10; // 10ms - más preciso que DispatcherTimer + _timerPLCUpdate.Elapsed += OnRefreshEvent; + _timerPLCUpdate.AutoReset = true; // Reinicio automático + _timerPLCUpdate.SynchronizingObject = null; // No sincronizar automáticamente con UI InitializeTipoSimulableList(); ItemDoubleClickCommand = new ParameterizedRelayCommand(ExecuteDoubleClick); - _timerSimulacion = new DispatcherTimer(); - _timerSimulacion.Interval = TimeSpan.FromMilliseconds(10); // Restaurado a 10ms - _timerSimulacion.Tick += OnTickSimulacion; + _timerSimulacion = new System.Timers.Timer(); + _timerSimulacion.Interval = 10; // 10ms - más preciso que DispatcherTimer + _timerSimulacion.Elapsed += OnTickSimulacion; + _timerSimulacion.AutoReset = true; // Reinicio automático + _timerSimulacion.SynchronizingObject = null; // No sincronizar automáticamente con UI // Nuevo timer para actualización de display _timerDisplayUpdate = new DispatcherTimer(); @@ -1104,6 +1118,7 @@ namespace CtrEditor Debug_SimulacionCreado = false; } _timerSimulacion.Stop(); + _timerPLCUpdate.Stop(); // También detener el timer PLC // Restaurar los rectángulos de selección si hay objetos seleccionados _objectManager.UpdateSelectionVisuals(); @@ -1114,39 +1129,55 @@ namespace CtrEditor - private void OnTickSimulacion(object sender, EventArgs e) + private void OnTickSimulacion(object sender, System.Timers.ElapsedEventArgs e) { - var stopwatch = Stopwatch.StartNew(); // Start measuring time - - // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos - var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last; - stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds; - - // Acumular tiempo para el promedio - accumulatedSimTime += elapsedMilliseconds; - simSampleCount++; - - // Contador de tiempo desde el inicio de la simulación - if (TiempoDesdeStartSimulacion <= 1200) - TiempoDesdeStartSimulacion += (float)elapsedMilliseconds; - - foreach (var objetoSimulable in ObjetosSimulables) - objetoSimulable.UpdateGeometryStep(); - - simulationManager.Step(); - - var objetosSimulablesCopy = new List(ObjetosSimulables); - - foreach (var objetoSimulable in objetosSimulablesCopy) + // Verificar si la aplicación todavía está disponible + if (Application.Current == null) return; + + // Ejecutar en el thread de UI para acceso a controles + Application.Current.Dispatcher.Invoke(() => { - if (!objetoSimulable.RemoverDesdeSimulacion) - objetoSimulable.UpdateControl((int)elapsedMilliseconds); - else - RemoverObjetoSimulable(objetoSimulable); - } + var executionStopwatch = Stopwatch.StartNew(); // ✅ NUEVO: Medir tiempo de ejecución del método - stopwatch.Stop(); // Stop measuring time - //Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.TotalMilliseconds} ms"); + // ✅ MANTENER: Tiempo entre llamadas del timer para lógica interna + var timeBetweenCalls = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimModel_last; + stopwatch_SimModel_last = stopwatch_Sim.Elapsed.TotalMilliseconds; + + // Acumular tiempo para el promedio (usando tiempo real del timer) + accumulatedSimTime += timeBetweenCalls; + simSampleCount++; + + // Contador de tiempo desde el inicio de la simulación + if (TiempoDesdeStartSimulacion <= 1200) + TiempoDesdeStartSimulacion += (float)timeBetweenCalls; + + // ✅ NUEVO: Optimización temprana para escenarios sin objetos + if (ObjetosSimulables?.Count > 0) + { + foreach (var objetoSimulable in ObjetosSimulables) + objetoSimulable.UpdateGeometryStep(); + } + + simulationManager.Step(); + + // ✅ NUEVO: Solo crear la copia si hay objetos para procesar + if (ObjetosSimulables?.Count > 0) + { + var objetosSimulablesCopy = new List(ObjetosSimulables); + + foreach (var objetoSimulable in objetosSimulablesCopy) + { + if (!objetoSimulable.RemoverDesdeSimulacion) + objetoSimulable.UpdateControl((int)timeBetweenCalls); + else + RemoverObjetoSimulable(objetoSimulable); + } + } + + executionStopwatch.Stop(); // ✅ CORREGIDO: Detener cronómetro de ejecución + //Debug.WriteLine($"OnTickSimulacion execution time: {stopwatch.ElapsedMilliseconds} ms"); + //Debug.WriteLine($"OnTickSimulacion execution time: {executionStopwatch.Elapsed.TotalMilliseconds:F2}ms | Timer interval: {timeBetweenCalls:F2}ms | Objects: {ObjetosSimulables?.Count ?? 0}"); + }); } private void ConnectPLC() @@ -1168,45 +1199,117 @@ namespace CtrEditor MainWindow?.ClearUndoHistory(); } - private List objetosSimulablesLlamados = new List(); - - private void OnRefreshEvent(object sender, EventArgs e) + /// + /// Limpia todos los recursos y detiene todos los timers antes del cierre de la aplicación + /// + public void CleanupResources() { - var stopwatch = Stopwatch.StartNew(); // Start measuring time - - if (PLCViewModel.IsConnected) + // Detener simulación si está corriendo + if (IsSimulationRunning) { - // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos - var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimPLC_last; - stopwatch_SimPLC_last = stopwatch_Sim.Elapsed.TotalMilliseconds; - - // Acumular tiempo para el promedio - accumulatedPlcTime += elapsedMilliseconds; - plcSampleCount++; - - // Reiniciar el cronómetro para la próxima medición - var remainingObjetosSimulables = ObjetosSimulables.Except(objetosSimulablesLlamados).ToList(); - - foreach (var objetoSimulable in remainingObjetosSimulables) - { - var objStopwatch = Stopwatch.StartNew(); - objetoSimulable.UpdatePLC(PLCViewModel, (int)elapsedMilliseconds); - objStopwatch.Stop(); - - objetosSimulablesLlamados.Add(objetoSimulable); - - if (stopwatch.Elapsed.TotalMilliseconds >= 10) - break; - } - - if (remainingObjetosSimulables.Count == 0) - { - objetosSimulablesLlamados.Clear(); - } + StopSimulation(); } - stopwatch.Stop(); // Stop measuring time - // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms"); + // Detener todos los timers + _timerSimulacion?.Stop(); + _timerPLCUpdate?.Stop(); + _timer3DUpdate?.Stop(); + + // Desconectar PLC si está conectado + if (PLCViewModel?.IsConnected == true) + { + PLCViewModel.Disconnect(); + } + } + + private List objetosSimulablesLlamados = new List(); + + private void OnRefreshEvent(object sender, System.Timers.ElapsedEventArgs e) + { + // Verificar si la aplicación todavía está disponible + if (Application.Current == null) return; + + // Ejecutar en el thread de UI para acceso a controles + Application.Current.Dispatcher.Invoke(() => + { + var stopwatch = Stopwatch.StartNew(); // Start measuring time + + if (PLCViewModel.IsConnected) + { + // Detener el cronómetro y obtener el tiempo transcurrido en milisegundos + var elapsedMilliseconds = stopwatch_Sim.Elapsed.TotalMilliseconds - stopwatch_SimPLC_last; + stopwatch_SimPLC_last = stopwatch_Sim.Elapsed.TotalMilliseconds; + + // Acumular tiempo para el promedio + accumulatedPlcTime += elapsedMilliseconds; + plcSampleCount++; + + // Reiniciar el cronómetro para la próxima medición + var remainingObjetosSimulables = ObjetosSimulables.Except(objetosSimulablesLlamados).ToList(); + + foreach (var objetoSimulable in remainingObjetosSimulables) + { + var objStopwatch = Stopwatch.StartNew(); + objetoSimulable.UpdatePLC(PLCViewModel, (int)elapsedMilliseconds); + objStopwatch.Stop(); + + objetosSimulablesLlamados.Add(objetoSimulable); + + if (stopwatch.Elapsed.TotalMilliseconds >= 10) + break; + } + + if (remainingObjetosSimulables.Count == 0) + { + objetosSimulablesLlamados.Clear(); + } + } + + stopwatch.Stop(); // Stop measuring time + + // Sistema adaptativo de timing PLC + AdaptPlcTiming(stopwatch.Elapsed.TotalMilliseconds); + + // Debug.WriteLine($"OnRefreshEvent: {stopwatch.Elapsed.TotalMilliseconds} ms, Interval: {_timerPLCUpdate.Interval}ms"); + }); + } + + private void AdaptPlcTiming(double executionTime) + { + lastPlcExecutionTime = executionTime; + maxPlcExecutionTime = Math.Max(maxPlcExecutionTime, executionTime); + plcTimingAdaptationCounter++; + + // Evaluar cada PLC_ADAPTATION_SAMPLES ejecuciones + if (plcTimingAdaptationCounter >= PLC_ADAPTATION_SAMPLES) + { + double currentInterval = _timerPLCUpdate.Interval; + double newInterval = currentInterval; + + // Si el tiempo máximo de ejecución + tiempo para simulación > intervalo actual + if (maxPlcExecutionTime + SIM_PRIORITY_TIME > currentInterval) + { + // Incrementar intervalo para dar más tiempo + newInterval = Math.Min(maxPlcExecutionTime + SIM_PRIORITY_TIME + 5, MAX_PLC_INTERVAL); + Debug.WriteLine($"PLC Timer: Aumentando intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); + } + // Si hemos estado por debajo del umbral, podemos reducir el intervalo + else if (maxPlcExecutionTime + SIM_PRIORITY_TIME < currentInterval * 0.7) + { + newInterval = Math.Max(maxPlcExecutionTime + SIM_PRIORITY_TIME + 2, MIN_PLC_INTERVAL); + Debug.WriteLine($"PLC Timer: Reduciendo intervalo a {newInterval:F1}ms (ejecución máxima: {maxPlcExecutionTime:F1}ms)"); + } + + // Aplicar el nuevo intervalo si cambió + if (Math.Abs(newInterval - currentInterval) > 1) + { + _timerPLCUpdate.Interval = newInterval; + } + + // Reset para el próximo período de evaluación + maxPlcExecutionTime = 0; + plcTimingAdaptationCounter = 0; + } } private void OpenWorkDirectory() diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 84c49b7..439d529 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -491,6 +491,9 @@ namespace CtrEditor if (DataContext is MainViewModel viewModel) { viewModel.ImageSelected -= ViewModel_ImageSelected; + + // Limpiar todos los recursos antes del cierre + viewModel.CleanupResources(); } } diff --git a/Simulacion/BEPU.cs b/Simulacion/BEPU.cs index dd8d129..30315b9 100644 --- a/Simulacion/BEPU.cs +++ b/Simulacion/BEPU.cs @@ -1013,6 +1013,17 @@ namespace CtrEditor.Simulacion { _frameCount++; + // ✅ NUEVO: Verificación temprana para cuando no hay objetos simulables + // Esto evita cálculos de tiempo irregulares cuando no hay trabajo que hacer + if (Cuerpos == null || Cuerpos.Count == 0) + { + // Mantener el cronómetro actualizado pero usar deltaTime fijo + var currentTimeEmpty = stopwatch.Elapsed.TotalMilliseconds; + stopwatch_last = currentTimeEmpty; + GlobalTime += 1f / 60f; // Incremento fijo de 60 FPS + return; + } + // ✅ NUEVO: Resetear el estado de contacto para el sistema de presión foreach (var botella in Cuerpos.OfType()) { @@ -1109,6 +1120,9 @@ namespace CtrEditor.Simulacion _descarteContacts.Clear(); } + // ✅ NUEVO: Actualizar el tiempo global de simulación + GlobalTime += deltaTime; + // ✅ CONSERVAR - sincronización 3D if (Is3DUpdateEnabled) {