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