/* * Copyright 2024, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include // #pragma mark - media_timed_event 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(); } // #pragma mark - media_timed_event global operators 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; } // #pragma mark - BTimedEventQueue struct queue_entry : public DoublyLinkedListLinkImpl { media_timed_event event; }; typedef DoublyLinkedList QueueEntryList; class BPrivate::TimedEventQueueData { public: TimedEventQueueData() : fLock("BTimedEventQueue"), 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() { BAutolock locker(fEntryAllocationLock); if (fFreeEntries.Head() != NULL) return fFreeEntries.RemoveHead(); // We need a new chunk. 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) { BAutolock locker(fEntryAllocationLock); fFreeEntries.Add(entry); // TODO: Chunks are currently only freed in the destructor. // (Is that a problem? They're probably rarely used, anyway.) } 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: BLocker fEntryAllocationLock; QueueEntryList fFreeEntries; QueueEntryList fChunkHeads; queue_entry fInlineEntries[8]; }; using BPrivate::TimedEventQueueData; BTimedEventQueue::BTimedEventQueue() : fData(new TimedEventQueueData) { CALLED(); } BTimedEventQueue::~BTimedEventQueue() { CALLED(); 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(); // No cleanup. 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) { // No cleanup. *_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; // Insert the new event after this entry. 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) { // B_DELETE is a keyboard code, but the Be Book indicates it's a valid // cleanup value. (Early sample code may have used it too.) cleanup = BTimedEventQueue::B_USER_CLEANUP; } if (cleanup == BTimedEventQueue::B_NO_CLEANUP) { // Nothing to do. } else if (entry->event.type == BTimedEventQueue::B_HANDLE_BUFFER && cleanup == BTimedEventQueue::B_RECYCLE_BUFFER) { (reinterpret_cast(entry->event.pointer))->Recycle(); } else if (cleanup == BTimedEventQueue::B_EXPIRE_TIMER) { // TimerExpired() is invoked in BMediaEventLooper::DispatchEvent; nothing to do. } 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.MoveFrom(&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) { // Nothing to check. } 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; } // #pragma mark - C++ binary compatibility 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() {}