Home | History | Annotate | Download | only in midi
      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 
     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 
     51         FastEventQueue(SchedulableEvent event) {
     52             mFirst = event;
     53             mLast = mFirst;
     54             mEventsAdded = 1;
     55             mEventsRemoved = 0;
     56         }
     57 
     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          */
     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          */
     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          */
     97         public SchedulableEvent(long timestamp) {
     98             mTimestamp = timestamp;
     99         }
    100 
    101         /**
    102          * @return timestamp
    103          */
    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          */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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 
    240     protected void flush() {
    241         // Replace our event buffer with a fresh empty one
    242         mEventBuffer = new TreeMap<Long, FastEventQueue>();
    243     }
    244 
    245     public void close() {
    246         synchronized (mLock) {
    247             mClosed = true;
    248             mLock.notify();
    249         }
    250     }
    251 }
    252