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