X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.simulator.toolkit%2Fsrc%2Forg%2Fsimantics%2Fsimulator%2Ftoolkit%2FDynamicExperimentThread.java;fp=bundles%2Forg.simantics.simulator.toolkit%2Fsrc%2Forg%2Fsimantics%2Fsimulator%2Ftoolkit%2FDynamicExperimentThread.java;h=66cec38a46b62664c0a5037af4d24060378730b2;hp=0000000000000000000000000000000000000000;hb=14a4f7a9d486fba5be815e511fb2a497fca4eb70;hpb=751ee12501d220832b672dd433655a4d65806fd9 diff --git a/bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/DynamicExperimentThread.java b/bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/DynamicExperimentThread.java new file mode 100644 index 000000000..66cec38a4 --- /dev/null +++ b/bundles/org.simantics.simulator.toolkit/src/org/simantics/simulator/toolkit/DynamicExperimentThread.java @@ -0,0 +1,298 @@ +package org.simantics.simulator.toolkit; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.concurrent.Callable; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Semaphore; + +import org.simantics.simulator.ExperimentState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Antti Villberg + * @since 1.34.0 + */ +abstract public class DynamicExperimentThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicExperimentThread.class); + + private CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); + + private ExperimentState state = StandardExperimentStates.CREATED; + + private long runStart = 0; + + private double desiredRealtimeRatio = 1000.0; + private double obtainedRealtimeRatio = 1.0; + + private long runTimeNs = 0; + private long endTimeNs = 0; + protected long simulationStepNs = 0; + protected double stepInSeconds = 1.0; + + public DynamicExperimentThread() { + } + + private void updateTimes() { + long time = System.nanoTime(); + long elapsed = time-runStart; + + obtainedRealtimeRatio = longToDoubleDivision(runTimeNs, elapsed); + } + + private long rt; + private long rt_l; + + protected double longToDoubleDivision(long l1, long l2) { + rt = l1 / l2; + rt_l = l1 % l2; + double d = ((double)rt_l)/((double)l2); + d += (double)rt; + return d; + } + + protected ArrayList tasks = new ArrayList<>(); + + abstract public void step(double stepLengthNanoSeconds); + + public boolean inState(Class state) { + return state.isInstance(this.state); + } + + public void initialize() throws Exception { + } + + public void deinitialize() throws Exception { + } + + long stepTime = 0; + long taskTime = 0; + + @Override + public void run() { + + try { + + try { + + initialize(); + + try { + runReally(); + } catch (Exception e) { + LOGGER.error("Unhandled exception while running simulation thread", e); + } + + } catch (Exception e) { + LOGGER.error("Unhandled exception while initializing simulation thread", e); + } + + } finally { + + try { + deinitialize(); + } catch (Exception e) { + LOGGER.error("Error while deinitializing simulation thread", e); + } + + } + + } + + protected boolean inActiveState() { + return !( + inState(StandardExperimentStates.Disposed.class) + || inState(StandardExperimentStates.Disposing.class) + //|| inState(StandardExperimentStates.Failure.class) + || inState(StandardExperimentStates.ToBeDisposed.class) + ); + } + + private void runReally() { + + while(inActiveState()) { + + if(inState(StandardExperimentStates.Running.class)) { + + long asd = System.nanoTime(); + step(simulationStepNs); + stepTime += System.nanoTime() - asd; + runTimeNs += simulationStepNs; + updateTimes(); + long asd2 = System.nanoTime(); + runTasks(); + taskTime += System.nanoTime() - asd2; + + System.err.println(" st = " + 1e-9*stepTime + " tt = " + 1e-9*taskTime); + + while(obtainedRealtimeRatio > desiredRealtimeRatio) { + int ran = runTasks(); + if(ran == 0) { + long elapsed = System.nanoTime()-runStart; + long deltaNs = BigDecimal.valueOf(runTimeNs).divide(BigDecimal.valueOf(desiredRealtimeRatio)).longValue() - elapsed; + + long deltaMs = deltaNs / 1000000; + int deltaNsRem = (int)(deltaNs % 1000000); + + if(deltaNs > 0) { + synchronized(tasks) { + try { + tasks.wait(deltaMs, deltaNsRem); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + updateTimes(); + } + + } else { + + while(!inState(StandardExperimentStates.Running.class) && inActiveState()) { + + synchronized(tasks) { + int ran = runTasks(); + if(ran == 0) { + try { + tasks.wait(Integer.MAX_VALUE); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + } + + } + + if(runTimeNs >= endTimeNs && inActiveState()) + changeState(StandardExperimentStates.STOPPED); + + } + + } + + Thread executorThread = this; + + Semaphore beginSyncExec = new Semaphore(0); + Semaphore endSyncExec = new Semaphore(0); + + Runnable scheduleSyncExec = () -> { + beginSyncExec.release(); + try { + endSyncExec.acquire(); + } catch (InterruptedException e) { + } + }; + + public int runTasks() { + ArrayList todo = new ArrayList<>(); + synchronized(tasks) { + todo.addAll(tasks); + tasks.clear(); + } + todo.forEach(Runnable::run); + return todo.size(); + } + + public void queue(Runnable runnable) { + synchronized(tasks) { + tasks.add(runnable); + tasks.notify(); + } + } + + public T syncExec(Callable callable) throws InterruptedException { + + if(executorThread == Thread.currentThread()) { + try { + return callable.call(); + } catch (Throwable t) { + LOGGER.error("syncExec in current thread failed", t); + return null; + } finally { + } + } + + queue(scheduleSyncExec); + + beginSyncExec.acquire(); + Thread oldThread = executorThread; + executorThread = Thread.currentThread(); + try { + return callable.call(); + } catch (Throwable t) { + LOGGER.error("syncExec failed", t); + return null; + } finally { + executorThread = oldThread; + endSyncExec.release(); + } + + } + + public void asyncExec(Runnable runnable) { + + if(executorThread == Thread.currentThread()) { + try { + runnable.run(); + } catch (Throwable t) { + LOGGER.error("asyncExec failed", t); + } finally { + } + return; + } + + queue(runnable); + + } + + public void setSimulationStepNs(long ns) { + simulationStepNs = ns; + stepInSeconds = BigDecimal.valueOf(simulationStepNs).multiply(BigDecimal.valueOf(1e-9)).doubleValue(); + } + + public void runDuration(long ns) { + runStart = System.nanoTime(); + runTimeNs = 0; + endTimeNs = ns; + synchronized(tasks) { + changeState(StandardExperimentStates.RUNNING); + tasks.notify(); + } + } + + public ExperimentState getExperimentState() { + return state; + } + + public void changeState(ExperimentState state) { + this.state = state; + fireStateChanged(state); + } + + public void addListener(DynamicExperimentThreadListener listener) { + if(!listeners.contains(listener)) + listeners.add(listener); + } + + public void removeListener(DynamicExperimentThreadListener listener) { + listeners.remove(listener); + } + + protected void fireAfterStep() { + listeners.forEach(DynamicExperimentThreadListener::afterStep); + } + + protected void fireBeforeStep() { + listeners.forEach(DynamicExperimentThreadListener::beforeStep); + } + + protected void fireStateChanged(ExperimentState newState) { + listeners.forEach(l -> l.stateChanged(newState)); + } + +} \ No newline at end of file