Implementar temporizadores más precisos utilizando System.Timers en MainViewModel y optimizar la lógica de simulación en BEPU para evitar cálculos innecesarios cuando no hay objetos simulables.

This commit is contained in:
Miguel 2025-09-04 14:16:12 +02:00
parent 091170b70d
commit 1e6ad6377e
3 changed files with 193 additions and 73 deletions

View File

@ -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
// Verificar si la aplicación todavía está disponible
if (Application.Current == null) return;
// 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<osBase>(ObjetosSimulables);
foreach (var objetoSimulable in objetosSimulablesCopy)
// 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<osBase>(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<osBase> objetosSimulablesLlamados = new List<osBase>();
private void OnRefreshEvent(object sender, EventArgs e)
/// <summary>
/// Limpia todos los recursos y detiene todos los timers antes del cierre de la aplicación
/// </summary>
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<osBase> objetosSimulablesLlamados = new List<osBase>();
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()

View File

@ -491,6 +491,9 @@ namespace CtrEditor
if (DataContext is MainViewModel viewModel)
{
viewModel.ImageSelected -= ViewModel_ImageSelected;
// Limpiar todos los recursos antes del cierre
viewModel.CleanupResources();
}
}

View File

@ -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<simBotella>())
{
@ -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)
{