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
17package com.android.internal.midi;
18
19import java.util.Iterator;
20import java.util.SortedMap;
21import 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 */
28public 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