1 /*
2  * Copyright (C) 2015 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.example.android.common.midi;
18 
19 import java.util.SortedMap;
20 import java.util.TreeMap;
21 
22 /**
23  * Store SchedulableEvents in a timestamped buffer.
24  * Events may be written in any order.
25  * Events will be read in sorted order.
26  * Events with the same timestamp will be read in the order they were added.
27  *
28  * Only one Thread can write into the buffer.
29  * And only one Thread can read from the buffer.
30  */
31 public class EventScheduler {
32     private static final long NANOS_PER_MILLI = 1000000;
33 
34     private final Object lock = new Object();
35     private SortedMap<Long, FastEventQueue> mEventBuffer;
36     // This does not have to be guarded. It is only set by the writing thread.
37     // If the reader sees a null right before being set then that is OK.
38     private FastEventQueue mEventPool = null;
39     private static final int MAX_POOL_SIZE = 200;
40 
EventScheduler()41     public EventScheduler() {
42         mEventBuffer = new TreeMap<Long, FastEventQueue>();
43     }
44 
45     // If we keep at least one node in the list then it can be atomic
46     // and non-blocking.
47     private class FastEventQueue {
48         // One thread takes from the beginning of the list.
49         volatile SchedulableEvent mFirst;
50         // A second thread returns events to the end of the list.
51         volatile SchedulableEvent mLast;
52         volatile long mEventsAdded;
53         volatile long mEventsRemoved;
54 
FastEventQueue(SchedulableEvent event)55         FastEventQueue(SchedulableEvent event) {
56             mFirst = event;
57             mLast = mFirst;
58             mEventsAdded = 1; // Always created with one event added. Never empty.
59             mEventsRemoved = 0; // None removed yet.
60         }
61 
size()62         int size() {
63             return (int)(mEventsAdded - mEventsRemoved);
64         }
65 
66         /**
67          * Do not call this unless there is more than one event
68          * in the list.
69          * @return first event in the list
70          */
remove()71         public SchedulableEvent remove() {
72             // Take first event.
73             mEventsRemoved++;
74             SchedulableEvent event = mFirst;
75             mFirst = event.mNext;
76             return event;
77         }
78 
79         /**
80          * @param event
81          */
add(SchedulableEvent event)82         public void add(SchedulableEvent event) {
83             event.mNext = null;
84             mLast.mNext = event;
85             mLast = event;
86             mEventsAdded++;
87         }
88     }
89 
90     /**
91      * Base class for events that can be stored in the EventScheduler.
92      */
93     public static class SchedulableEvent {
94         private long mTimestamp;
95         private SchedulableEvent mNext = null;
96 
97         /**
98          * @param timestamp
99          */
SchedulableEvent(long timestamp)100         public SchedulableEvent(long timestamp) {
101             mTimestamp = timestamp;
102         }
103 
104         /**
105          * @return timestamp
106          */
getTimestamp()107         public long getTimestamp() {
108             return mTimestamp;
109         }
110 
111         /**
112          * The timestamp should not be modified when the event is in the
113          * scheduling buffer.
114          */
setTimestamp(long timestamp)115         public void setTimestamp(long timestamp) {
116             mTimestamp = timestamp;
117         }
118     }
119 
120     /**
121      * Get an event from the pool.
122      * Always leave at least one event in the pool.
123      * @return event or null
124      */
removeEventfromPool()125     public SchedulableEvent removeEventfromPool() {
126         SchedulableEvent event = null;
127         if (mEventPool != null && (mEventPool.size() > 1)) {
128             event = mEventPool.remove();
129         }
130         return event;
131     }
132 
133     /**
134      * Return events to a pool so they can be reused.
135      *
136      * @param event
137      */
addEventToPool(SchedulableEvent event)138     public void addEventToPool(SchedulableEvent event) {
139         if (mEventPool == null) {
140             mEventPool = new FastEventQueue(event);
141         // If we already have enough items in the pool then just
142         // drop the event. This prevents unbounded memory leaks.
143         } else if (mEventPool.size() < MAX_POOL_SIZE) {
144             mEventPool.add(event);
145         }
146     }
147 
148     /**
149      * Add an event to the scheduler. Events with the same time will be
150      * processed in order.
151      *
152      * @param event
153      */
add(SchedulableEvent event)154     public void add(SchedulableEvent event) {
155         synchronized (lock) {
156             FastEventQueue list = mEventBuffer.get(event.getTimestamp());
157             if (list == null) {
158                 long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
159                         : mEventBuffer.firstKey();
160                 list = new FastEventQueue(event);
161                 mEventBuffer.put(event.getTimestamp(), list);
162                 // If the event we added is earlier than the previous earliest
163                 // event then notify any threads waiting for the next event.
164                 if (event.getTimestamp() < lowestTime) {
165                     lock.notify();
166                 }
167             } else {
168                 list.add(event);
169             }
170         }
171     }
172 
173     // Caller must synchronize on lock before calling.
removeNextEventLocked(long lowestTime)174     private SchedulableEvent removeNextEventLocked(long lowestTime) {
175         SchedulableEvent event;
176         FastEventQueue list = mEventBuffer.get(lowestTime);
177         // Remove list from tree if this is the last node.
178         if ((list.size() == 1)) {
179             mEventBuffer.remove(lowestTime);
180         }
181         event = list.remove();
182         return event;
183     }
184 
185     /**
186      * Check to see if any scheduled events are ready to be processed.
187      *
188      * @param timestamp
189      * @return next event or null if none ready
190      */
getNextEvent(long time)191     public SchedulableEvent getNextEvent(long time) {
192         SchedulableEvent event = null;
193         synchronized (lock) {
194             if (!mEventBuffer.isEmpty()) {
195                 long lowestTime = mEventBuffer.firstKey();
196                 // Is it time for this list to be processed?
197                 if (lowestTime <= time) {
198                     event = removeNextEventLocked(lowestTime);
199                 }
200             }
201         }
202         // Log.i(TAG, "getNextEvent: event = " + event);
203         return event;
204     }
205 
206     /**
207      * Return the next available event or wait until there is an event ready to
208      * be processed. This method assumes that the timestamps are in nanoseconds
209      * and that the current time is System.nanoTime().
210      *
211      * @return event
212      * @throws InterruptedException
213      */
waitNextEvent()214     public SchedulableEvent waitNextEvent() throws InterruptedException {
215         SchedulableEvent event = null;
216         while (true) {
217             long millisToWait = Integer.MAX_VALUE;
218             synchronized (lock) {
219                 if (!mEventBuffer.isEmpty()) {
220                     long now = System.nanoTime();
221                     long lowestTime = mEventBuffer.firstKey();
222                     // Is it time for the earliest list to be processed?
223                     if (lowestTime <= now) {
224                         event = removeNextEventLocked(lowestTime);
225                         break;
226                     } else {
227                         // Figure out how long to sleep until next event.
228                         long nanosToWait = lowestTime - now;
229                         // Add 1 millisecond so we don't wake up before it is
230                         // ready.
231                         millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI);
232                         // Clip 64-bit value to 32-bit max.
233                         if (millisToWait > Integer.MAX_VALUE) {
234                             millisToWait = Integer.MAX_VALUE;
235                         }
236                     }
237                 }
238                 lock.wait((int) millisToWait);
239             }
240         }
241         return event;
242     }
243 }
244