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