Setting simulation speed higher should no longer have an effect on logic
[Mograsim.git] / plugins / net.mograsim.logic.core / src / net / mograsim / logic / core / timeline / Timeline.java
1 package net.mograsim.logic.core.timeline;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.PriorityQueue;
6 import java.util.function.BooleanSupplier;
7 import java.util.function.Consumer;
8
9 /**
10  * Orders Events by the time they are due to be executed. Can execute Events individually.
11  * 
12  * @author Fabian Stemmler
13  *
14  */
15 public class Timeline
16 {
17         private PriorityQueue<InnerEvent> events;
18         private TimeFunction time;
19         private long lastTimeUpdated = 0;
20         private long eventCounter = 0;
21
22         private final List<Consumer<TimelineEvent>> eventAddedListener;
23
24         public final TimeFunction stepByStepExec = new TimeFunction()
25         {
26
27                 @Override
28                 public void setTime(long time)
29                 {
30                         lastTimeUpdated = time;
31                 }
32
33                 @Override
34                 public long getTime()
35                 {
36                         return lastTimeUpdated;
37                 }
38         };
39         public final TimeFunction realTimeExec = new TimeFunction()
40         {
41                 private long offset = 0;
42
43                 @Override
44                 public void setTime(long time)
45                 {
46                         offset = time;
47                 }
48
49                 @Override
50                 public long getTime()
51                 {
52                         return System.currentTimeMillis() - offset;
53                 }
54         };
55         private boolean isWorking;
56
57         /**
58          * Constructs a Timeline object. Per default the time function is set to step by step execution.
59          * 
60          * @param initCapacity The initial capacity of the event queue.
61          */
62         public Timeline(int initCapacity)
63         {
64                 events = new PriorityQueue<>(initCapacity);
65                 eventAddedListener = new ArrayList<>();
66                 time = stepByStepExec;
67         }
68
69         /**
70          * @param timestamp exclusive
71          * @return <code>true</code> if the first event in queue is later than the given timestamp
72          */
73         public BooleanSupplier laterThan(long timestamp)
74         {
75                 return () -> timeCmp(events.peek().getTiming(), timestamp) > 0;
76         }
77
78         /**
79          * @return <code>true</code> if there is at least one event enqueued. <code>false</code> otherwise
80          */
81         public boolean hasNext()
82         {
83                 return !events.isEmpty();
84         }
85
86         /**
87          * Executes all events at the next timestamp, at which there are any
88          */
89         public void executeNext()
90         {
91                 InnerEvent first = events.peek();
92                 if (first != null)
93                         executeUntil(laterThan(first.getTiming()), -1);
94         }
95
96         /**
97          * Executes all events enqueued in the {@link Timeline}. Use very carefully! Events may generate new events, causing an infinite loop.
98          */
99         public void executeAll()
100         {
101                 while (hasNext())
102                         executeNext();
103         }
104
105         /**
106          * Executes all events until a given condition is met. The simulation process can be constrained by a real world timestamp.
107          * 
108          * @param condition  the condition until which the events are be processed
109          * @param stopMillis the System.currentTimeMillis() when simulation definitely needs to stop. A value of -1 means no timeout.
110          * @return State of the event execution
111          * @formatter:off
112          * <code>NOTHING_DONE</code> if the {@link Timeline} was already empty
113          * <code>EXEC_OUT_OF_TIME</code> if the given maximum time was reached
114          * <code>EXEC_UNTIL_CONDITION</code> if the condition was met
115          * <code>EXEC_UNTIL_EMPTY</code> if events were executed until the {@link Timeline} was empty
116          * @formatter:on
117          */
118         public ExecutionResult executeUntil(BooleanSupplier condition, long stopMillis)
119         {
120                 if (events.isEmpty())
121                 {
122                         lastTimeUpdated = getSimulationTime();
123                         return ExecutionResult.NOTHING_DONE;
124                 }
125                 working();
126                 int checkStop = 0;
127                 while (hasNext() && !condition.getAsBoolean())
128                 {
129                         InnerEvent event;
130                         synchronized (events)
131                         {
132                                 event = events.remove();
133                         }
134                         lastTimeUpdated = event.getTiming();
135                         event.run();
136                         // Don't check after every run
137                         checkStop = (checkStop + 1) % 10;
138                         if (checkStop == 0 && System.currentTimeMillis() >= stopMillis)
139                         {
140                                 notWorking();
141                                 lastTimeUpdated = getSimulationTime();
142                                 return ExecutionResult.EXEC_OUT_OF_TIME;
143                         }
144                 }
145                 notWorking();
146                 lastTimeUpdated = getSimulationTime();
147                 return hasNext() ? ExecutionResult.EXEC_UNTIL_EMPTY : ExecutionResult.EXEC_UNTIL_CONDITION;
148         }
149
150         /**
151          * Sets the function, which defines the current simulation time at any time.
152          * 
153          * @param time The return value of calling this function is the current simulation time.
154          */
155         public void setTimeFunction(TimeFunction time)
156         {
157                 time.setTime(this.time.getTime());
158                 this.time = time;
159         }
160
161         /**
162          * Calculates the current simulation time.
163          * 
164          * @return The simulation time as defined by the time function.
165          */
166         public long getSimulationTime()
167         {
168                 return isWorking() ? lastTimeUpdated : time.getTime();
169         }
170
171         /**
172          * Retrieves the timestamp of the next event.
173          * 
174          * @return The timestamp of the next enqueued event, if the {@link Timeline} is not empty, -1 otherwise.
175          */
176         public long nextEventTime()
177         {
178                 if (!hasNext())
179                         return -1;
180                 return events.peek().getTiming();
181         }
182
183         /**
184          * Clears the {@link Timeline} of enqueued events.
185          */
186         public void reset()
187         {
188                 synchronized (events)
189                 {
190                         events.clear();
191                 }
192                 lastTimeUpdated = time.getTime();
193         }
194
195         /**
196          * Adds a listener, that is called when a {@link TimelineEvent} is added.
197          */
198         public void addEventAddedListener(Consumer<TimelineEvent> listener)
199         {
200                 eventAddedListener.add(listener);
201         }
202
203         /**
204          * Removes the listener, if possible. It will no longer be called when a {@link TimelineEvent} is added.
205          */
206         public void removeEventAddedListener(Consumer<TimelineEvent> listener)
207         {
208                 eventAddedListener.remove(listener);
209         }
210
211         /**
212          * Adds an Event to the {@link Timeline}
213          * 
214          * @param function       The {@link TimelineEventHandler} that will be executed, when the {@link InnerEvent} occurs on the timeline.
215          * @param relativeTiming The amount of MI ticks in which the {@link InnerEvent} is called, starting from the current time.
216          */
217         public void addEvent(TimelineEventHandler function, int relativeTiming)
218         {
219                 long timing = getSimulationTime() + relativeTiming;
220                 TimelineEvent event = new TimelineEvent(timing);
221                 synchronized (events)
222                 {
223                         events.add(new InnerEvent(function, event, eventCounter++));
224                 }
225                 eventAddedListener.forEach(l -> l.accept(event));
226         }
227
228         //@formatter:off
229         private void working() { isWorking = true; }
230         private void notWorking() { isWorking = false; }
231         private boolean isWorking() { return isWorking; }
232         //@formatter:on
233
234         private class InnerEvent implements Runnable, Comparable<InnerEvent>
235         {
236                 private final TimelineEventHandler function;
237                 private final TimelineEvent event;
238                 private final long id;
239
240                 /**
241                  * Creates an {@link InnerEvent}
242                  * 
243                  * @param function {@link TimelineEventHandler} to be executed when the {@link InnerEvent} occurs
244                  * @param timing   Point in the MI simulation {@link Timeline}, at which the {@link InnerEvent} is executed;
245                  */
246                 InnerEvent(TimelineEventHandler function, TimelineEvent event, long id)
247                 {
248                         this.function = function;
249                         this.event = event;
250                         this.id = id;
251                 }
252
253                 public long getTiming()
254                 {
255                         return event.getTiming();
256                 }
257
258                 @Override
259                 public void run()
260                 {
261                         function.handle(event);
262                 }
263
264                 @Override
265                 public String toString()
266                 {
267                         return event.toString();
268                 }
269
270                 @Override
271                 public int compareTo(InnerEvent o)
272                 {
273                         int c1;
274                         return (c1 = timeCmp(getTiming(), o.getTiming())) == 0 ? timeCmp(id, o.id) : c1;
275                 }
276         }
277
278         public static int timeCmp(long a, long b)
279         {
280                 return Long.signum(a - b);
281         }
282
283         @Override
284         public String toString()
285         {
286                 String eventsString;
287                 synchronized (events)
288                 {
289                         eventsString = events.toString();
290                 }
291                 return String.format("Simulation time: %s, Last update: %d, Events: %s", getSimulationTime(), lastTimeUpdated, eventsString);
292         }
293
294         public static enum ExecutionResult
295         {
296                 NOTHING_DONE, EXEC_UNTIL_EMPTY, EXEC_UNTIL_CONDITION, EXEC_OUT_OF_TIME
297         }
298
299         public static interface TimeFunction
300         {
301                 long getTime();
302
303                 void setTime(long time);
304         }
305 }