273 lines
8.4 KiB
Java
273 lines
8.4 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.internal.midi;
|
|
|
|
import java.util.SortedMap;
|
|
import java.util.TreeMap;
|
|
|
|
/**
|
|
* Store arbitrary timestamped events using a Long timestamp.
|
|
* Only one Thread can write into the buffer.
|
|
* And only one Thread can read from the buffer.
|
|
*/
|
|
public class EventScheduler {
|
|
public static final long NANOS_PER_MILLI = 1000000;
|
|
|
|
private final Object mLock = new Object();
|
|
protected volatile SortedMap<Long, FastEventQueue> mEventBuffer;
|
|
protected FastEventQueue mEventPool = null;
|
|
private int mMaxPoolSize = 200;
|
|
private boolean mClosed;
|
|
|
|
public EventScheduler() {
|
|
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
|
}
|
|
|
|
/**
|
|
* Class for a fast event queue.
|
|
*
|
|
* If we keep at least one node in the list then it can be atomic
|
|
* and non-blocking.
|
|
*/
|
|
public static class FastEventQueue {
|
|
// One thread takes from the beginning of the list.
|
|
volatile SchedulableEvent mFirst;
|
|
// A second thread returns events to the end of the list.
|
|
volatile SchedulableEvent mLast;
|
|
volatile long mEventsAdded;
|
|
volatile long mEventsRemoved;
|
|
|
|
public FastEventQueue(SchedulableEvent event) {
|
|
mFirst = event;
|
|
mLast = mFirst;
|
|
mEventsAdded = 1;
|
|
mEventsRemoved = 0;
|
|
}
|
|
|
|
int size() {
|
|
return (int)(mEventsAdded - mEventsRemoved);
|
|
}
|
|
|
|
/**
|
|
* Do not call this unless there is more than one event
|
|
* in the list.
|
|
* @return first event in the list
|
|
*/
|
|
public SchedulableEvent remove() {
|
|
// Take first event.
|
|
mEventsRemoved++;
|
|
SchedulableEvent event = mFirst;
|
|
mFirst = event.mNext;
|
|
event.mNext = null;
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* @param event
|
|
*/
|
|
public void add(SchedulableEvent event) {
|
|
event.mNext = null;
|
|
mLast.mNext = event;
|
|
mLast = event;
|
|
mEventsAdded++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class for events that can be stored in the EventScheduler.
|
|
*/
|
|
public static class SchedulableEvent {
|
|
private long mTimestamp;
|
|
volatile private SchedulableEvent mNext = null;
|
|
|
|
/**
|
|
* @param timestamp
|
|
*/
|
|
public SchedulableEvent(long timestamp) {
|
|
mTimestamp = timestamp;
|
|
}
|
|
|
|
/**
|
|
* @return timestamp
|
|
*/
|
|
public long getTimestamp() {
|
|
return mTimestamp;
|
|
}
|
|
|
|
/**
|
|
* The timestamp should not be modified when the event is in the
|
|
* scheduling buffer.
|
|
*/
|
|
public void setTimestamp(long timestamp) {
|
|
mTimestamp = timestamp;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an event from the pool.
|
|
* Always leave at least one event in the pool.
|
|
* @return event or null
|
|
*/
|
|
public SchedulableEvent removeEventfromPool() {
|
|
SchedulableEvent event = null;
|
|
if (mEventPool != null && (mEventPool.size() > 1)) {
|
|
event = mEventPool.remove();
|
|
}
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* Return events to a pool so they can be reused.
|
|
*
|
|
* @param event
|
|
*/
|
|
public void addEventToPool(SchedulableEvent event) {
|
|
if (mEventPool == null) {
|
|
mEventPool = new FastEventQueue(event);
|
|
// If we already have enough items in the pool then just
|
|
// drop the event. This prevents unbounded memory leaks.
|
|
} else if (mEventPool.size() < mMaxPoolSize) {
|
|
mEventPool.add(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an event to the scheduler. Events with the same time will be
|
|
* processed in order.
|
|
*
|
|
* @param event
|
|
*/
|
|
public void add(SchedulableEvent event) {
|
|
Object lock = getLock();
|
|
synchronized (lock) {
|
|
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
|
|
if (list == null) {
|
|
long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
|
|
: mEventBuffer.firstKey();
|
|
list = new FastEventQueue(event);
|
|
mEventBuffer.put(event.getTimestamp(), list);
|
|
// If the event we added is earlier than the previous earliest
|
|
// event then notify any threads waiting for the next event.
|
|
if (event.getTimestamp() < lowestTime) {
|
|
lock.notify();
|
|
}
|
|
} else {
|
|
list.add(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected SchedulableEvent removeNextEventLocked(long lowestTime) {
|
|
SchedulableEvent event;
|
|
FastEventQueue list = mEventBuffer.get(lowestTime);
|
|
// Remove list from tree if this is the last node.
|
|
if ((list.size() == 1)) {
|
|
mEventBuffer.remove(lowestTime);
|
|
}
|
|
event = list.remove();
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* Check to see if any scheduled events are ready to be processed.
|
|
*
|
|
* @param timestamp
|
|
* @return next event or null if none ready
|
|
*/
|
|
public SchedulableEvent getNextEvent(long time) {
|
|
SchedulableEvent event = null;
|
|
Object lock = getLock();
|
|
synchronized (lock) {
|
|
if (!mEventBuffer.isEmpty()) {
|
|
long lowestTime = mEventBuffer.firstKey();
|
|
// Is it time for this list to be processed?
|
|
if (lowestTime <= time) {
|
|
event = removeNextEventLocked(lowestTime);
|
|
}
|
|
}
|
|
}
|
|
// Log.i(TAG, "getNextEvent: event = " + event);
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* Return the next available event or wait until there is an event ready to
|
|
* be processed. This method assumes that the timestamps are in nanoseconds
|
|
* and that the current time is System.nanoTime().
|
|
*
|
|
* @return event
|
|
* @throws InterruptedException
|
|
*/
|
|
public SchedulableEvent waitNextEvent() throws InterruptedException {
|
|
SchedulableEvent event = null;
|
|
Object lock = getLock();
|
|
synchronized (lock) {
|
|
while (!mClosed) {
|
|
long millisToWait = Integer.MAX_VALUE;
|
|
if (!mEventBuffer.isEmpty()) {
|
|
long now = System.nanoTime();
|
|
long lowestTime = mEventBuffer.firstKey();
|
|
// Is it time for the earliest list to be processed?
|
|
if (lowestTime <= now) {
|
|
event = removeNextEventLocked(lowestTime);
|
|
break;
|
|
} else {
|
|
// Figure out how long to sleep until next event.
|
|
long nanosToWait = lowestTime - now;
|
|
// Add 1 millisecond so we don't wake up before it is
|
|
// ready.
|
|
millisToWait = 1 + (nanosToWait / NANOS_PER_MILLI);
|
|
// Clip 64-bit value to 32-bit max.
|
|
if (millisToWait > Integer.MAX_VALUE) {
|
|
millisToWait = Integer.MAX_VALUE;
|
|
}
|
|
}
|
|
}
|
|
lock.wait((int) millisToWait);
|
|
}
|
|
}
|
|
return event;
|
|
}
|
|
|
|
protected void flush() {
|
|
// Replace our event buffer with a fresh empty one
|
|
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
|
}
|
|
|
|
/**
|
|
* Stops the EventScheduler.
|
|
* The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null.
|
|
*/
|
|
public void close() {
|
|
Object lock = getLock();
|
|
synchronized (lock) {
|
|
mClosed = true;
|
|
lock.notify();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the lock. This doesn't lock it in anyway.
|
|
* Subclasses can override this.
|
|
*
|
|
* @return Object
|
|
*/
|
|
protected Object getLock() {
|
|
return mLock;
|
|
}
|
|
}
|