Fixed a bug in Am2900; created dlatch8/80; relayouted some components
[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                 while (hasNext() && !condition.getAsBoolean())
98                 {
99                         InnerEvent event;
100                         synchronized (events)
101                         {
102                                 event = events.remove();
103                         }
104                         lastTimeUpdated = event.getTiming();
105                         event.run();
106                         // Don't check after every run
107                         checkStop = (checkStop + 1) % 10;
108                         if (checkStop == 0 && System.currentTimeMillis() >= stopMillis)
109                                 return ExecutionResult.EXEC_OUT_OF_TIME;
110                 }
111                 lastTimeUpdated = getSimulationTime();
112                 return hasNext() ? ExecutionResult.EXEC_UNTIL_EMPTY : ExecutionResult.EXEC_UNTIL_CONDITION;
113         }
114
115         /**
116          * Sets the function, which defines the current simulation time at any time.
117          * 
118          * @param time The return value of calling this function is the current simulation time.
119          */
120         public void setTimeFunction(LongSupplier time)
121         {
122                 this.time = time;
123         }
124
125         /**
126          * Calculates the current simulation time.
127          * 
128          * @return The simulation time as defined by the time function.
129          */
130         public long getSimulationTime()
131         {
132                 return time.getAsLong();
133         }
134
135         /**
136          * Retrieves the timestamp of the next event.
137          * 
138          * @return The timestamp of the next enqueued event, if the {@link Timeline} is not empty, -1 otherwise.
139          */
140         public long nextEventTime()
141         {
142                 if (!hasNext())
143                         return -1;
144                 return events.peek().getTiming();
145         }
146
147         /**
148          * Clears the {@link Timeline} of enqueued events.
149          */
150         public void reset()
151         {
152                 synchronized (events)
153                 {
154                         events.clear();
155                 }
156                 lastTimeUpdated = 0;
157         }
158
159         /**
160          * Adds a listener, that is called when a {@link TimelineEvent} is added.
161          */
162         public void addEventAddedListener(Consumer<TimelineEvent> listener)
163         {
164                 eventAddedListener.add(listener);
165         }
166
167         /**
168          * Removes the listener, if possible. It will no longer be called when a {@link TimelineEvent} is added.
169          */
170         public void removeEventAddedListener(Consumer<TimelineEvent> listener)
171         {
172                 eventAddedListener.remove(listener);
173         }
174
175         /**
176          * Adds an Event to the {@link Timeline}
177          * 
178          * @param function       The {@link TimelineEventHandler} that will be executed, when the {@link InnerEvent} occurs on the timeline.
179          * @param relativeTiming The amount of MI ticks in which the {@link InnerEvent} is called, starting from the current time.
180          */
181         public void addEvent(TimelineEventHandler function, int relativeTiming)
182         {
183                 long timing = getSimulationTime() + relativeTiming;
184                 TimelineEvent event = new TimelineEvent(timing);
185                 synchronized (events)
186                 {
187                         events.add(new InnerEvent(function, event, eventCounter++));
188                 }
189                 eventAddedListener.forEach(l -> l.accept(event));
190         }
191
192         private class InnerEvent implements Runnable, Comparable<InnerEvent>
193         {
194                 private final TimelineEventHandler function;
195                 private final TimelineEvent event;
196                 private final long id;
197
198                 /**
199                  * Creates an {@link InnerEvent}
200                  * 
201                  * @param function {@link TimelineEventHandler} to be executed when the {@link InnerEvent} occurs
202                  * @param timing   Point in the MI simulation {@link Timeline}, at which the {@link InnerEvent} is executed;
203                  */
204                 InnerEvent(TimelineEventHandler function, TimelineEvent event, long id)
205                 {
206                         this.function = function;
207                         this.event = event;
208                         this.id = id;
209                 }
210
211                 public long getTiming()
212                 {
213                         return event.getTiming();
214                 }
215
216                 @Override
217                 public void run()
218                 {
219                         function.handle(event);
220                 }
221
222                 @Override
223                 public String toString()
224                 {
225                         return event.toString();
226                 }
227
228                 @Override
229                 public int compareTo(InnerEvent o)
230                 {
231                         int c1;
232                         return (c1 = timeCmp(getTiming(), o.getTiming())) == 0 ? timeCmp(id, o.id) : c1;
233                 }
234         }
235
236         public static int timeCmp(long a, long b)
237         {
238                 return Long.signum(a - b);
239         }
240
241         @Override
242         public String toString()
243         {
244                 String eventsString;
245                 synchronized (events)
246                 {
247                         eventsString = events.toString();
248                 }
249                 return String.format("Simulation time: %s, Last update: %d, Events: %s", getSimulationTime(), lastTimeUpdated, eventsString);
250         }
251
252         public enum ExecutionResult
253         {
254                 NOTHING_DONE, EXEC_UNTIL_EMPTY, EXEC_UNTIL_CONDITION, EXEC_OUT_OF_TIME
255         }
256 }