diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/plugin.xml b/plugins/org.eclipse.fordiac.ide.debug.ui/plugin.xml index 3f5624ea58..e89bac3456 100644 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/plugin.xml +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/plugin.xml @@ -111,7 +111,7 @@ diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/Messages.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/Messages.java index c6b1c7186f..76f5fe37e9 100644 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/Messages.java +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/Messages.java @@ -16,7 +16,19 @@ @SuppressWarnings("squid:S3008") // tell sonar the java naming convention does not make sense for this class public final class Messages extends NLS { - private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$ + private static final String BUNDLE_NAME = "org.eclipse.fordiac.ide.debug.ui.messages"; //$NON-NLS-1$ + public static String DebugClockWidget_ClockIntervalTextLabel; + public static String DebugClockWidget_FixedClock; + public static String DebugClockWidget_IntervalClock; + public static String DebugClockWidget_InvalidInterval; + public static String DebugClockWidget_InvalidMonotonicClockValue; + public static String DebugClockWidget_InvalidRealtimeClockValue; + public static String DebugClockWidget_MonotonicClock; + public static String DebugClockWidget_MonotonicClockTextLabel; + public static String DebugClockWidget_RealtimeClock; + public static String DebugClockWidget_RealtimeClockTextLabel; + public static String DebugClockWidget_SystemClock; + public static String DebugClockWidget_Title; public static String EvaluatorDebugFindAction_Text; public static String EvaluatorDebugFindDialog_Title; public static String EvaluatorVariableValueEditor_Exception; @@ -31,18 +43,11 @@ public final class Messages extends NLS { public static String MainLaunchConfigurationTab_ErrorUpdatingArguments; public static String MainLaunchConfigurationTab_InvalidValueMessage; public static String MainLaunchConfigurationTab_InvalidValueTitle; - - public static String FBLaunchConfigurationTab_ClockInterval; - public static String FBLaunchConfigurationTab_DebugTime; - public static String FBLaunchConfigurationTab_ERROR_InvalidDebugTime; + public static String FBDebugViewClockWidget_Apply; + public static String FBDebugViewClockWidget_InvalidValues; public static String FBLaunchConfigurationTab_Event; - public static String FBLaunchConfigurationTab_IncrementClockAfterEachEventBySpecifiedAmount; public static String FBLaunchConfigurationTab_KeepDebuggerRunningWhenIdle; public static String FBLaunchConfigurationTab_RepeatEvent; - - public static String FBLaunchConfigurationTab_UseFixedClockWithSpecifiedTime; - public static String FBLaunchConfigurationTab_UseSystemClock; - static { // initialize resource bundle NLS.initializeMessages(BUNDLE_NAME, Messages.class); diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/fb/FBLaunchConfigurationTab.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/fb/FBLaunchConfigurationTab.java index d9f78b813d..005a45674f 100644 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/fb/FBLaunchConfigurationTab.java +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/fb/FBLaunchConfigurationTab.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; import org.eclipse.core.resources.IFile; @@ -29,6 +30,7 @@ import org.eclipse.fordiac.ide.debug.fb.FBLaunchConfigurationDelegate; import org.eclipse.fordiac.ide.debug.ui.MainLaunchConfigurationTab; import org.eclipse.fordiac.ide.debug.ui.Messages; +import org.eclipse.fordiac.ide.debug.ui.widgets.DebugClockWidget; import org.eclipse.fordiac.ide.model.eval.variable.Variable; import org.eclipse.fordiac.ide.model.libraryElement.AdapterDeclaration; import org.eclipse.fordiac.ide.model.libraryElement.AdapterFB; @@ -47,32 +49,32 @@ import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; public class FBLaunchConfigurationTab extends MainLaunchConfigurationTab { private ComboViewer eventCombo; private Button repeatEventCheckbox; private Button keepDebuggerRunningCheckbox; - private Button systemTimeRadio; - private Button incrementTimeRadio; - private Button manualTimeRadio; - private Text debugTimeText; + private final DebugClockWidget clockWidget = new DebugClockWidget(this::updateLaunchConfigurationDialog); @Override public void createControl(final Composite parent) { super.createControl(parent); - final Composite eventComponent = createEventComponent((Composite) getControl()); + final Composite comp = (Composite) getControl(); + + final Composite eventComponent = createEventComponent(comp); GridDataFactory.fillDefaults().grab(true, false).applyTo(eventComponent); - final Composite argumentsComponent = createArgumentsComponent((Composite) getControl()); + final Composite argumentsComponent = createArgumentsComponent(comp); GridDataFactory.fillDefaults().grab(true, true).applyTo(argumentsComponent); + + final Control clockComponent = clockWidget.createControl(comp); + GridDataFactory.fillDefaults().grab(true, false).applyTo(clockComponent); } @Override @@ -90,9 +92,6 @@ protected Composite createOptionsComponent(final Composite parent) { keepDebuggerRunningCheckbox.addSelectionListener(widgetSelectedAdapter(e -> updateLaunchConfigurationDialog())); GridDataFactory.fillDefaults().applyTo(keepDebuggerRunningCheckbox); - final Composite debugTimeComponent = createDebugTimeComponent(comp); - GridDataFactory.fillDefaults().grab(true, false).applyTo(debugTimeComponent); - return group; } @@ -114,47 +113,6 @@ protected Composite createEventComponent(final Composite parent) { return group; } - protected Composite createDebugTimeComponent(final Composite parent) { - final Group group = new Group(parent, SWT.BORDER); - GridLayoutFactory.swtDefaults().applyTo(group); - group.setText(Messages.FBLaunchConfigurationTab_DebugTime); - - final Composite radioComp = new Composite(group, SWT.NONE); - GridLayoutFactory.swtDefaults().numColumns(1).applyTo(radioComp); - GridDataFactory.fillDefaults().grab(true, false).applyTo(radioComp); - - systemTimeRadio = new Button(radioComp, SWT.RADIO); - systemTimeRadio.setText(Messages.FBLaunchConfigurationTab_UseSystemClock); - systemTimeRadio.addListener(SWT.Selection, e -> updateLaunchConfigurationDialog()); - GridDataFactory.fillDefaults().applyTo(systemTimeRadio); - - incrementTimeRadio = new Button(radioComp, SWT.RADIO); - incrementTimeRadio.setText(Messages.FBLaunchConfigurationTab_IncrementClockAfterEachEventBySpecifiedAmount); - incrementTimeRadio.addListener(SWT.Selection, e -> updateLaunchConfigurationDialog()); - GridDataFactory.fillDefaults().applyTo(incrementTimeRadio); - - manualTimeRadio = new Button(radioComp, SWT.RADIO); - manualTimeRadio.setText(Messages.FBLaunchConfigurationTab_UseFixedClockWithSpecifiedTime); - manualTimeRadio.addListener(SWT.Selection, e -> updateLaunchConfigurationDialog()); - GridDataFactory.fillDefaults().applyTo(manualTimeRadio); - - final Composite c = new Composite(group, SWT.NONE); - GridLayoutFactory.swtDefaults().numColumns(3).applyTo(c); - GridDataFactory.fillDefaults().grab(true, false).applyTo(c); - - final Label clockIntervalLabel = new Label(c, SWT.NONE); - clockIntervalLabel.setText(Messages.FBLaunchConfigurationTab_ClockInterval); - debugTimeText = new Text(c, SWT.BORDER); - debugTimeText.setText("0"); //$NON-NLS-1$ - debugTimeText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1)); - debugTimeText.addSegmentListener(e -> updateLaunchConfigurationDialog()); - final Label debugTimeLabel = new Label(c, SWT.NONE); - debugTimeLabel.setText("ms"); //$NON-NLS-1$ - debugTimeLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); - - return group; - } - @Override public void setDefaults(final ILaunchConfigurationWorkingCopy configuration) { super.setDefaults(configuration); @@ -163,6 +121,8 @@ public void setDefaults(final ILaunchConfigurationWorkingCopy configuration) { configuration.removeAttribute(FBLaunchConfigurationAttributes.KEEP_RUNNING_WHEN_IDLE); configuration.removeAttribute(FBLaunchConfigurationAttributes.CLOCK_MODE); configuration.removeAttribute(FBLaunchConfigurationAttributes.CLOCK_INTERVAL); + configuration.removeAttribute(FBLaunchConfigurationAttributes.CLOCK_REALTIME_OFFSET); + configuration.removeAttribute(FBLaunchConfigurationAttributes.CLOCK_MONOTONIC_OFFSET); } @Override @@ -179,10 +139,12 @@ public void initializeFrom(final ILaunchConfiguration configuration) { repeatEventCheckbox.setSelection(FBLaunchConfigurationAttributes.isRepeatEvent(configuration)); keepDebuggerRunningCheckbox .setSelection(FBLaunchConfigurationAttributes.isKeepRunningWhenIdle(configuration)); - systemTimeRadio.setSelection(FBLaunchConfigurationAttributes.isSystem(configuration)); - incrementTimeRadio.setSelection(FBLaunchConfigurationAttributes.isIncrement(configuration)); - manualTimeRadio.setSelection(FBLaunchConfigurationAttributes.isManual(configuration)); - debugTimeText.setText(FBLaunchConfigurationAttributes.getClockIntervalText(configuration)); + clockWidget.setSelectedClockMode(FBLaunchConfigurationAttributes.getClockMode(configuration)); + clockWidget.setClockIntervalText(FBLaunchConfigurationAttributes.getClockIntervalText(configuration)); + clockWidget + .setRealtimeClockText(FBLaunchConfigurationAttributes.getClockRealtimeOffsetText(configuration)); + clockWidget.setMonotonicClockText( + FBLaunchConfigurationAttributes.getClockMonotonicOffsetText(configuration)); } catch (final CoreException e) { // ignore } @@ -202,21 +164,16 @@ public void performApply(final ILaunchConfigurationWorkingCopy configuration) { configuration.setAttribute(FBLaunchConfigurationAttributes.KEEP_RUNNING_WHEN_IDLE, keepDebuggerRunningCheckbox.getSelection()); - if (systemTimeRadio.getSelection()) { - configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_MODE, FBDebugClockMode.SYSTEM.toString()); - } - if (incrementTimeRadio.getSelection()) { - configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_MODE, - FBDebugClockMode.INCREMENT.toString()); + final FBDebugClockMode clockMode = clockWidget.getSelectedClockMode(); + configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_MODE, clockMode.toString()); + if (clockMode == FBDebugClockMode.INTERVAL) { + configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_INTERVAL, clockWidget.getClockIntervalText()); } - if (manualTimeRadio.getSelection()) { - configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_MODE, FBDebugClockMode.MANUAL.toString()); - } - final String debugTime = debugTimeText.getText(); - if (!debugTime.isBlank()) { - configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_INTERVAL, debugTime); - } else { - configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_INTERVAL, "0"); //$NON-NLS-1$ + if (clockMode == FBDebugClockMode.INTERVAL || clockMode == FBDebugClockMode.FIXED) { + configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_REALTIME_OFFSET, + clockWidget.getRealtimeClockText()); + configuration.setAttribute(FBLaunchConfigurationAttributes.CLOCK_MONOTONIC_OFFSET, + clockWidget.getMonotonicClockText()); } } @@ -279,17 +236,9 @@ protected boolean filterTargetResource(final IResource resource) throws CoreExce @Override public boolean isValid(final ILaunchConfiguration launchConfig) { - setErrorMessage(null); - try { - final int debugTime = Integer.parseInt(debugTimeText.getText()); - if (debugTime < 0) { - throw new NumberFormatException(); - } - } catch (final NumberFormatException nfe) { - setErrorMessage(Messages.FBLaunchConfigurationTab_ERROR_InvalidDebugTime); - return false; - } - return true; + final Optional clockValid = clockWidget.validate(); + setErrorMessage(clockValid.orElse(null)); + return clockValid.isEmpty(); } @SuppressWarnings("static-method") // subclasses may override diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/messages.properties b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/messages.properties index ba12fbf116..3ed6cd9d83 100644 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/messages.properties +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/messages.properties @@ -1,3 +1,15 @@ +DebugClockWidget_ClockIntervalTextLabel=T\# +DebugClockWidget_FixedClock=Use fixed clock with specified value +DebugClockWidget_IntervalClock=Increment clock on each event by +DebugClockWidget_InvalidInterval=Invalid clock interval: {0} +DebugClockWidget_InvalidMonotonicClockValue=Invalid monotonic clock value: {0} +DebugClockWidget_InvalidRealtimeClockValue=Invalid real-time clock value: {0} +DebugClockWidget_MonotonicClock=Monotonic Clock: +DebugClockWidget_MonotonicClockTextLabel=T\# +DebugClockWidget_RealtimeClock=Real-time Clock: +DebugClockWidget_RealtimeClockTextLabel=DT\# +DebugClockWidget_SystemClock=Use system clock +DebugClockWidget_Title=Clock EvaluatorDebugFindAction_Text=&Find... EvaluatorDebugFindDialog_Title=Filter EvaluatorVariableValueEditor_Exception=An exception occurred @@ -12,13 +24,8 @@ MainLaunchConfigurationTab_ErrorInitializingArguments=Error initializing argumen MainLaunchConfigurationTab_ErrorUpdatingArguments=Error updating arguments MainLaunchConfigurationTab_InvalidValueMessage={0} is not a valid value for variable {1} with type {2} MainLaunchConfigurationTab_InvalidValueTitle=Invalid Value - -FBLaunchConfigurationTab_ClockInterval=Clock interval: -FBLaunchConfigurationTab_DebugTime=Debug Time -FBLaunchConfigurationTab_ERROR_InvalidDebugTime=Invalid value for clock interval given. Must be number greater or equal to 0. +FBDebugViewClockWidget_Apply=Apply +FBDebugViewClockWidget_InvalidValues=Invalid Values FBLaunchConfigurationTab_Event=Event -FBLaunchConfigurationTab_IncrementClockAfterEachEventBySpecifiedAmount=Increment clock after each event by specified amount FBLaunchConfigurationTab_KeepDebuggerRunningWhenIdle=Keep debugger running when idle FBLaunchConfigurationTab_RepeatEvent=Repeat event -FBLaunchConfigurationTab_UseFixedClockWithSpecifiedTime=Use fixed clock with specified time -FBLaunchConfigurationTab_UseSystemClock=Use system clock diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/DebugTimeComposite.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/DebugTimeComposite.java deleted file mode 100644 index c6ba9861b8..0000000000 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/DebugTimeComposite.java +++ /dev/null @@ -1,251 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023 Primetals Technologies Austria GmbH - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Lukas Leimeister - initial API and implementation and/or initial documentation - *******************************************************************************/ -package org.eclipse.fordiac.ide.debug.ui.view; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; - -import org.eclipse.fordiac.ide.debug.EvaluatorProcess; -import org.eclipse.fordiac.ide.debug.fb.FBDebugClockMode; -import org.eclipse.fordiac.ide.debug.fb.FBLaunchEventQueue; -import org.eclipse.fordiac.ide.model.eval.AbstractEvaluator; -import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluator; -import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.layout.GridLayoutFactory; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -public class DebugTimeComposite { - - private static final int NUM_COLUMNS = 1; - private static final int NUM_BUTTONS = 3; - private EvaluatorProcess evaluator; - private FBLaunchEventQueue eventQueue; - private final Group debugTimeGroup; - private Button systemTimeRadio; - private Button incrementTimeRadio; - private Button manualTimeRadio; - private Text debugTimeText; - - /** - * Create an new edit part to control the debug time settings. This interface - * contains three radio buttons for mode selection and one text/ lable to adjust - * the time value. It is part of the debug view and only visible, when context - * is activated. - * - * @param parent The parent Composite of the debug view. - */ - public DebugTimeComposite(final Composite parent) { - debugTimeGroup = createDebugTimeGroup(parent); - GridLayoutFactory.fillDefaults().numColumns(NUM_COLUMNS).margins(0, 0).generateLayout(debugTimeGroup); - GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, false).applyTo(debugTimeGroup); - setEditPartVisible(false); - } - - protected Group createDebugTimeGroup(final Composite parent) { - // this method creates the Test Recorder Interface Group - final Group group = new Group(parent, SWT.NONE); - group.setText("Set Debug Time"); //$NON-NLS-1$ - - // create sleeptime selection - final Composite debugTimeSelectionComposite = createDebugTimeModeSelectionComposite(group); - GridLayoutFactory.fillDefaults().numColumns(NUM_BUTTONS).margins(0, 0) - .generateLayout(debugTimeSelectionComposite); - GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, false) - .applyTo(debugTimeSelectionComposite); - - // create sleeptime settings - final Composite debugTimeValueComposite = createDebugTimeValueComposite(group); - GridLayoutFactory.fillDefaults().numColumns(NUM_BUTTONS).margins(0, 0).generateLayout(debugTimeValueComposite); - GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, false).applyTo(debugTimeValueComposite); - - // set visability of the group - setEditPartVisible(false); - return group; - } - - protected Composite createDebugTimeValueComposite(final Group group) { - // create composite for sleeptime section - final Composite composite = new Composite(group, SWT.NONE); - - // create elements fo composite - final Button setButton = new Button(composite, SWT.PUSH); - setButton.setText("Set"); //$NON-NLS-1$ - setButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true, 1, 1)); - debugTimeText = new Text(composite, SWT.BORDER); - final Label debugTimeLable = new Label(composite, SWT.NONE); - - // customize elements of composite - debugTimeText.setText(""); //$NON-NLS-1$ - debugTimeText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1)); - debugTimeLable.setText("ms"); //$NON-NLS-1$ - debugTimeLable.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1)); - - // customize elements of composite - setButton.addSelectionListener( - SelectionListener.widgetSelectedAdapter(e -> setDebugTime(debugTimeText.getText()))); - return composite; - } - - protected Composite createDebugTimeModeSelectionComposite(final Group group) { - // create composite for sleeptime section - final Composite composite = new Composite(group, SWT.NONE); - - systemTimeRadio = new Button(composite, SWT.RADIO); - systemTimeRadio.setText("System"); //$NON-NLS-1$ - incrementTimeRadio = new Button(composite, SWT.RADIO); - incrementTimeRadio.setText("Increment"); //$NON-NLS-1$ - manualTimeRadio = new Button(composite, SWT.RADIO); - manualTimeRadio.setText("Manual"); //$NON-NLS-1$ - return composite; - } - - protected void setCheckbox(final String text) { - // only the most recent selected box is checked - switch (text) { - case "System": //$NON-NLS-1$ - systemTimeRadio.setSelection(true); - incrementTimeRadio.setSelection(false); - manualTimeRadio.setSelection(false); - break; - case "Increment": //$NON-NLS-1$ - systemTimeRadio.setSelection(false); - incrementTimeRadio.setSelection(true); - manualTimeRadio.setSelection(false); - break; - case "Manual": //$NON-NLS-1$ - systemTimeRadio.setSelection(false); - incrementTimeRadio.setSelection(false); - manualTimeRadio.setSelection(true); - break; - default: - break; - } - } - - private void setDebugTime(final String text) { - // set button is pushed combined with one of the three selections - if (manualTimeRadio.getSelection() && (text != null)) { - manualSelection(text); - } - if (incrementTimeRadio.getSelection() && (text != null)) { - incrementSelection(text); - } - if (systemTimeRadio.getSelection()) { - systemSelection(); - } - } - - private void systemSelection() { - // time is set to default system time - evaluator.getExecutor().setClock(AbstractEvaluator.MonotonicClock.UTC); - if (eventQueue != null) { - eventQueue.setEvaluatorProcess(evaluator); - eventQueue.setDebugTimeValue(FBDebugClockMode.SYSTEM, Duration.ZERO); - } - } - - private void incrementSelection(final String text) { - // time is incrementally increased every time an event is triggered - try { - final long value = Long.parseLong(text); - final Duration sleepTime = Duration.of(value, ChronoUnit.MILLIS); - if (eventQueue != null) { - eventQueue.setEvaluatorProcess(evaluator); - eventQueue.setDebugTimeValue(FBDebugClockMode.INCREMENT, sleepTime); - } - } catch (final NumberFormatException | java.lang.ArithmeticException e) { - throw new IllegalStateException("Debug Time Value is not accepted!"); //$NON-NLS-1$ - } - } - - private void manualSelection(final String text) { - // time is fixed and set to user input - try { - final long value = Long.parseLong(text); - final Duration manualTime = Duration.of(value, ChronoUnit.MILLIS); - final Instant instant = Instant.ofEpochSecond(manualTime.getSeconds(), manualTime.getNano()); - if (eventQueue != null) { - eventQueue.setEvaluatorProcess(evaluator); - eventQueue.setDebugTimeValue(FBDebugClockMode.MANUAL, manualTime); - } - evaluator.getExecutor().setClock(Clock.fixed(instant, ZoneId.systemDefault())); - } catch (final NumberFormatException | java.lang.ArithmeticException e) { - throw new IllegalStateException("Debug Time Value is not accepted!"); //$NON-NLS-1$ - } - } - - /** - * Set the Edit Part visible or invisible. - * - * @param visiblestate The boolean state of the visibility - */ - public void setEditPartVisible(final boolean visiblestate) { - // set the visibility status of the group - if (debugTimeGroup != null) { - debugTimeGroup.setVisible(visiblestate); - } - } - - /** - * Update the contents of the Edit Part. - * - * @param visiblestate The boolean state of the visibility - */ - public void updateEditPartVisible() { - // update elements of edit part - final FBLaunchEventQueue newEventQueue = getEventQueue(evaluator); - if (eventQueue != newEventQueue) { - eventQueue = newEventQueue; - } - if (eventQueue != null) { - systemTimeRadio.setSelection(eventQueue.isDebugTimeSystem()); - incrementTimeRadio.setSelection(eventQueue.isDebugTimeIncremental()); - manualTimeRadio.setSelection(eventQueue.isDebugTimeManual()); - debugTimeText.setText(String.valueOf(eventQueue.getDebugTimeValue().toMillis())); - eventQueue.setEvaluatorProcess(evaluator); - } - } - - /** - * Set the content of the DebugTimeEditPart. - * - * @param evaluatorprocess Evaluator Process which is currently active in View - */ - public void setContent(final EvaluatorProcess evaluatorprocess) { - evaluator = evaluatorprocess; - final FBLaunchEventQueue newEventQueue = getEventQueue(evaluator); - if (eventQueue != newEventQueue) { - eventQueue = newEventQueue; - } - } - - private static FBLaunchEventQueue getEventQueue(final EvaluatorProcess evaluator) { - if (evaluator != null) { - final var queue = ((FBEvaluator) evaluator.getEvaluator()).getEventQueue(); - if (queue instanceof final FBLaunchEventQueue fBLaunchEventQueue) { - return fBLaunchEventQueue; - } - } - return null; - } -} \ No newline at end of file diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugView.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugView.java index a637481ff3..7efd6e851c 100644 --- a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugView.java +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugView.java @@ -115,17 +115,18 @@ public Rectangle getClientArea(final Rectangle rect) { private GraphicalViewer viewer; private ActionRegistry actionRegistry; - private static final int NUM_COLUMNS = 1; private KeyHandler sharedKeyHandler; private RepeatEventAction repeatEventAction; - private DebugTimeComposite debugTimeEditPart; + private final FBDebugViewClockWidget clockWidget = new FBDebugViewClockWidget(); @Override public void createPartControl(final Composite parent) { getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(this); - GridLayoutFactory.fillDefaults().numColumns(NUM_COLUMNS).margins(0, 0).generateLayout(parent); + GridLayoutFactory.fillDefaults().applyTo(parent); createGraphicalViewer(parent); - debugTimeEditPart = new DebugTimeComposite(parent); + final Composite clockControl = clockWidget.createControl(parent); + clockControl.setVisible(false); + GridDataFactory.fillDefaults().grab(true, false).applyTo(clockControl); createToolBarEntries(); hookDebugListeners(); } @@ -263,10 +264,10 @@ private void contextActivated(final ISelection selection) { final Object source = structSel.getFirstElement(); if (source == null) { setContents(null); - debugTimeEditPart.setEditPartVisible(false); + clockWidget.getControl().setVisible(false); } else { final EvaluatorProcess evaluator = getFBEvaluatorDebugContext(source); - debugTimeEditPart.setEditPartVisible(true); + clockWidget.getControl().setVisible(true); if (!isViewerContent(evaluator)) { setContents(evaluator); } @@ -277,8 +278,8 @@ private void contextActivated(final ISelection selection) { private void setContents(final EvaluatorProcess evaluator) { viewer.setContents(evaluator); repeatEventAction.updateEvaluator(evaluator); - debugTimeEditPart.setContent(evaluator); - debugTimeEditPart.updateEditPartVisible(); + clockWidget.setProcess(evaluator); + clockWidget.refresh(true); setScrollPosition(); } diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugViewClockWidget.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugViewClockWidget.java new file mode 100644 index 0000000000..a27e8ed2c5 --- /dev/null +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/view/FBDebugViewClockWidget.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2025 Martin Erich Jobst + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Martin Jobst - initial API and implementation and/or initial documentation + *******************************************************************************/ +package org.eclipse.fordiac.ide.debug.ui.view; + +import java.time.Clock; +import java.time.Duration; +import java.util.Collection; +import java.util.Optional; + +import org.eclipse.fordiac.ide.debug.EvaluatorProcess; +import org.eclipse.fordiac.ide.debug.fb.FBDebugClockMode; +import org.eclipse.fordiac.ide.debug.fb.FBLaunchConfigurationDelegate; +import org.eclipse.fordiac.ide.debug.ui.Messages; +import org.eclipse.fordiac.ide.debug.ui.widgets.DebugClockWidget; +import org.eclipse.fordiac.ide.model.eval.AbstractEvaluator; +import org.eclipse.fordiac.ide.model.eval.Evaluator; +import org.eclipse.fordiac.ide.model.eval.EvaluatorMonitor; +import org.eclipse.fordiac.ide.model.eval.EvaluatorMonitor.NullEvaluatorMonitor; +import org.eclipse.fordiac.ide.model.eval.EvaluatorThreadPoolExecutor; +import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluator; +import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluatorCountingEventQueue; +import org.eclipse.fordiac.ide.model.eval.variable.Variable; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; + +public class FBDebugViewClockWidget extends DebugClockWidget { + + private EvaluatorProcess process; + private boolean dirty; + private boolean refreshing; + + private Button applyButton; + + private final EvaluatorMonitor evaluatorMonitor = new NullEvaluatorMonitor() { + + @Override + public void update(final Collection> variables, final Evaluator evaluator) { + if (process != null && evaluator == process.getEvaluator() && !refreshing) { + refreshing = true; + Display.getDefault().asyncExec(FBDebugViewClockWidget.this::refresh); + } + } + + @Override + public void terminated(final EvaluatorThreadPoolExecutor executor) { + Display.getDefault().asyncExec(FBDebugViewClockWidget.this::refresh); + } + }; + + @Override + public Composite createControl(final Composite parent) { + final Composite composite = super.createControl(parent); + applyButton = new Button(composite, SWT.PUSH); + applyButton.setText(Messages.FBDebugViewClockWidget_Apply); + applyButton.setEnabled(false); + applyButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> performApply())); + GridDataFactory.swtDefaults().applyTo(applyButton); + return composite; + } + + private void performApply() { + if (process == null || process.isTerminated()) { + return; + } + final Optional error = validate(); + if (error.isPresent()) { + MessageDialog.openError(getControl().getShell(), Messages.FBDebugViewClockWidget_InvalidValues, + error.get()); + return; + } + final FBDebugClockMode clockMode = getSelectedClockMode(); + final Duration clockInterval = getClockInterval(); + final FBEvaluatorCountingEventQueue eventQueue = getEventQueue(); + + process.getExecutor().setRealtimeClock(FBLaunchConfigurationDelegate.createClock(clockMode, + getRealtimeClockValue(), clockInterval, eventQueue)); + process.getExecutor().setMonotonicClock(FBLaunchConfigurationDelegate.createClock(clockMode, + getMonotonicClockValue(), clockInterval, eventQueue)); + + setDirty(false); + } + + public void refresh() { + refresh(false); + } + + public void refresh(final boolean force) { + if (process != null && (force || !isDirty())) { + final Clock realtimeClock = process.getExecutor().getRealtimeClock(); + final Clock monotonicClock = process.getExecutor().getMonotonicClock(); + setRealtimeClockValue(realtimeClock.instant()); + setMonotonicClockValue(monotonicClock.instant()); + switch (monotonicClock) { + case final AbstractEvaluator.MonotonicClock unused -> setSelectedClockMode(FBDebugClockMode.SYSTEM); + case final AbstractEvaluator.IntervalClock intervalClock -> { + setClockInterval(intervalClock.getInterval()); + setSelectedClockMode(FBDebugClockMode.INTERVAL); + } + default -> setSelectedClockMode(FBDebugClockMode.FIXED); + } + setDirty(false); // ensure not dirty + } + refreshing = false; + } + + protected FBEvaluatorCountingEventQueue getEventQueue() { + return process != null && process.getEvaluator() instanceof final FBEvaluator evaluator + && evaluator.getEventQueue() instanceof final FBEvaluatorCountingEventQueue queue ? queue : null; + } + + @Override + protected void handleClockUpdated() { + setDirty(true); + super.handleClockUpdated(); + } + + public EvaluatorProcess getProcess() { + return process; + } + + public void setProcess(final EvaluatorProcess process) { + if (this.process != null) { + this.process.getExecutor().removeMonitor(evaluatorMonitor); + } + this.process = process; + if (this.process != null) { + this.process.getExecutor().addMonitor(evaluatorMonitor); + } + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(final boolean dirty) { + this.dirty = dirty; + applyButton.setEnabled(dirty); + } +} diff --git a/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/widgets/DebugClockWidget.java b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/widgets/DebugClockWidget.java new file mode 100644 index 0000000000..1f6a88f480 --- /dev/null +++ b/plugins/org.eclipse.fordiac.ide.debug.ui/src/org/eclipse/fordiac/ide/debug/ui/widgets/DebugClockWidget.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * Copyright (c) 2025 Martin Erich Jobst + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Martin Jobst - initial API and implementation and/or initial documentation + *******************************************************************************/ +package org.eclipse.fordiac.ide.debug.ui.widgets; + +import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter; + +import java.text.MessageFormat; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Optional; +import java.util.stream.Stream; + +import org.eclipse.fordiac.ide.debug.fb.FBDebugClockMode; +import org.eclipse.fordiac.ide.debug.ui.Messages; +import org.eclipse.fordiac.ide.model.value.DateAndTimeValueConverter; +import org.eclipse.fordiac.ide.model.value.TimeValueConverter; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +public class DebugClockWidget { + + private Group group; + private Button systemClockRadio; + private Button intervalClockRadio; + private Button fixedClockRadio; + private Text clockIntervalText; + private Text realtimeClockText; + private Text monotonicClockText; + + private Runnable updateRunnable; + + public DebugClockWidget() { + } + + public DebugClockWidget(final Runnable updateRunnable) { + this.updateRunnable = updateRunnable; + } + + public Composite createControl(final Composite parent) { + group = new Group(parent, SWT.BORDER); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(group); + group.setText(Messages.DebugClockWidget_Title); + + final Composite clockModes = new Composite(group, SWT.NONE); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(clockModes); + GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(clockModes); + + systemClockRadio = new Button(clockModes, SWT.RADIO); + systemClockRadio.setText(Messages.DebugClockWidget_SystemClock); + systemClockRadio.addSelectionListener(widgetSelectedAdapter(e -> handleClockModeUpdated())); + GridDataFactory.fillDefaults().span(2, 1).applyTo(systemClockRadio); + + intervalClockRadio = new Button(clockModes, SWT.RADIO); + intervalClockRadio.setText(Messages.DebugClockWidget_IntervalClock); + intervalClockRadio.addSelectionListener(widgetSelectedAdapter(e -> handleClockModeUpdated())); + GridDataFactory.swtDefaults().applyTo(intervalClockRadio); + + final Composite clockIntervalComposite = createClockIntervalComposite(clockModes); + GridDataFactory.fillDefaults().grab(true, false).applyTo(clockIntervalComposite); + + fixedClockRadio = new Button(clockModes, SWT.RADIO); + fixedClockRadio.setText(Messages.DebugClockWidget_FixedClock); + fixedClockRadio.addSelectionListener(widgetSelectedAdapter(e -> handleClockModeUpdated())); + GridDataFactory.fillDefaults().span(2, 1).applyTo(fixedClockRadio); + + final Label realtimeClockLabel = new Label(group, SWT.NONE); + realtimeClockLabel.setText(Messages.DebugClockWidget_RealtimeClock); + GridDataFactory.swtDefaults().applyTo(realtimeClockLabel); + + final Composite realtimeClockTextComposite = createRealtimeClockTextComposite(group); + GridDataFactory.fillDefaults().grab(true, false).applyTo(realtimeClockTextComposite); + + final Label monotonicClockLabel = new Label(group, SWT.NONE); + monotonicClockLabel.setText(Messages.DebugClockWidget_MonotonicClock); + GridDataFactory.swtDefaults().applyTo(monotonicClockLabel); + + final Composite monotonicClockTextComposite = createMonotonicClockTextComposite(group); + GridDataFactory.fillDefaults().grab(true, false).applyTo(monotonicClockTextComposite); + + return group; + } + + public Composite createClockIntervalComposite(final Composite parent) { + final Composite clockIntervalComposite = new Composite(parent, SWT.NONE); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(clockIntervalComposite); + + final Label clockIntervalTextLabel = new Label(clockIntervalComposite, SWT.NONE); + clockIntervalTextLabel.setText(Messages.DebugClockWidget_ClockIntervalTextLabel); + GridDataFactory.swtDefaults().applyTo(clockIntervalTextLabel); + + clockIntervalText = new Text(clockIntervalComposite, SWT.BORDER); + clockIntervalText.setEditable(false); + clockIntervalText.addModifyListener(e -> handleClockUpdated()); + GridDataFactory.fillDefaults().grab(true, false).applyTo(clockIntervalText); + return clockIntervalComposite; + } + + public Composite createRealtimeClockTextComposite(final Composite parent) { + final Composite realtimeClockComposite = new Composite(parent, SWT.NONE); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(realtimeClockComposite); + + final Label realtimeClockTextLabel = new Label(realtimeClockComposite, SWT.NONE); + realtimeClockTextLabel.setText(Messages.DebugClockWidget_RealtimeClockTextLabel); + GridDataFactory.swtDefaults().applyTo(realtimeClockTextLabel); + + realtimeClockText = new Text(realtimeClockComposite, SWT.BORDER); + realtimeClockText.setEditable(false); + realtimeClockText.addModifyListener(e -> handleClockUpdated()); + GridDataFactory.fillDefaults().grab(true, false).applyTo(realtimeClockText); + return realtimeClockComposite; + } + + public Composite createMonotonicClockTextComposite(final Composite parent) { + final Composite monotonicClockComposite = new Composite(parent, SWT.NONE); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(monotonicClockComposite); + + final Label monotonicClockTextLabel = new Label(monotonicClockComposite, SWT.NONE); + monotonicClockTextLabel.setText(Messages.DebugClockWidget_MonotonicClockTextLabel); + GridDataFactory.swtDefaults().applyTo(monotonicClockTextLabel); + + monotonicClockText = new Text(monotonicClockComposite, SWT.BORDER); + monotonicClockText.setEditable(false); + monotonicClockText.addModifyListener(e -> handleClockUpdated()); + GridDataFactory.fillDefaults().grab(true, false).applyTo(monotonicClockText); + return monotonicClockComposite; + } + + public FBDebugClockMode getSelectedClockMode() { + if (systemClockRadio.getSelection()) { + return FBDebugClockMode.SYSTEM; + } + if (fixedClockRadio.getSelection()) { + return FBDebugClockMode.FIXED; + } + if (intervalClockRadio.getSelection()) { + return FBDebugClockMode.INTERVAL; + } + return FBDebugClockMode.SYSTEM; + } + + public void setSelectedClockMode(final FBDebugClockMode clockMode) { + final Button selected = switch (clockMode) { + case SYSTEM -> systemClockRadio; + case INTERVAL -> intervalClockRadio; + case FIXED -> fixedClockRadio; + case null, default -> null; + }; + Stream.of(systemClockRadio, intervalClockRadio, fixedClockRadio) + .forEach(radio -> radio.setSelection(radio == selected)); + clockIntervalText.setEditable(selected == intervalClockRadio); + realtimeClockText.setEditable(selected == intervalClockRadio || selected == fixedClockRadio); + monotonicClockText.setEditable(selected == intervalClockRadio || selected == fixedClockRadio); + } + + protected void handleClockModeUpdated() { + clockIntervalText.setEditable(intervalClockRadio.getSelection()); + realtimeClockText.setEditable(intervalClockRadio.getSelection() || fixedClockRadio.getSelection()); + monotonicClockText.setEditable(intervalClockRadio.getSelection() || fixedClockRadio.getSelection()); + handleClockUpdated(); + } + + protected void handleClockUpdated() { + if (updateRunnable != null) { + updateRunnable.run(); + } + } + + public Optional validate() { + final FBDebugClockMode clockMode = getSelectedClockMode(); + if (clockMode == FBDebugClockMode.INTERVAL) { + try { + TimeValueConverter.INSTANCE.toValue(getClockIntervalText()); + } catch (final IllegalArgumentException e) { + return Optional + .of(MessageFormat.format(Messages.DebugClockWidget_InvalidInterval, e.getLocalizedMessage())); + } + } + if (clockMode == FBDebugClockMode.INTERVAL || clockMode == FBDebugClockMode.FIXED) { + try { + DateAndTimeValueConverter.INSTANCE.toValue(getRealtimeClockText()); + } catch (final IllegalArgumentException e) { + return Optional.of(MessageFormat.format(Messages.DebugClockWidget_InvalidRealtimeClockValue, + e.getLocalizedMessage())); + } + try { + TimeValueConverter.INSTANCE.toValue(getMonotonicClockText()); + } catch (final IllegalArgumentException e) { + return Optional.of(MessageFormat.format(Messages.DebugClockWidget_InvalidMonotonicClockValue, + e.getLocalizedMessage())); + } + } + return Optional.empty(); + } + + public Group getControl() { + return group; + } + + public void setControl(final Group control) { + this.group = control; + } + + public Duration getClockInterval() { + try { + return TimeValueConverter.INSTANCE.toValue(getClockIntervalText()); + } catch (final IllegalArgumentException e) { + return Duration.ZERO; + } + } + + public void setClockInterval(final Duration value) { + setClockIntervalText(TimeValueConverter.INSTANCE.toString(value)); + } + + public String getClockIntervalText() { + return clockIntervalText.getText(); + } + + public void setClockIntervalText(final String value) { + clockIntervalText.setText(value); + } + + public Instant getRealtimeClockValue() { + try { + return DateAndTimeValueConverter.INSTANCE.toValue(getRealtimeClockText()).toInstant(ZoneOffset.UTC); + } catch (final Exception e) { + return Instant.EPOCH; + } + } + + public void setRealtimeClockValue(final Instant value) { + setRealtimeClockText( + DateAndTimeValueConverter.INSTANCE.toString(LocalDateTime.ofInstant(value, ZoneOffset.UTC))); + } + + public String getRealtimeClockText() { + return realtimeClockText.getText(); + } + + public void setRealtimeClockText(final String value) { + realtimeClockText.setText(value); + } + + public Instant getMonotonicClockValue() { + try { + final Duration value = TimeValueConverter.INSTANCE.toValue(getMonotonicClockText()); + return Instant.ofEpochSecond(value.getSeconds(), value.getNano()); + } catch (final Exception e) { + return Instant.EPOCH; + } + } + + public void setMonotonicClockValue(final Instant value) { + setMonotonicClockText( + TimeValueConverter.INSTANCE.toString(Duration.ofSeconds(value.getEpochSecond(), value.getNano()))); + } + + public String getMonotonicClockText() { + return monotonicClockText.getText(); + } + + public void setMonotonicClockText(final String value) { + monotonicClockText.setText(value); + } + + public Runnable getUpdateRunnable() { + return updateRunnable; + } + + public void setUpdateRunnable(final Runnable updateRunnable) { + this.updateRunnable = updateRunnable; + } +} diff --git a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/CommonLaunchConfigurationDelegate.java b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/CommonLaunchConfigurationDelegate.java index e6ba51486e..ab17aa89d8 100644 --- a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/CommonLaunchConfigurationDelegate.java +++ b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/CommonLaunchConfigurationDelegate.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.eclipse.fordiac.ide.debug; +import java.time.Clock; + import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; @@ -25,15 +27,25 @@ public abstract class CommonLaunchConfigurationDelegate extends LaunchConfigurationDelegate { - @SuppressWarnings("static-method") protected void launch(final Evaluator evaluator, final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IResource resource, final IProgressMonitor monitor) throws CoreException { + launch(evaluator, null, null, configuration, mode, launch, resource, monitor); + } + + @SuppressWarnings("static-method") + protected void launch(final Evaluator evaluator, final Clock realtimeClock, final Clock monotonicClock, + final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IResource resource, + final IProgressMonitor monitor) throws CoreException { if (ILaunchManager.RUN_MODE.equals(mode)) { final EvaluatorProcess process = new EvaluatorProcess(configuration.getName(), evaluator, launch); + process.getExecutor().setRealtimeClock(realtimeClock); + process.getExecutor().setMonotonicClock(monotonicClock); process.start(); } else if (ILaunchManager.DEBUG_MODE.equals(mode)) { final EvaluatorDebugTarget debugTarget = new EvaluatorDebugTarget(configuration.getName(), evaluator, launch, resource); + debugTarget.getProcess().getExecutor().setRealtimeClock(realtimeClock); + debugTarget.getProcess().getExecutor().setMonotonicClock(monotonicClock); if (LaunchConfigurationAttributes.isStopOnFirstLine(configuration)) { debugTarget.getDebugger().setSuspendOnFirstLine(true); } diff --git a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBDebugClockMode.java b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBDebugClockMode.java index c6145892ce..0b03167f23 100644 --- a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBDebugClockMode.java +++ b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBDebugClockMode.java @@ -12,19 +12,7 @@ *******************************************************************************/ package org.eclipse.fordiac.ide.debug.fb; -import org.eclipse.fordiac.ide.ui.FordiacLogHelper; - public enum FBDebugClockMode { - SYSTEM, INCREMENT, MANUAL; - - public static FBDebugClockMode fromString(final String val) { - try { - return valueOf(val); - } catch (final IllegalArgumentException | NullPointerException ex) { - FordiacLogHelper.logWarning("Could not convert clock mode from string: " + val, ex); //$NON-NLS-1$ - } - return SYSTEM; - } - + SYSTEM, INTERVAL, FIXED; } diff --git a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationAttributes.java b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationAttributes.java index f11952eed9..3689445cd0 100644 --- a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationAttributes.java +++ b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationAttributes.java @@ -13,18 +13,22 @@ package org.eclipse.fordiac.ide.debug.fb; import java.time.Duration; -import java.time.temporal.ChronoUnit; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.fordiac.ide.debug.LaunchConfigurationAttributes; import org.eclipse.fordiac.ide.model.libraryElement.AdapterDeclaration; import org.eclipse.fordiac.ide.model.libraryElement.AdapterFB; import org.eclipse.fordiac.ide.model.libraryElement.Event; import org.eclipse.fordiac.ide.model.libraryElement.FBType; +import org.eclipse.fordiac.ide.model.value.DateAndTimeValueConverter; +import org.eclipse.fordiac.ide.model.value.TimeValueConverter; public interface FBLaunchConfigurationAttributes extends LaunchConfigurationAttributes { String ID = "org.eclipse.fordiac.ide.debug.fbLaunch"; //$NON-NLS-1$ @@ -33,8 +37,9 @@ public interface FBLaunchConfigurationAttributes extends LaunchConfigurationAttr String KEEP_RUNNING_WHEN_IDLE = "org.eclipse.fordiac.ide.debug.keepRunningWhenIdle"; //$NON-NLS-1$ String CLOCK_MODE = "org.eclipse.fordiac.ide.debug.clockMode"; //$NON-NLS-1$ - - String CLOCK_INTERVAL = "org.eclipse.fordiac.ide.debug.debugTime"; //$NON-NLS-1$ + String CLOCK_INTERVAL = "org.eclipse.fordiac.ide.debug.clockInterval"; //$NON-NLS-1$ + String CLOCK_REALTIME_OFFSET = "org.eclipse.fordiac.ide.debug.clockRealtimeOffset"; //$NON-NLS-1$ + String CLOCK_MONOTONIC_OFFSET = "org.eclipse.fordiac.ide.debug.clockMonotonicOffset"; //$NON-NLS-1$ static Event getEvent(final ILaunchConfiguration configuration, final FBType type, final Event defaultEvent) throws CoreException { @@ -62,35 +67,63 @@ static boolean isKeepRunningWhenIdle(final ILaunchConfiguration configuration) t } static FBDebugClockMode getClockMode(final ILaunchConfiguration configuration) throws CoreException { - return FBDebugClockMode.fromString(configuration.getAttribute(CLOCK_MODE, (String) null)); - } - - static boolean isSystem(final ILaunchConfiguration configuration) throws CoreException { - return FBDebugClockMode.SYSTEM.equals(getClockMode(configuration)); + final String modeAttribute = configuration.getAttribute(CLOCK_MODE, (String) null); + if (modeAttribute != null) { + try { + return FBDebugClockMode.valueOf(modeAttribute); + } catch (final IllegalArgumentException e) { + throw new CoreException(Status.error("Invalid value for clock mode")); //$NON-NLS-1$ + } + } + return FBDebugClockMode.SYSTEM; } - static boolean isIncrement(final ILaunchConfiguration configuration) throws CoreException { - return FBDebugClockMode.INCREMENT.equals(getClockMode(configuration)); + static Duration getClockInterval(final ILaunchConfiguration configuration) throws CoreException { + final var value = configuration.getAttribute(CLOCK_INTERVAL, (String) null); + if (value != null) { + try { + return TimeValueConverter.INSTANCE.toValue(value); + } catch (final IllegalArgumentException e) { + throw new CoreException(Status.error("Invalid value for clock interval", e)); //$NON-NLS-1$ + } + } + return Duration.ZERO; } - static boolean isManual(final ILaunchConfiguration configuration) throws CoreException { - return FBDebugClockMode.MANUAL.equals(getClockMode(configuration)); + static Instant getClockRealtimeOffset(final ILaunchConfiguration configuration) throws CoreException { + final var value = configuration.getAttribute(CLOCK_REALTIME_OFFSET, (String) null); + if (value != null) { + try { + return DateAndTimeValueConverter.INSTANCE.toValue(value).toInstant(ZoneOffset.UTC); + } catch (final IllegalArgumentException e) { + throw new CoreException(Status.error("Invalid value for clock monotonic offset", e)); //$NON-NLS-1$ + } + } + return Instant.EPOCH; } - static Duration getClockInterval(final ILaunchConfiguration configuration) throws CoreException { - final var debugtime = getClockIntervalText(configuration); - if (debugtime != null) { + static Instant getClockMonotonicOffset(final ILaunchConfiguration configuration) throws CoreException { + final var value = configuration.getAttribute(CLOCK_MONOTONIC_OFFSET, (String) null); + if (value != null) { try { - final long value = Long.parseLong(debugtime); - return Duration.of(value, ChronoUnit.MILLIS); - } catch (final NumberFormatException | ArithmeticException e) { - throw new IllegalStateException("Debug clock interval is not accepted!"); //$NON-NLS-1$ + final Duration duration = TimeValueConverter.INSTANCE.toValue(value); + return Instant.ofEpochSecond(duration.getSeconds(), duration.getNano()); + } catch (final IllegalArgumentException e) { + throw new CoreException(Status.error("Invalid value for clock monotonic offset", e)); //$NON-NLS-1$ } } - return Duration.ZERO; + return Instant.EPOCH; } static String getClockIntervalText(final ILaunchConfiguration configuration) throws CoreException { - return configuration.getAttribute(CLOCK_INTERVAL, "0"); + return configuration.getAttribute(CLOCK_INTERVAL, "1s"); //$NON-NLS-1$ + } + + static String getClockRealtimeOffsetText(final ILaunchConfiguration configuration) throws CoreException { + return configuration.getAttribute(CLOCK_REALTIME_OFFSET, "1970-01-01-00:00:00.000"); //$NON-NLS-1$ + } + + static String getClockMonotonicOffsetText(final ILaunchConfiguration configuration) throws CoreException { + return configuration.getAttribute(CLOCK_MONOTONIC_OFFSET, "0s"); //$NON-NLS-1$ } } diff --git a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationDelegate.java b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationDelegate.java index dd3c666871..7a77fd113f 100644 --- a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationDelegate.java +++ b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchConfigurationDelegate.java @@ -13,6 +13,10 @@ package org.eclipse.fordiac.ide.debug.fb; import java.text.MessageFormat; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.List; import org.eclipse.core.resources.IFile; @@ -25,8 +29,10 @@ import org.eclipse.fordiac.ide.debug.CommonLaunchConfigurationDelegate; import org.eclipse.fordiac.ide.debug.LaunchConfigurationAttributes; import org.eclipse.fordiac.ide.debug.Messages; +import org.eclipse.fordiac.ide.model.eval.AbstractEvaluator; import org.eclipse.fordiac.ide.model.eval.EvaluatorFactory; import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluator; +import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluatorCountingEventQueue; import org.eclipse.fordiac.ide.model.eval.variable.FBVariable; import org.eclipse.fordiac.ide.model.eval.variable.Variable; import org.eclipse.fordiac.ide.model.libraryElement.Event; @@ -46,14 +52,17 @@ public void launch(final ILaunchConfiguration configuration, final String mode, final var keepRunningWhenIdle = FBLaunchConfigurationAttributes.isKeepRunningWhenIdle(configuration); final var defaultArguments = getDefaultArguments(type); final var variables = LaunchConfigurationAttributes.getArguments(configuration, defaultArguments); - final var evaluator = createEvaluator(type, variables); + final FBDebugClockMode clockMode = FBLaunchConfigurationAttributes.getClockMode(configuration); + final Duration clockInterval = FBLaunchConfigurationAttributes.getClockInterval(configuration); + final Instant clockRealtimeOffset = FBLaunchConfigurationAttributes.getClockRealtimeOffset(configuration); + final Instant clockMonotonicOffset = FBLaunchConfigurationAttributes.getClockMonotonicOffset(configuration); - final FBLaunchEventQueue fBLaunchEventQueue = new FBLaunchEventQueue(event, repeatEvent, - keepRunningWhenIdle); - fBLaunchEventQueue.setDebugTimeValue(FBLaunchConfigurationAttributes.getClockMode(configuration), - FBLaunchConfigurationAttributes.getClockInterval(configuration)); - evaluator.setEventQueue(fBLaunchEventQueue); - launch(evaluator, configuration, mode, launch, resource, monitor); + final var eventQueue = new FBLaunchEventQueue(event, repeatEvent, keepRunningWhenIdle); + final var realtimeClock = createClock(clockMode, clockRealtimeOffset, clockInterval, eventQueue); + final var monotonicClock = createClock(clockMode, clockMonotonicOffset, clockInterval, eventQueue); + final var evaluator = createEvaluator(type, variables); + evaluator.setEventQueue(eventQueue); + launch(evaluator, realtimeClock, monotonicClock, configuration, mode, launch, resource, monitor); } } @@ -63,6 +72,16 @@ protected FBEvaluator createEvaluator(final FBType type, final List null; // use system default + case INTERVAL -> new AbstractEvaluator.IntervalClock(offset, interval, ZoneOffset.UTC, queue, + queue.getTotalInputCount().get()); + case FIXED -> Clock.fixed(offset, ZoneOffset.UTC); + }; + } + public static List> getDefaultArguments(final FBType type) throws CoreException { try { return List.copyOf(new FBVariable("dummy", type).getChildren().toList()); //$NON-NLS-1$ diff --git a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchEventQueue.java b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchEventQueue.java index a0c331958d..c6feeeae92 100644 --- a/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchEventQueue.java +++ b/plugins/org.eclipse.fordiac.ide.debug/src/org/eclipse/fordiac/ide/debug/fb/FBLaunchEventQueue.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022-2023 Martin Erich Jobst + * Copyright (c) 2022, 2025 Martin Erich Jobst * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -14,20 +14,14 @@ *******************************************************************************/ package org.eclipse.fordiac.ide.debug.fb; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicLong; -import org.eclipse.fordiac.ide.debug.EvaluatorProcess; -import org.eclipse.fordiac.ide.model.eval.AbstractEvaluator; import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluatorCountingEventQueue; import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluatorExternalEventQueue; import org.eclipse.fordiac.ide.model.libraryElement.Event; @@ -35,15 +29,10 @@ public class FBLaunchEventQueue implements FBEvaluatorCountingEventQueue, FBEvaluatorExternalEventQueue { private final AtomicBoolean repeat; private final AtomicBoolean blocking; - private final AtomicReference clockMode = new AtomicReference<>(FBDebugClockMode.SYSTEM); - private final AtomicReference incrementDebugTime = new AtomicReference<>(Duration.ZERO); - private final AtomicReference currentDebugTime = new AtomicReference<>(Duration.ZERO); - private final AtomicReference currentClock = new AtomicReference<>(AbstractEvaluator.MonotonicClock.UTC); - private EvaluatorProcess evaluator; - private boolean initialized = false; private final BlockingQueue queue = new LinkedBlockingQueue<>(); + private final AtomicLong totalInputEventCount = new AtomicLong(); private final ConcurrentMap eventCounts = new ConcurrentHashMap<>(); /** @@ -69,7 +58,6 @@ public Event receiveInputEvent() throws InterruptedException { final Event result = blocking.get() ? queue.take() : queue.poll(); if (result != null) { incrementEventCount(result); - setDebugTime(); if (repeat.get()) { queue.add(result); } @@ -91,6 +79,9 @@ public boolean triggerInputEvent(final Event event) { protected void incrementEventCount(final Event ev) { final AtomicInteger count = getCount(ev); count.incrementAndGet(); + if (ev.isIsInput()) { + totalInputEventCount.incrementAndGet(); + } } @Override @@ -98,6 +89,11 @@ public AtomicInteger getCount(final Event ev) { return eventCounts.computeIfAbsent(ev, e -> new AtomicInteger()); } + @Override + public AtomicLong getTotalInputCount() { + return totalInputEventCount; + } + /** * Get whether to repeat the last event * @@ -133,89 +129,4 @@ public boolean isBlocking() { public void setBlocking(final boolean blocking) { this.blocking.set(blocking); } - - /** - * Get whether Debug Time is incremented every time an event is triggered. - * - * @return the Debug Time state - */ - public boolean isDebugTimeIncremental() { - return clockMode.get() == FBDebugClockMode.INCREMENT; - } - - /** - * Get whether Debug Time is is fixed to manual time value. - * - * @return the Debug Time state - */ - public boolean isDebugTimeManual() { - return clockMode.get() == FBDebugClockMode.MANUAL; - } - - /** - * Get whether Debug Time is set to default system time (UTC). - * - */ - public boolean isDebugTimeSystem() { - return clockMode.get() == FBDebugClockMode.SYSTEM; - } - - /** - * Set the value of the Debug Time. This value is used to set the Executor - * Clock. Depending on the selected mode, this value is either fixed or - * incremented each time an event is triggered. - * - * @param clockMode the clock mode to be enabled - * @param sleepTime the time value - */ - public void setDebugTimeValue(final FBDebugClockMode clockMode, final Duration sleepTime) { - this.clockMode.set(clockMode); - if (isDebugTimeIncremental()) { - incrementDebugTime.set(sleepTime); - } else if (isDebugTimeManual()) { - currentDebugTime.set(sleepTime); - } - } - - /** - * Get the current intervall time. - * - * @return the time as Duration - */ - public Duration getDebugTimeValue() { - return incrementDebugTime.get(); - } - - /** - * Set the Evaluator Process. - * - * @param evaluatorProcess the current EvaluatorProcess - */ - public void setEvaluatorProcess(final EvaluatorProcess evaluatorProcess) { - evaluator = evaluatorProcess; - updateEvaluatorClock(); - } - - /** - * Set Debug Time in Evaluator depending on mode selection. - * - */ - void setDebugTime() { - Duration debugTime = currentDebugTime.get(); - if (isDebugTimeIncremental() && initialized) { - debugTime = debugTime.plus(incrementDebugTime.get()); - } - currentDebugTime.set(debugTime); - final Instant instant = Instant.ofEpochSecond(debugTime.getSeconds(), debugTime.getNano()); - currentClock.set(isDebugTimeSystem() ? AbstractEvaluator.MonotonicClock.UTC - : Clock.fixed(instant, ZoneId.systemDefault())); - updateEvaluatorClock(); - initialized = true; - } - - private void updateEvaluatorClock() { - if (evaluator != null) { - evaluator.getExecutor().setClock(currentClock.get()); - } - } -} \ No newline at end of file +} diff --git a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/AbstractEvaluator.java b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/AbstractEvaluator.java index 07ae50248f..f0cbdf2d2e 100644 --- a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/AbstractEvaluator.java +++ b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/AbstractEvaluator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022, 2023 Martin Erich Jobst + * Copyright (c) 2022, 2025 Martin Erich Jobst * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -14,6 +14,7 @@ import java.io.Closeable; import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; @@ -23,6 +24,7 @@ import java.util.Objects; import java.util.Set; +import org.eclipse.fordiac.ide.model.eval.fb.FBEvaluatorCountingEventQueue; import org.eclipse.fordiac.ide.model.eval.variable.Variable; public abstract class AbstractEvaluator implements Evaluator { @@ -101,18 +103,32 @@ public static Map getSharedResources() { return evaluatorThread.getExecutor().getSharedResources(); } - public static Clock currentClock() { + public static Clock currentMonotonicClock() { if (Thread.currentThread() instanceof final EvaluatorThread evaluatorThread) { - return evaluatorThread.getExecutor().getClock(); + return evaluatorThread.getExecutor().getMonotonicClock(); } return AbstractEvaluator.MonotonicClock.UTC; } - public static void setClock(final Clock clock) { + public static void setMonotonicClock(final Clock clock) { if (!(Thread.currentThread() instanceof final EvaluatorThread evaluatorThread)) { throw new IllegalStateException("Cannot set clock without evaluator thread"); //$NON-NLS-1$ } - evaluatorThread.getExecutor().setClock(clock); + evaluatorThread.getExecutor().setMonotonicClock(clock); + } + + public static Clock currentRealtimeClock() { + if (Thread.currentThread() instanceof final EvaluatorThread evaluatorThread) { + return evaluatorThread.getExecutor().getRealtimeClock(); + } + return Clock.systemUTC(); + } + + public static void setRealtimeClock(final Clock clock) { + if (!(Thread.currentThread() instanceof final EvaluatorThread evaluatorThread)) { + throw new IllegalStateException("Cannot set clock without evaluator thread"); //$NON-NLS-1$ + } + evaluatorThread.getExecutor().setRealtimeClock(clock); } public static class MonotonicClock extends Clock { @@ -164,4 +180,92 @@ public String toString() { return String.format("%s [zone=%s]", getClass().getName(), zone); //$NON-NLS-1$ } } + + public static class IntervalClock extends Clock { + private final Instant offset; + private final Duration interval; + private final ZoneId zone; + private final FBEvaluatorCountingEventQueue queue; + private final long queueOffset; + + public IntervalClock(final Instant offset, final Duration interval, final ZoneId zone, + final FBEvaluatorCountingEventQueue queue, final long queueOffset) { + this.offset = Objects.requireNonNull(offset); + this.interval = Objects.requireNonNull(interval); + this.zone = Objects.requireNonNull(zone); + this.queue = Objects.requireNonNull(queue); + this.queueOffset = queueOffset; + } + + @Override + public Instant instant() { + return instant(queue.getTotalInputCount().get()); + } + + private Instant instant(final long totalCount) { + return offset.plus(interval.multipliedBy(totalCount - queueOffset)); + } + + @Override + public Clock withZone(final ZoneId zone) { + final long totalCount = queue.getTotalInputCount().get(); + return new IntervalClock(instant(totalCount), interval, zone, queue, totalCount); + } + + public static Clock startingAt(final Clock clock, final Duration interval, + final FBEvaluatorCountingEventQueue queue) { + final long totalCount = queue.getTotalInputCount().get(); + return new IntervalClock(clock != null ? clock.instant() : Instant.EPOCH, interval, + clock != null ? clock.getZone() : ZoneOffset.UTC, queue, totalCount); + } + + public Instant getStart() { + return offset; + } + + public Duration getInterval() { + return interval; + } + + @Override + public ZoneId getZone() { + return zone; + } + + public FBEvaluatorCountingEventQueue getQueue() { + return queue; + } + + public long getQueueOffset() { + return queueOffset; + } + + @Override + public int hashCode() { + return Objects.hash(offset, interval, zone, queue, Long.valueOf(queueOffset)); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IntervalClock other = (IntervalClock) obj; + return Objects.equals(offset, other.offset) && Objects.equals(interval, other.interval) + && Objects.equals(zone, other.zone) && Objects.equals(queue, other.queue) + && queueOffset == other.queueOffset; + } + + @Override + public String toString() { + return String.format("%s [offset=%s, interval=%s, zone=%s]", getClass().getName(), offset, interval, //$NON-NLS-1$ + zone); + } + } } diff --git a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/EvaluatorThreadPoolExecutor.java b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/EvaluatorThreadPoolExecutor.java index ca1b53127f..5d1e6eba79 100644 --- a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/EvaluatorThreadPoolExecutor.java +++ b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/EvaluatorThreadPoolExecutor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Martin Erich Jobst + * Copyright (c) 2023, 2025 Martin Erich Jobst * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -15,6 +15,7 @@ import java.io.Closeable; import java.time.Clock; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.SynchronousQueue; @@ -35,7 +36,8 @@ public class EvaluatorThreadPoolExecutor extends ThreadPoolExecutor { private final Set monitorSet = ConcurrentHashMap.newKeySet(); private final Map context = new ConcurrentHashMap<>(); private final Map sharedResources = new ConcurrentHashMap<>(); - private Clock clock = AbstractEvaluator.MonotonicClock.UTC; + private Clock monotonicClock = AbstractEvaluator.MonotonicClock.UTC; + private Clock realtimeClock = Clock.systemUTC(); /** * Create a new evaluator thread pool executor with unlimited number of threads @@ -142,21 +144,39 @@ public Map getSharedResources() { } /** - * Get the current clock for this executor + * Get the current monotonic clock for this executor * - * @return The clock + * @return The monotonic clock */ - public Clock getClock() { - return clock; + public Clock getMonotonicClock() { + return monotonicClock; } /** - * Set the current clock for this executor + * Set the current monotonic clock for this executor * - * @param clock The clock + * @param monotonicClock The monotonic clock or null to reset to system default */ - public void setClock(final Clock clock) { - this.clock = clock; + public void setMonotonicClock(final Clock monotonicClock) { + this.monotonicClock = Objects.requireNonNullElse(monotonicClock, AbstractEvaluator.MonotonicClock.UTC); + } + + /** + * Get the current realtime clock for this executor + * + * @return The realtime clock + */ + public Clock getRealtimeClock() { + return realtimeClock; + } + + /** + * Set the current realtime clock for this executor + * + * @param realtimeClock The realtime clock or null to reset to system default + */ + public void setRealtimeClock(final Clock realtimeClock) { + this.realtimeClock = Objects.requireNonNullElseGet(realtimeClock, Clock::systemUTC); } @Override diff --git a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/fb/FBEvaluatorCountingEventQueue.java b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/fb/FBEvaluatorCountingEventQueue.java index 65295549a6..229fbb5535 100644 --- a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/fb/FBEvaluatorCountingEventQueue.java +++ b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/fb/FBEvaluatorCountingEventQueue.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023, 2024 Martin Erich Jobst + * Copyright (c) 2023, 2025 Martin Erich Jobst * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,6 +13,7 @@ package org.eclipse.fordiac.ide.model.eval.fb; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.eclipse.fordiac.ide.model.libraryElement.Event; @@ -21,4 +22,9 @@ public interface FBEvaluatorCountingEventQueue extends FBEvaluatorEventQueue { * Get the number of events processed by this queue */ AtomicInteger getCount(final Event event); + + /** + * Get the total number of input events processed by this queue + */ + AtomicLong getTotalInputCount(); } diff --git a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/function/StandardFunctions.java b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/function/StandardFunctions.java index 3b3dea5503..24c7291092 100644 --- a/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/function/StandardFunctions.java +++ b/plugins/org.eclipse.fordiac.ide.model.eval/src/org/eclipse/fordiac/ide/model/eval/function/StandardFunctions.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2023 Martin Erich Jobst, + * Copyright (c) 2022, 2025 Martin Erich Jobst, * Primetals Technologies Austria GmbH * * This program and the accompanying materials are made available under the @@ -25,7 +25,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.function.BinaryOperator; import java.util.function.DoubleBinaryOperator; @@ -2226,20 +2226,28 @@ private static BigInteger convertFromBCD(final BigInteger value) { @Comment("Returns relative time stamp.") static TimeValue NOW_MONOTONIC() { - return TimeValue.toTimeValue(Instant.EPOCH.until(AbstractEvaluator.currentClock().instant(), ChronoUnit.NANOS)); + return TimeValue.toTimeValue( + Instant.EPOCH.until(AbstractEvaluator.currentMonotonicClock().instant(), ChronoUnit.NANOS)); } static DateAndTimeValue NOW() { - return DateAndTimeValue - .toDateAndTimeValue(Instant.EPOCH.until(AbstractEvaluator.currentClock().instant(), ChronoUnit.NANOS)); + return DateAndTimeValue.toDateAndTimeValue( + Instant.EPOCH.until(AbstractEvaluator.currentRealtimeClock().instant(), ChronoUnit.NANOS)); } @OnlySupportedBy("4diac IDE") - @Comment("Returns relative time stamp.") + @Comment("Override the monotonic clock.") static void OVERRIDE_NOW_MONOTONIC(final TimeValue value) { final Duration duration = value.toDuration(); final Instant instant = Instant.ofEpochSecond(duration.getSeconds(), duration.getNano()); - AbstractEvaluator.setClock(Clock.fixed(instant, ZoneId.systemDefault())); + AbstractEvaluator.setMonotonicClock(Clock.fixed(instant, ZoneOffset.UTC)); + } + + @OnlySupportedBy("4diac IDE") + @Comment("Override the real-time clock.") + static void OVERRIDE_NOW(final DateAndTimeValue value) { + final Instant instant = value.toLocalDateTime().toInstant(ZoneOffset.UTC); + AbstractEvaluator.setRealtimeClock(Clock.fixed(instant, ZoneOffset.UTC)); } /* Date and Time conversions */