1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.midi;
18 
19 import java.util.Iterator;
20 import java.util.SortedMap;
21 import java.util.TreeMap;
22 
23 /**
24  * Store arbitrary timestamped events using a Long timestamp.
25  * Only one Thread can write into the buffer.
26  * And only one Thread can read from the buffer.
27  */
28 public class EventScheduler {
29     private static final long NANOS_PER_MILLI = 1000000;
30 
31     private final Object mLock = new Object();
32     volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
33     private FastEventQueue mEventPool = null;
34     private int mMaxPoolSize = 200;
35     private boolean mClosed;
36 
EventScheduler()37     public EventScheduler() {
38         mEventBuffer = new TreeMap<Long, FastEventQueue>();
39     }
40 
41     // If we keep at least one node in the list then it can be atomic
42     // and non-blocking.
43     private class FastEventQueue {
44         // One thread takes from the beginning of the list.
45         volatile SchedulableEvent mFirst;
46         // A second thread returns events to the end of the list.
47         volatile SchedulableEvent mLast;
48         volatile long mEventsAdded;
49         volatile long mEventsRemoved;
50 
FastEventQueue(SchedulableEvent event)51         FastEventQueue(SchedulableEvent event) {
52             mFirst = event;
53             mLast = mFirst;
54             mEventsAdded = 1;
55             mEventsRemoved = 0;
56         }
57 
size()58         int size() {
59             return (int)(mEventsAdded - mEventsRemoved);
60         }
61 
62         /**
63          * Do not call this unless there is more than one event
64          * in the list.
65          * @return first event in the list
66          */
remove()67         public SchedulableEvent remove() {
68             // Take first event.
69             mEventsRemoved++;
70             SchedulableEvent event = mFirst;
71             mFirst = event.mNext;
72             event.mNext = null;
73             return event;
74         }
75 
76         /**
77          * @param event
78          */
add(SchedulableEvent event)79         public void add(SchedulableEvent event) {
80             event.mNext = null;
81             mLast.mNext = event;
82             mLast = event;
83             mEventsAdded++;
84         }
85     }
86 
87     /**
88      * Base class for events that can be stored in the EventScheduler.
89      */
90     public static class SchedulableEvent {
91         private long mTimestamp;
92         volatile private SchedulableEvent mNext = null;
93 
94         /**
95          * @param timestamp
96          */
SchedulableEvent(long timestamp)97         public SchedulableEvent(long timestamp) {
98             mTimestamp = timestamp;
99         }
100 
101         /**
102          * @return timestamp
103          */
getTimestamp()104         public long getTimestamp() {
105             return mTimestamp;
106         }
107 
108         /**
109          * The timestamp should not be modified when the event is in the
110          * scheduling buffer.
111          */
setTimestamp(long timestamp)112         public void setTimestamp(long timestamp) {
113             mTimestamp = timestamp;
114         }
115     }
116 
117     /**
118      * Get an event from the pool.
119      * Always leave at least one event in the pool.
120      * @return event or null
121      */
removeEventfromPool()122     public SchedulableEvent removeEventfromPool() {
123         SchedulableEvent event = null;
124         if (mEventPool != null && (mEventPool.size() > 1)) {
125             event = mEventPool.remove();
126         }
127         return event;
128     }
129 
130     /**
131      * Return events to a pool so they can be reused.
132      *
133      * @param event
134      */
addEventToPool(SchedulableEvent event)135     public void addEventToPool(SchedulableEvent event) {
136         if (mEventPool == null) {
137             mEventPool = new FastEventQueue(event);
138         // If we already have enough items in the pool then just
139         // drop the event. This prevents unbounded memory leaks.
140         } else if (mEventPool.size() < mMaxPoolSize) {
141             mEventPool.add(event);
142         }
143     }
144 
145     /**
146      * Add an event to the scheduler. Events with the same time will be
147      * processed in order.
148      *
149      * @param event
150      */
add(SchedulableEvent event)151     public void add(SchedulableEvent event) {
152         synchronized (mLock) {
153             FastEventQueue list = mEventBuffer.get(event.getTimestamp());
154             if (list == null) {
155                 long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
156                         : mEventBuffer.firstKey();
157                 list = new FastEventQueue(event);
158                 mEventBuffer.put(event.getTimestamp(), list);
159                 // If the event we added is earlier than the previous earliest
160                 // event then notify any threads waiting for the next event.
161                 if (event.getTimestamp() < lowestTime) {
162                     mLock.notify();
163                 }
164             } else {
165                 list.add(event);
166             }
167         }
168     }
169 
removeNextEventLocked(long lowestTime)170     private SchedulableEvent removeNextEventLocked(long lowestTime) {
171         SchedulableEvent event;
172         FastEventQueue list = mEventBuffer.get(lowestTime);
173         // Remove list from tree if this is the last node.
174         if ((list.size() == 1)) {
175             mEventBuffer.remove(lowestTime);
176         }
177         event = list.remove();
178         return event;
179     }
180 
181     /**
182      * Check to see if any scheduled events are ready to be processed.
183      *
184      * @param timestamp
185      * @return next event or null if none ready
186      */
getNextEvent(long time)187     public SchedulableEvent getNextEvent(long time) {
188         SchedulableEvent event = null;
189         synchronized (mLock) {
190             if (!mEventBuffer.isEmpty()) {
191                 long lowestTime = mEventBuffer.firstKey();
192                 // Is it time for this list to be processed?
193                 if (lowestTime <= time) {
194                     event = removeNextEventLocked(lowestTime);
195                 }
196             }
197         }
198         // Log.i(TAG, "getNextEvent: event = " + event);
199         return event;
200     }
201 
202     /**
203      * Return the next available event or wait until there is an event ready to
204      * be processed. This method assumes that the timestamps are in nanoseconds
205      * and that the current time is System.nanoTime().
206      *
207      * @return event
208      * @throws InterruptedException
209      */
waitNextEvent()210     public SchedulableEvent waitNextEvent() throws InterruptedException {
211         SchedulableEvent event = null;
212         synchronized (mLock) {
213             while (!mClosed) {
214                 long millisToWait = Integer.MAX_VALUE;
215                 if (!mEventBuffer.isEmpty()) {
216                     long now = System.nanoTime();
217                     long lowestTime = mEventBuffer.firstKey();
218                     // Is it time for the earliest list to be processed?
219                     if (lowestTime <= now) {
220                         event = removeNextEventLocked(lowestTime);
221                         break;
222                     } else {
223                         // Figure out how long to sleep until next event.
224                         long nanosToWait = lowestTime - now;
225                         // Add 1 millisecond so we don't wake up before it is
226                         // ready.
227                         millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI);
228                         // Clip 64-bit value to 32-bit max.
229                         if (millisToWait > Integer.MAX_VALUE) {
230                             millisToWait = Integer.MAX_VALUE;
231                         }
232                     }
233                 }
234                 mLock.wait((int) millisToWait);
235             }
236         }
237         return event;
238     }
239 
flush()240     protected void flush() {
241         // Replace our event buffer with a fresh empty one
242         mEventBuffer = new TreeMap<Long, FastEventQueue>();
243     }
244 
close()245     public void close() {
246         synchronized (mLock) {
247             mClosed = true;
248             mLock.notify();
249         }
250     }
251 }
252