1607957935
* Added helper methods and rewrote Remove() to take advantage of the fact that the event list is ordered. * Introduced a termination flag to shutdown the thread as early as possible. * Event::Do() get a pointer to the queue as parameter. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@1515 a95241bf-73f2-0310-859d-f6bbb57e9c96
406 lines
11 KiB
C++
406 lines
11 KiB
C++
//------------------------------------------------------------------------------
|
|
// Copyright (c) 2001-2002, OpenBeOS
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// File Name: EventQueue.cpp
|
|
// Author: Ingo Weinhold (bonefish@users.sf.net)
|
|
// YellowBites (http://www.yellowbites.com)
|
|
// Description: A class providing a mechanism for executing events at
|
|
// specified times.
|
|
//------------------------------------------------------------------------------
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <String.h>
|
|
|
|
#include "Event.h"
|
|
#include "EventQueue.h"
|
|
|
|
static const char *kDefaultEventQueueName = "event looper";
|
|
|
|
/*!
|
|
\class EventQueue
|
|
\brief A class providing a mechanism for executing events at specified
|
|
times.
|
|
|
|
The class' interface is quite small. It basically features methods to
|
|
add or remove an event or to modify an event's time. It is derived from
|
|
BLocker to inherit a locking mechanism needed to serialize the access to
|
|
its member variables (especially the event list).
|
|
|
|
The queue runs an own thread (in _EventLooper()), which executes the
|
|
events at the right times. The execution of an event consists of invoking
|
|
its Event::Do() method. If the event's Event::IsAutoDelete() or its Do()
|
|
method return \c true, the event object is deleted after execution. In
|
|
any case the event is removed from the list before it is executed. The
|
|
queue is not locked while an event is executed.
|
|
|
|
The event list (\a fEvents) is ordered ascendingly by time. The thread
|
|
uses a semaphore (\a fLooperControl) to wait (time out) for the next
|
|
event. This semaphore is released to indicate changes to the event list.
|
|
*/
|
|
|
|
/*! \var BList EventQueue::fEvents
|
|
\brief List of events (Event*) in ascending order (by time).
|
|
*/
|
|
|
|
/*! \var thread_id EventQueue::fEventLooper
|
|
\brief Thread ID of the queue's thread.
|
|
*/
|
|
|
|
/*! \var sem_id EventQueue::fLooperControl
|
|
\brief Queue thread control semaphore.
|
|
|
|
The semaphore is initialized with count \c 0 and used by the queue's
|
|
thread for timing out at the time when the next event has to be executed.
|
|
If the event list is changed by another thread and that changed the time
|
|
of the next event the semaphore is released (_Reschedule()).
|
|
*/
|
|
|
|
/*! \var volatile bigtime_t EventQueue::fNextEventTime
|
|
\brief Time at which the queue's thread will wake up next time.
|
|
*/
|
|
|
|
/*! \var status_t EventQueue::fStatus
|
|
\brief Initialization status.
|
|
*/
|
|
|
|
/*! \var volatile bool EventQueue::fTerminating
|
|
\brief Set to \c true by Die() to signal the queue's thread not to
|
|
execute any more events.
|
|
*/
|
|
|
|
|
|
// constructor
|
|
/*! \brief Creates a new event queue.
|
|
|
|
The status of the initialization can and should be check with InitCheck().
|
|
|
|
\param name The name used for the queue's thread and semaphore. If \c NULL,
|
|
a default name is used.
|
|
*/
|
|
EventQueue::EventQueue(const char *name)
|
|
: fEvents(100),
|
|
fEventLooper(-1),
|
|
fLooperControl(-1),
|
|
fNextEventTime(0),
|
|
fStatus(B_ERROR),
|
|
fTerminating(false)
|
|
{
|
|
if (!name)
|
|
name = kDefaultEventQueueName;
|
|
fLooperControl = create_sem(0, (BString(name) += " control").String());
|
|
if (fLooperControl >= B_OK)
|
|
fStatus = B_OK;
|
|
else
|
|
fStatus = fLooperControl;
|
|
if (fStatus == B_OK) {
|
|
fEventLooper = spawn_thread(_EventLooperEntry, name, B_NORMAL_PRIORITY,
|
|
this);
|
|
if (fEventLooper >= B_OK) {
|
|
fStatus = B_OK;
|
|
resume_thread(fEventLooper);
|
|
} else
|
|
fStatus = fEventLooper;
|
|
}
|
|
}
|
|
|
|
// destructor
|
|
/*! \brief Frees all resources associated by this object.
|
|
|
|
Die() is called to terminate the queue's thread and all events whose
|
|
IsAutoDelete() returns \c true are deleted.
|
|
*/
|
|
EventQueue::~EventQueue()
|
|
{
|
|
Die();
|
|
while (Event *event = (Event*)fEvents.RemoveItem(0L)) {
|
|
if (event->IsAutoDelete())
|
|
delete event;
|
|
}
|
|
}
|
|
|
|
// InitCheck
|
|
/*! \brief Returns the initialization status of the event queue.
|
|
\return \c B_OK, if everything went fine, an error code otherwise.
|
|
*/
|
|
status_t
|
|
EventQueue::InitCheck()
|
|
{
|
|
return fStatus;
|
|
}
|
|
|
|
// Die
|
|
/*! \brief Terminates the queue's thread.
|
|
|
|
If an event is currently executed, it is allowed to finish its task
|
|
normally, but no more events will be executed.
|
|
|
|
Afterwards events can still be added to and removed from the queue.
|
|
*/
|
|
void
|
|
EventQueue::Die()
|
|
{
|
|
fTerminating = true;
|
|
if (delete_sem(fLooperControl) == B_OK) {
|
|
int32 dummy;
|
|
wait_for_thread(fEventLooper, &dummy);
|
|
}
|
|
}
|
|
|
|
// AddEvent
|
|
/*! \brief Adds a new event to the queue.
|
|
|
|
The event's time must be set, before adding it. Afterwards ModifyEvent()
|
|
must be used to change an event's time.
|
|
|
|
If the event's time is already passed, it is executed as soon as possible.
|
|
|
|
\param event The event to be added.
|
|
\return \c true, if the event has been added successfully, \c false, if
|
|
an error occured.
|
|
*/
|
|
bool
|
|
EventQueue::AddEvent(Event *event)
|
|
{
|
|
Lock();
|
|
bool result = (event && _AddEvent(event));
|
|
if (result)
|
|
_Reschedule();
|
|
Unlock();
|
|
return result;
|
|
}
|
|
|
|
// RemoveEvent
|
|
/*! \brief Removes an event from the queue.
|
|
\param event The event to be removed.
|
|
\return \c true, if the event has been removed successfully, \c false, if
|
|
the supplied event wasn't in the queue before.
|
|
*/
|
|
bool
|
|
EventQueue::RemoveEvent(Event *event)
|
|
{
|
|
bool result = false;
|
|
Lock();
|
|
if (event && ((result = _RemoveEvent(event))))
|
|
_Reschedule();
|
|
Unlock();
|
|
return result;
|
|
}
|
|
|
|
// ModifyEvent
|
|
/*! \brief Modifies an event's time.
|
|
|
|
The event must be in the queue.
|
|
|
|
If the event's new time is already passed, it is executed as soon as
|
|
possible.
|
|
|
|
\param event The event in question.
|
|
\param newTime The new event time.
|
|
*/
|
|
void
|
|
EventQueue::ModifyEvent(Event *event, bigtime_t newTime)
|
|
{
|
|
Lock();
|
|
if (fEvents.RemoveItem(event)) {
|
|
event->SetTime(newTime);
|
|
_AddEvent(event);
|
|
_Reschedule();
|
|
}
|
|
Unlock();
|
|
}
|
|
|
|
// _AddEvent
|
|
/*! \brief Adds an event to the event list.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
|
|
\param event The event to be added.
|
|
\return \c true, if the event has been added successfully, \c false, if
|
|
an error occured.
|
|
*/
|
|
bool
|
|
EventQueue::_AddEvent(Event *event)
|
|
{
|
|
int32 index = _FindInsertionIndex(event->Time());
|
|
return fEvents.AddItem(event, index);
|
|
}
|
|
|
|
// _RemoveEvent
|
|
/*! \brief Removes an event from the event list.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
|
|
\param event The event to be removed.
|
|
\return \c true, if the event has been removed successfully, \c false, if
|
|
the supplied event wasn't in the queue before.
|
|
*/
|
|
bool
|
|
EventQueue::_RemoveEvent(Event *event)
|
|
{
|
|
int32 index = _IndexOfEvent(event);
|
|
return (index >= 0 && fEvents.RemoveItem(index));
|
|
}
|
|
|
|
// _EventAt
|
|
/*! \brief Returns an event from the event list.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
|
|
\param index The list index of the event to be returned.
|
|
\return The event, or \c NULL, if the index is out of range.
|
|
*/
|
|
Event*
|
|
EventQueue::_EventAt(int32 index) const
|
|
{
|
|
return (Event*)fEvents.ItemAt(index);
|
|
}
|
|
|
|
// _IndexOfEvent
|
|
/*! \brief Returns the event list index of the supplied event.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
|
|
\param event The event to be found.
|
|
\return The event list index of the supplied event or \c -1, if the event
|
|
is not in the event list.
|
|
*/
|
|
int32
|
|
EventQueue::_IndexOfEvent(Event *event) const
|
|
{
|
|
bigtime_t time = event->Time();
|
|
int32 index = _FindInsertionIndex(time);
|
|
// The found index is the index succeeding the one of the last event with
|
|
// the same time.
|
|
// search backwards for the event
|
|
Event *listEvent = NULL;
|
|
while (((listEvent = _EventAt(--index))) && listEvent->Time() == time) {
|
|
if (listEvent == event)
|
|
return index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// _FindInsertionIndex
|
|
/*! \brief Finds the event list index at which an event with the supplied
|
|
has to be added.
|
|
|
|
The returned index is the greatest possible index, that is if there are
|
|
events with the same time in the list, the index is the successor of the
|
|
index of the last of these events.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
|
|
\param time The event time.
|
|
\return The index at which an event with the supplied time should be added.
|
|
*/
|
|
int32
|
|
EventQueue::_FindInsertionIndex(bigtime_t time) const
|
|
{
|
|
// binary search
|
|
int32 lower = 0;
|
|
int32 upper = fEvents.CountItems();
|
|
// invariant: lower-1:time <= time < upper:time (ignoring invalid indices)
|
|
while (lower < upper) {
|
|
int32 mid = (lower + upper) / 2;
|
|
Event* midEvent = _EventAt(mid);
|
|
if (time < midEvent->Time())
|
|
upper = mid; // time < mid:time => time < upper:time
|
|
else
|
|
lower = mid + 1; // mid:time <= time => lower-1:time <= time
|
|
}
|
|
return lower;
|
|
}
|
|
|
|
// _EventLooperEntry
|
|
/*! \brief Entry point from the queue's thread.
|
|
\param data The queue's \c this pointer.
|
|
\return The thread's result. Of no relevance in this case.
|
|
*/
|
|
int32
|
|
EventQueue::_EventLooperEntry(void *data)
|
|
{
|
|
return ((EventQueue*)data)->_EventLooper();
|
|
}
|
|
|
|
// _EventLooper
|
|
/*! \brief Method with the main loop of the queue's thread.
|
|
\return The thread's result. Of no relevance in this case.
|
|
*/
|
|
int32
|
|
EventQueue::_EventLooper()
|
|
{
|
|
bool running = true;
|
|
while (running) {
|
|
bigtime_t waitUntil = B_INFINITE_TIMEOUT;
|
|
if (Lock()) {
|
|
if (!fEvents.IsEmpty())
|
|
waitUntil = _EventAt(0)->Time();
|
|
fNextEventTime = waitUntil;
|
|
Unlock();
|
|
}
|
|
status_t err = acquire_sem_etc(fLooperControl, 1, B_ABSOLUTE_TIMEOUT,
|
|
waitUntil);
|
|
switch (err) {
|
|
case B_TIMED_OUT:
|
|
// do events, that are supposed to go off
|
|
while (!fTerminating && Lock() && !fEvents.IsEmpty()
|
|
&& system_time() >= _EventAt(0)->Time()) {
|
|
Event *event = (Event*)fEvents.RemoveItem(0L);
|
|
Unlock();
|
|
bool autoDeleteEvent = event->IsAutoDelete();
|
|
bool deleteEvent = event->Do(this) || autoDeleteEvent;
|
|
if (deleteEvent)
|
|
delete event;
|
|
}
|
|
if (IsLocked())
|
|
Unlock();
|
|
break;
|
|
case B_BAD_SEM_ID:
|
|
running = false;
|
|
break;
|
|
case B_OK:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// _Reschedule
|
|
/*! \brief To be called, when an event has been added or removed.
|
|
|
|
Checks whether the queue's thread has to recalculate the time when it
|
|
needs to wake up to execute the next event, and signals the thread to
|
|
do so, if necessary.
|
|
|
|
\note The object must be locked when this method is invoked.
|
|
*/
|
|
void
|
|
EventQueue::_Reschedule()
|
|
{
|
|
if (fStatus == B_OK) {
|
|
if (!fEvents.IsEmpty() && _EventAt(0)->Time() < fNextEventTime)
|
|
release_sem(fLooperControl);
|
|
}
|
|
}
|
|
|