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