Home | History | Annotate | Download | only in midi
      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 
     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 
     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 
     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          */
     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          */
     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          */
    100         public SchedulableEvent(long timestamp) {
    101             mTimestamp = timestamp;
    102         }
    103 
    104         /**
    105          * @return timestamp
    106          */
    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          */
    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      */
    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      */
    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      */
    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.
    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      */
    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      */
    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