Skip to content

Most visited

Recently visited

navigation
MidiScope / src / com.example.android.common.midi /

EventScheduler.java

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
}
This site uses cookies to store your preferences for site-specific language and display options.

Hooray!