* Copyright 2024, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <TimedEventQueue.h>
#include <Autolock.h>
#include <Buffer.h>
#include <MediaDebug.h>
#include <InterfaceDefs.h>
#include <util/DoublyLinkedList.h>
#include <locks.h>
media_timed_event::media_timed_event()
{
CALLED();
memset(this, 0, sizeof(*this));
}
media_timed_event::media_timed_event(bigtime_t inTime, int32 inType)
{
CALLED();
memset(this, 0, sizeof(*this));
event_time = inTime;
type = inType;
}
media_timed_event::media_timed_event(bigtime_t inTime, int32 inType,
void* inPointer, uint32 inCleanup)
{
CALLED();
memset(this, 0, sizeof(*this));
event_time = inTime;
type = inType;
pointer = inPointer;
cleanup = inCleanup;
}
media_timed_event::media_timed_event(bigtime_t inTime, int32 inType,
void* inPointer, uint32 inCleanup,
int32 inData, int64 inBigdata,
const char* inUserData, size_t dataSize)
{
CALLED();
memset(this, 0, sizeof(*this));
event_time = inTime;
type = inType;
pointer = inPointer;
cleanup = inCleanup;
data = inData;
bigdata = inBigdata;
memcpy(user_data, inUserData,
min_c(sizeof(media_timed_event::user_data), dataSize));
}
media_timed_event::media_timed_event(const media_timed_event& clone)
{
CALLED();
*this = clone;
}
void
media_timed_event::operator=(const media_timed_event& clone)
{
CALLED();
memcpy(this, &clone, sizeof(*this));
}
media_timed_event::~media_timed_event()
{
CALLED();
}
bool
operator==(const media_timed_event& a, const media_timed_event& b)
{
CALLED();
return (memcmp(&a, &b, sizeof(media_timed_event)) == 0);
}
bool
operator!=(const media_timed_event& a, const media_timed_event& b)
{
CALLED();
return (memcmp(&a, &b, sizeof(media_timed_event)) != 0);
}
bool
operator<(const media_timed_event& a, const media_timed_event& b)
{
CALLED();
return a.event_time < b.event_time;
}
bool
operator>(const media_timed_event& a, const media_timed_event& b)
{
CALLED();
return a.event_time > b.event_time;
}
namespace {
struct queue_entry : public DoublyLinkedListLinkImpl<queue_entry> {
media_timed_event event;
};
typedef DoublyLinkedList<queue_entry> QueueEntryList;
}
class BPrivate::TimedEventQueueData {
public:
TimedEventQueueData()
:
fLock("BTimedEventQueue")
{
mutex_init(&fEntryAllocationLock, "BTimedEventQueue entry allocation");
fEventCount = 0;
fCleanupHook = NULL;
fCleanupHookContext = NULL;
for (size_t i = 0; i < B_COUNT_OF(fInlineEntries); i++)
fFreeEntries.Add(&fInlineEntries[i]);
}
~TimedEventQueueData()
{
while (queue_entry* chunk = fChunkHeads.RemoveHead())
free(chunk);
}
queue_entry* AllocateEntry()
{
MutexLocker locker(fEntryAllocationLock);
if (fFreeEntries.Head() != NULL)
return fFreeEntries.RemoveHead();
const size_t chunkSize = B_PAGE_SIZE;
queue_entry* newEntries = (queue_entry*)malloc(chunkSize);
fChunkHeads.Add(&newEntries[0]);
for (size_t i = 1; i < (chunkSize / sizeof(queue_entry)); i++)
fFreeEntries.Add(&newEntries[i]);
return fFreeEntries.RemoveHead();
}
void FreeEntry(queue_entry* entry)
{
MutexLocker locker(fEntryAllocationLock);
fFreeEntries.Add(entry);
}
status_t AddEntry(queue_entry* newEntry);
void RemoveEntry(queue_entry* newEntry);
void CleanupAndFree(queue_entry* entry);
public:
BLocker fLock;
QueueEntryList fEvents;
int32 fEventCount;
BTimedEventQueue::cleanup_hook fCleanupHook;
void* fCleanupHookContext;
private:
mutex fEntryAllocationLock;
QueueEntryList fFreeEntries;
QueueEntryList fChunkHeads;
queue_entry fInlineEntries[8];
};
using BPrivate::TimedEventQueueData;
BTimedEventQueue::BTimedEventQueue()
: fData(new TimedEventQueueData)
{
CALLED();
}
BTimedEventQueue::~BTimedEventQueue()
{
CALLED();
FlushEvents(0, B_ALWAYS);
delete fData;
}
status_t
BTimedEventQueue::AddEvent(const media_timed_event& event)
{
CALLED();
if (event.type < B_START)
return B_BAD_VALUE;
queue_entry* newEntry = fData->AllocateEntry();
if (newEntry == NULL)
return B_NO_MEMORY;
newEntry->event = event;
BAutolock locker(fData->fLock);
return fData->AddEntry(newEntry);
}
status_t
BTimedEventQueue::RemoveEvent(const media_timed_event* event)
{
CALLED();
BAutolock locker(fData->fLock);
QueueEntryList::Iterator it = fData->fEvents.GetIterator();
while (queue_entry* entry = it.Next()) {
if (entry->event != *event)
continue;
fData->RemoveEntry(entry);
locker.Unlock();
fData->FreeEntry(entry);
return B_OK;
}
return B_ERROR;
}
status_t
BTimedEventQueue::RemoveFirstEvent(media_timed_event* _event)
{
CALLED();
BAutolock locker(fData->fLock);
if (fData->fEventCount == 0)
return B_ERROR;
queue_entry* entry = fData->fEvents.Head();
fData->RemoveEntry(entry);
locker.Unlock();
if (_event != NULL) {
*_event = entry->event;
fData->FreeEntry(entry);
} else {
fData->CleanupAndFree(entry);
}
return B_OK;
}
status_t
TimedEventQueueData::AddEntry(queue_entry* newEntry)
{
ASSERT(fLock.IsLocked());
if (fEvents.IsEmpty()) {
fEvents.Add(newEntry);
fEventCount++;
return B_OK;
}
if (fEvents.First()->event.event_time > newEntry->event.event_time) {
fEvents.Add(newEntry, false);
fEventCount++;
return B_OK;
}
QueueEntryList::ReverseIterator it = fEvents.GetReverseIterator();
while (queue_entry* entry = it.Next()) {
if (newEntry->event.event_time < entry->event.event_time)
continue;
fEvents.InsertAfter(entry, newEntry);
fEventCount++;
return B_OK;
}
debugger("BTimedEventQueue::AddEvent: Invalid queue!");
return B_ERROR;
}
void
TimedEventQueueData::RemoveEntry(queue_entry* entry)
{
ASSERT(fLock.IsLocked());
fEvents.Remove(entry);
fEventCount--;
}
void
TimedEventQueueData::CleanupAndFree(queue_entry* entry)
{
uint32 cleanup = entry->event.cleanup;
if (cleanup == B_DELETE) {
cleanup = BTimedEventQueue::B_USER_CLEANUP;
}
if (cleanup == BTimedEventQueue::B_NO_CLEANUP) {
} else if (entry->event.type == BTimedEventQueue::B_HANDLE_BUFFER
&& cleanup == BTimedEventQueue::B_RECYCLE_BUFFER) {
(reinterpret_cast<BBuffer*>(entry->event.pointer))->Recycle();
} else if (cleanup == BTimedEventQueue::B_EXPIRE_TIMER) {
} else if (cleanup >= BTimedEventQueue::B_USER_CLEANUP) {
if (fCleanupHook != NULL)
(*fCleanupHook)(&entry->event, fCleanupHookContext);
} else {
ERROR("BTimedEventQueue: Unhandled cleanup! (type %" B_PRId32 ", "
"cleanup %" B_PRId32 ")\n", entry->event.type, entry->event.cleanup);
}
FreeEntry(entry);
}
void
BTimedEventQueue::SetCleanupHook(cleanup_hook hook, void* context)
{
CALLED();
BAutolock lock(fData->fLock);
fData->fCleanupHook = hook;
fData->fCleanupHookContext = context;
}
bool
BTimedEventQueue::HasEvents() const
{
CALLED();
BAutolock locker(fData->fLock);
return fData->fEventCount != 0;
}
int32
BTimedEventQueue::EventCount() const
{
CALLED();
BAutolock locker(fData->fLock);
return fData->fEventCount;
}
const media_timed_event*
BTimedEventQueue::FirstEvent() const
{
CALLED();
BAutolock locker(fData->fLock);
queue_entry* entry = fData->fEvents.First();
if (entry == NULL)
return NULL;
return &entry->event;
}
bigtime_t
BTimedEventQueue::FirstEventTime() const
{
CALLED();
BAutolock locker(fData->fLock);
queue_entry* entry = fData->fEvents.First();
if (entry == NULL)
return B_INFINITE_TIMEOUT;
return entry->event.event_time;
}
const media_timed_event*
BTimedEventQueue::LastEvent() const
{
CALLED();
BAutolock locker(fData->fLock);
queue_entry* entry = fData->fEvents.Last();
if (entry == NULL)
return NULL;
return &entry->event;
}
bigtime_t
BTimedEventQueue::LastEventTime() const
{
CALLED();
BAutolock locker(fData->fLock);
queue_entry* entry = fData->fEvents.Last();
if (entry == NULL)
return B_INFINITE_TIMEOUT;
return entry->event.event_time;
}
const media_timed_event*
BTimedEventQueue::FindFirstMatch(bigtime_t eventTime,
time_direction direction, bool inclusive, int32 eventType)
{
CALLED();
BAutolock locker(fData->fLock);
QueueEntryList::Iterator it = fData->fEvents.GetIterator();
while (queue_entry* entry = it.Next()) {
int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
if (match == B_DONE)
break;
if (match == B_NO_ACTION)
continue;
return &entry->event;
}
return NULL;
}
status_t
BTimedEventQueue::DoForEach(for_each_hook hook, void* context,
bigtime_t eventTime, time_direction direction,
bool inclusive, int32 eventType)
{
CALLED();
BAutolock locker(fData->fLock);
bool resort = false;
QueueEntryList::Iterator it = fData->fEvents.GetIterator();
while (queue_entry* entry = it.Next()) {
int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
if (match == B_DONE)
break;
if (match == B_NO_ACTION)
continue;
queue_action action = hook(&entry->event, context);
if (action == B_DONE)
break;
switch (action) {
case B_REMOVE_EVENT:
fData->RemoveEntry(entry);
fData->CleanupAndFree(entry);
break;
case B_RESORT_QUEUE:
resort = true;
break;
case B_NO_ACTION:
default:
break;
}
}
if (resort) {
QueueEntryList entries;
entries.TakeFrom(&fData->fEvents);
fData->fEventCount = 0;
while (queue_entry* entry = entries.RemoveHead())
fData->AddEntry(entry);
}
return B_OK;
}
status_t
BTimedEventQueue::FlushEvents(bigtime_t eventTime, time_direction direction,
bool inclusive, int32 eventType)
{
CALLED();
BAutolock locker(fData->fLock);
QueueEntryList::Iterator it = fData->fEvents.GetIterator();
while (queue_entry* entry = it.Next()) {
int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
if (match == B_DONE)
break;
if (match == B_NO_ACTION)
continue;
fData->RemoveEntry(entry);
fData->CleanupAndFree(entry);
}
return B_OK;
}
int
BTimedEventQueue::_Match(const media_timed_event& event,
bigtime_t eventTime, time_direction direction,
bool inclusive, int32 eventType)
{
if (direction == B_ALWAYS) {
} else if (direction == B_BEFORE_TIME) {
if (event.event_time > eventTime)
return B_DONE;
if (event.event_time == eventTime && !inclusive)
return B_DONE;
} else if (direction == B_AT_TIME) {
if (event.event_time > eventTime)
return B_DONE;
if (event.event_time != eventTime)
return B_NO_ACTION;
} else if (direction == B_AFTER_TIME) {
if (event.event_time < eventTime)
return B_NO_ACTION;
if (event.event_time == eventTime && !inclusive)
return B_NO_ACTION;
}
if (eventType != B_ANY_EVENT && eventType != event.type)
return B_NO_ACTION;
return 1;
}
void*
BTimedEventQueue::operator new(size_t size)
{
CALLED();
return ::operator new(size);
}
void
BTimedEventQueue::operator delete(void* ptr, size_t s)
{
CALLED();
return ::operator delete(ptr);
}
void BTimedEventQueue::_ReservedTimedEventQueue0() {}
void BTimedEventQueue::_ReservedTimedEventQueue1() {}
void BTimedEventQueue::_ReservedTimedEventQueue2() {}
void BTimedEventQueue::_ReservedTimedEventQueue3() {}
void BTimedEventQueue::_ReservedTimedEventQueue4() {}
void BTimedEventQueue::_ReservedTimedEventQueue5() {}
void BTimedEventQueue::_ReservedTimedEventQueue6() {}
void BTimedEventQueue::_ReservedTimedEventQueue7() {}
void BTimedEventQueue::_ReservedTimedEventQueue8() {}
void BTimedEventQueue::_ReservedTimedEventQueue9() {}
void BTimedEventQueue::_ReservedTimedEventQueue10() {}
void BTimedEventQueue::_ReservedTimedEventQueue11() {}
void BTimedEventQueue::_ReservedTimedEventQueue12() {}
void BTimedEventQueue::_ReservedTimedEventQueue13() {}
void BTimedEventQueue::_ReservedTimedEventQueue14() {}
void BTimedEventQueue::_ReservedTimedEventQueue15() {}
void BTimedEventQueue::_ReservedTimedEventQueue16() {}
void BTimedEventQueue::_ReservedTimedEventQueue17() {}
void BTimedEventQueue::_ReservedTimedEventQueue18() {}
void BTimedEventQueue::_ReservedTimedEventQueue19() {}
void BTimedEventQueue::_ReservedTimedEventQueue20() {}
void BTimedEventQueue::_ReservedTimedEventQueue21() {}
void BTimedEventQueue::_ReservedTimedEventQueue22() {}
void BTimedEventQueue::_ReservedTimedEventQueue23() {}