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