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