package org.simantics.simulation.sequences.action; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.PriorityQueue; import org.simantics.scl.runtime.SCLContext; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.tuple.Tuple0; public abstract class AbstractActionContext implements ActionContext { public static final double TIME_TOLERANCE = 1e-6; double currentTime; volatile boolean stopped; ArrayList> scheduledNow = new ArrayList<>(); ArrayList> scheduledNextStep = new ArrayList<>(); ArrayList> scheduledWhenStopped = new ArrayList<>(); PriorityQueue scheduledAt = new PriorityQueue<>(); public List exceptions; private static class Task implements Comparable { final double time; final Function1 continuation; public Task(double time, Function1 continuation) { this.time = time; this.continuation = continuation; } @Override public int compareTo(Task o) { return Double.compare(time, o.time); } } @Override public double time() { return currentTime; } @Override public void scheduleNow(Function1 continuation) { scheduledNow.add(continuation); } @Override public void scheduleNextStep(Function1 continuation) { scheduledNextStep.add(continuation); } @Override public void scheduleAt(double time, Function1 continuation) { if(time <= currentTime) scheduleNow(continuation); else scheduledAt.add(new Task(time, continuation)); } @Override public void scheduleWhenStopped(Function1 continuation) { scheduledWhenStopped.add(continuation); } @Override public void stop() { stop(StopReason.STOPPED); } public void stop(StopReason reason) { stopped = true; handleStop(reason); } public boolean isStopped() { synchronized (this) { return stopped || (scheduledNextStep.isEmpty() && scheduledAt.isEmpty()); } } public double handleStep(double currentTime) { synchronized (this) { this.currentTime = currentTime; { ArrayList> temp = scheduledNow; scheduledNow = scheduledNextStep; scheduledNextStep = temp; Collections.reverse(scheduledNow); } SCLContext context = SCLContext.getCurrent(); Object oldActionContext = context.put("sequenceAction", this); try { Task firstTask = scheduledAt.peek(); while(true) { while(!scheduledNow.isEmpty()) { try { Function1 currentContinuation = scheduledNow.remove(scheduledNow.size()-1); currentContinuation.apply(Tuple0.INSTANCE); currentContinuation = null; } catch (Exception e) { if (this.exceptions == null) this.exceptions = new ArrayList<>(); this.exceptions.add(new RuntimeException("Action failure at " + currentTime + ": " + e.getMessage(), e)); } } if(firstTask == null) return Double.POSITIVE_INFINITY; else if(firstTask.time > currentTime+TIME_TOLERANCE) return firstTask.time; else { firstTask.continuation.apply(Tuple0.INSTANCE); synchronized (this) { scheduledAt.remove(); } firstTask = scheduledAt.peek(); } } } finally { context.put("sequenceAction", oldActionContext); } } } private void handleStop(StopReason reason) { synchronized (this) { List> stopFunctions = new ArrayList<>(scheduledWhenStopped); scheduledWhenStopped.clear(); SCLContext context = SCLContext.getCurrent(); Object oldActionContext = context.put("sequenceAction", this); try { stopFunctions.forEach(f -> { try { f.apply(reason); } catch (Exception e) { if (this.exceptions == null) this.exceptions = new ArrayList<>(); this.exceptions.add(new RuntimeException("Stop action failure at " + currentTime + ": " + e.getMessage(), e)); } }); } finally { context.put("sequenceAction", oldActionContext); } } } }