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