Fixed 2 Simulation bugs
[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 processedUntil = 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                         processedUntil = time;
31                 }
32
33                 @Override
34                 public long getTime()
35                 {
36                         return processedUntil;
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                         processedUntil = 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                         processedUntil = 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                                 return ExecutionResult.EXEC_OUT_OF_TIME;
142                         }
143                 }
144                 notWorking();
145                 processedUntil = getSimulationTime();
146                 return hasNext() ? ExecutionResult.EXEC_UNTIL_EMPTY : ExecutionResult.EXEC_UNTIL_CONDITION;
147         }
148
149         /**
150          * Sets the function, which defines the current simulation time at any time.
151          * 
152          * @param time The return value of calling this function is the current simulation time.
153          */
154         public void setTimeFunction(TimeFunction time)
155         {
156                 time.setTime(this.time.getTime());
157                 this.time = time;
158         }
159
160         /**
161          * Calculates the current simulation time.
162          * 
163          * @return The simulation time as defined by the time function.
164          */
165         public long getSimulationTime()
166         {
167                 return isWorking() ? processedUntil : time.getTime();
168         }
169
170         /**
171          * Retrieves the timestamp of the next event.
172          * 
173          * @return The timestamp of the next enqueued event, if the {@link Timeline} is not empty, -1 otherwise.
174          */
175         public long nextEventTime()
176         {
177                 if (!hasNext())
178                         return -1;
179                 return events.peek().getTiming();
180         }
181
182         /**
183          * Clears the {@link Timeline} of enqueued events.
184          */
185         public void reset()
186         {
187                 synchronized (events)
188                 {
189                         events.clear();
190                 }
191                 processedUntil = time.getTime();
192         }
193
194         /**
195          * Adds a listener, that is called when a {@link TimelineEvent} is added.
196          */
197         public void addEventAddedListener(Consumer<TimelineEvent> listener)
198         {
199                 eventAddedListener.add(listener);
200         }
201
202         /**
203          * Removes the listener, if possible. It will no longer be called when a {@link TimelineEvent} is added.
204          */
205         public void removeEventAddedListener(Consumer<TimelineEvent> listener)
206         {
207                 eventAddedListener.remove(listener);
208         }
209
210         /**
211          * Adds an Event to the {@link Timeline}
212          * 
213          * @param function       The {@link TimelineEventHandler} that will be executed, when the {@link InnerEvent} occurs on the timeline.
214          * @param relativeTiming The amount of MI ticks in which the {@link InnerEvent} is called, starting from the current time.
215          */
216         public void addEvent(TimelineEventHandler function, int relativeTiming)
217         {
218                 long timing = getSimulationTime() + relativeTiming;
219                 TimelineEvent event = new TimelineEvent(timing);
220                 synchronized (events)
221                 {
222                         events.add(new InnerEvent(function, event, eventCounter++));
223                 }
224                 eventAddedListener.forEach(l -> l.accept(event));
225         }
226
227         //@formatter:off
228         private void working() { isWorking = true; }
229         private void notWorking() { isWorking = false; }
230         private boolean isWorking() { return isWorking; }
231         //@formatter:on
232
233         private class InnerEvent implements Runnable, Comparable<InnerEvent>
234         {
235                 private final TimelineEventHandler function;
236                 private final TimelineEvent event;
237                 private final long id;
238
239                 /**
240                  * Creates an {@link InnerEvent}
241                  * 
242                  * @param function {@link TimelineEventHandler} to be executed when the {@link InnerEvent} occurs
243                  * @param timing   Point in the MI simulation {@link Timeline}, at which the {@link InnerEvent} is executed;
244                  */
245                 InnerEvent(TimelineEventHandler function, TimelineEvent event, long id)
246                 {
247                         this.function = function;
248                         this.event = event;
249                         this.id = id;
250                 }
251
252                 public long getTiming()
253                 {
254                         return event.getTiming();
255                 }
256
257                 @Override
258                 public void run()
259                 {
260                         function.handle(event);
261                 }
262
263                 @Override
264                 public String toString()
265                 {
266                         return event.toString();
267                 }
268
269                 @Override
270                 public int compareTo(InnerEvent o)
271                 {
272                         int c1;
273                         return (c1 = timeCmp(getTiming(), o.getTiming())) == 0 ? timeCmp(id, o.id) : c1;
274                 }
275         }
276
277         public static int timeCmp(long a, long b)
278         {
279                 return Long.signum(a - b);
280         }
281
282         @Override
283         public String toString()
284         {
285                 String eventsString;
286                 synchronized (events)
287                 {
288                         eventsString = events.toString();
289                 }
290                 return String.format("Simulation time: %s, Last update: %d, Events: %s", getSimulationTime(), processedUntil, eventsString);
291         }
292
293         public static enum ExecutionResult
294         {
295                 NOTHING_DONE, EXEC_UNTIL_EMPTY, EXEC_UNTIL_CONDITION, EXEC_OUT_OF_TIME
296         }
297
298         public static interface TimeFunction
299         {
300                 long getTime();
301
302                 void setTime(long time);
303         }
304
305         /**
306          * Sets the time of the {@link TimeFunction} to the timestamp of the latest processed event
307          */
308         public void synchTime()
309         {
310                 time.setTime(processedUntil);
311         }
312 }