1 /*
2 * Copyright 2024, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6 #include <TimedEventQueue.h>
7
8 #include <Autolock.h>
9 #include <Buffer.h>
10 #include <MediaDebug.h>
11 #include <InterfaceDefs.h>
12 #include <util/DoublyLinkedList.h>
13
14
15 // #pragma mark - media_timed_event
16
17
media_timed_event()18 media_timed_event::media_timed_event()
19 {
20 CALLED();
21 memset(this, 0, sizeof(*this));
22 }
23
24
media_timed_event(bigtime_t inTime,int32 inType)25 media_timed_event::media_timed_event(bigtime_t inTime, int32 inType)
26 {
27 CALLED();
28 memset(this, 0, sizeof(*this));
29
30 event_time = inTime;
31 type = inType;
32 }
33
34
media_timed_event(bigtime_t inTime,int32 inType,void * inPointer,uint32 inCleanup)35 media_timed_event::media_timed_event(bigtime_t inTime, int32 inType,
36 void* inPointer, uint32 inCleanup)
37 {
38 CALLED();
39 memset(this, 0, sizeof(*this));
40
41 event_time = inTime;
42 type = inType;
43 pointer = inPointer;
44 cleanup = inCleanup;
45 }
46
47
media_timed_event(bigtime_t inTime,int32 inType,void * inPointer,uint32 inCleanup,int32 inData,int64 inBigdata,const char * inUserData,size_t dataSize)48 media_timed_event::media_timed_event(bigtime_t inTime, int32 inType,
49 void* inPointer, uint32 inCleanup,
50 int32 inData, int64 inBigdata,
51 const char* inUserData, size_t dataSize)
52 {
53 CALLED();
54 memset(this, 0, sizeof(*this));
55
56 event_time = inTime;
57 type = inType;
58 pointer = inPointer;
59 cleanup = inCleanup;
60 data = inData;
61 bigdata = inBigdata;
62 memcpy(user_data, inUserData,
63 min_c(sizeof(media_timed_event::user_data), dataSize));
64 }
65
66
media_timed_event(const media_timed_event & clone)67 media_timed_event::media_timed_event(const media_timed_event& clone)
68 {
69 CALLED();
70 *this = clone;
71 }
72
73
74 void
operator =(const media_timed_event & clone)75 media_timed_event::operator=(const media_timed_event& clone)
76 {
77 CALLED();
78 memcpy(this, &clone, sizeof(*this));
79 }
80
81
~media_timed_event()82 media_timed_event::~media_timed_event()
83 {
84 CALLED();
85 }
86
87
88 // #pragma mark - media_timed_event global operators
89
90
91 bool
operator ==(const media_timed_event & a,const media_timed_event & b)92 operator==(const media_timed_event& a, const media_timed_event& b)
93 {
94 CALLED();
95 return (memcmp(&a, &b, sizeof(media_timed_event)) == 0);
96 }
97
98
99 bool
operator !=(const media_timed_event & a,const media_timed_event & b)100 operator!=(const media_timed_event& a, const media_timed_event& b)
101 {
102 CALLED();
103 return (memcmp(&a, &b, sizeof(media_timed_event)) != 0);
104 }
105
106
107 bool
operator <(const media_timed_event & a,const media_timed_event & b)108 operator<(const media_timed_event& a, const media_timed_event& b)
109 {
110 CALLED();
111 return a.event_time < b.event_time;
112 }
113
114
115 bool
operator >(const media_timed_event & a,const media_timed_event & b)116 operator>(const media_timed_event& a, const media_timed_event& b)
117 {
118 CALLED();
119 return a.event_time > b.event_time;
120 }
121
122
123 // #pragma mark - BTimedEventQueue
124
125
126 struct queue_entry : public DoublyLinkedListLinkImpl<queue_entry> {
127 media_timed_event event;
128 };
129 typedef DoublyLinkedList<queue_entry> QueueEntryList;
130
131
132 class BPrivate::TimedEventQueueData {
133 public:
TimedEventQueueData()134 TimedEventQueueData()
135 :
136 fLock("BTimedEventQueue"),
137 fEntryAllocationLock("BTimedEventQueue entry allocation")
138 {
139 fEventCount = 0;
140 fCleanupHook = NULL;
141 fCleanupHookContext = NULL;
142
143 for (size_t i = 0; i < B_COUNT_OF(fInlineEntries); i++)
144 fFreeEntries.Add(&fInlineEntries[i]);
145 }
~TimedEventQueueData()146 ~TimedEventQueueData()
147 {
148 while (queue_entry* chunk = fChunkHeads.RemoveHead())
149 free(chunk);
150 }
151
AllocateEntry()152 queue_entry* AllocateEntry()
153 {
154 BAutolock locker(fEntryAllocationLock);
155 if (fFreeEntries.Head() != NULL)
156 return fFreeEntries.RemoveHead();
157
158 // We need a new chunk.
159 const size_t chunkSize = B_PAGE_SIZE;
160 queue_entry* newEntries = (queue_entry*)malloc(chunkSize);
161 fChunkHeads.Add(&newEntries[0]);
162 for (size_t i = 1; i < (chunkSize / sizeof(queue_entry)); i++)
163 fFreeEntries.Add(&newEntries[i]);
164
165 return fFreeEntries.RemoveHead();
166 }
167
FreeEntry(queue_entry * entry)168 void FreeEntry(queue_entry* entry)
169 {
170 BAutolock locker(fEntryAllocationLock);
171 fFreeEntries.Add(entry);
172
173 // TODO: Chunks are currently only freed in the destructor.
174 // (Is that a problem? They're probably rarely used, anyway.)
175 }
176
177 status_t AddEntry(queue_entry* newEntry);
178 void RemoveEntry(queue_entry* newEntry);
179 void CleanupAndFree(queue_entry* entry);
180
181 public:
182 BLocker fLock;
183 QueueEntryList fEvents;
184 int32 fEventCount;
185
186 BTimedEventQueue::cleanup_hook fCleanupHook;
187 void* fCleanupHookContext;
188
189 private:
190 BLocker fEntryAllocationLock;
191 QueueEntryList fFreeEntries;
192 QueueEntryList fChunkHeads;
193 queue_entry fInlineEntries[8];
194 };
195
196 using BPrivate::TimedEventQueueData;
197
198
BTimedEventQueue()199 BTimedEventQueue::BTimedEventQueue()
200 : fData(new TimedEventQueueData)
201 {
202 CALLED();
203 }
204
205
~BTimedEventQueue()206 BTimedEventQueue::~BTimedEventQueue()
207 {
208 CALLED();
209 delete fData;
210 }
211
212
213 status_t
AddEvent(const media_timed_event & event)214 BTimedEventQueue::AddEvent(const media_timed_event& event)
215 {
216 CALLED();
217
218 if (event.type < B_START)
219 return B_BAD_VALUE;
220
221 queue_entry* newEntry = fData->AllocateEntry();
222 if (newEntry == NULL)
223 return B_NO_MEMORY;
224
225 newEntry->event = event;
226
227 BAutolock locker(fData->fLock);
228 return fData->AddEntry(newEntry);
229 }
230
231
232 status_t
RemoveEvent(const media_timed_event * event)233 BTimedEventQueue::RemoveEvent(const media_timed_event* event)
234 {
235 CALLED();
236 BAutolock locker(fData->fLock);
237
238 QueueEntryList::Iterator it = fData->fEvents.GetIterator();
239 while (queue_entry* entry = it.Next()) {
240 if (entry->event != *event)
241 continue;
242
243 fData->RemoveEntry(entry);
244
245 locker.Unlock();
246
247 // No cleanup.
248 fData->FreeEntry(entry);
249 return B_OK;
250 }
251
252 return B_ERROR;
253 }
254
255
256 status_t
RemoveFirstEvent(media_timed_event * _event)257 BTimedEventQueue::RemoveFirstEvent(media_timed_event* _event)
258 {
259 CALLED();
260 BAutolock locker(fData->fLock);
261
262 if (fData->fEventCount == 0)
263 return B_ERROR;
264
265 queue_entry* entry = fData->fEvents.Head();
266 fData->RemoveEntry(entry);
267
268 locker.Unlock();
269
270 if (_event != NULL) {
271 // No cleanup.
272 *_event = entry->event;
273 fData->FreeEntry(entry);
274 } else {
275 fData->CleanupAndFree(entry);
276 }
277 return B_OK;
278 }
279
280
281 status_t
AddEntry(queue_entry * newEntry)282 TimedEventQueueData::AddEntry(queue_entry* newEntry)
283 {
284 ASSERT(fLock.IsLocked());
285
286 if (fEvents.IsEmpty()) {
287 fEvents.Add(newEntry);
288 fEventCount++;
289 return B_OK;
290 }
291 if (fEvents.First()->event.event_time > newEntry->event.event_time) {
292 fEvents.Add(newEntry, false);
293 fEventCount++;
294 return B_OK;
295 }
296
297 QueueEntryList::ReverseIterator it = fEvents.GetReverseIterator();
298 while (queue_entry* entry = it.Next()) {
299 if (newEntry->event.event_time < entry->event.event_time)
300 continue;
301
302 // Insert the new event after this entry.
303 fEvents.InsertAfter(entry, newEntry);
304 fEventCount++;
305 return B_OK;
306 }
307
308 debugger("BTimedEventQueue::AddEvent: Invalid queue!");
309 return B_ERROR;
310 }
311
312
313 void
RemoveEntry(queue_entry * entry)314 TimedEventQueueData::RemoveEntry(queue_entry* entry)
315 {
316 ASSERT(fLock.IsLocked());
317
318 fEvents.Remove(entry);
319 fEventCount--;
320 }
321
322
323 void
CleanupAndFree(queue_entry * entry)324 TimedEventQueueData::CleanupAndFree(queue_entry* entry)
325 {
326 uint32 cleanup = entry->event.cleanup;
327 if (cleanup == B_DELETE) {
328 // B_DELETE is a keyboard code, but the Be Book indicates it's a valid
329 // cleanup value. (Early sample code may have used it too.)
330 cleanup = BTimedEventQueue::B_USER_CLEANUP;
331 }
332
333 if (cleanup == BTimedEventQueue::B_NO_CLEANUP) {
334 // Nothing to do.
335 } else if (entry->event.type == BTimedEventQueue::B_HANDLE_BUFFER
336 && cleanup == BTimedEventQueue::B_RECYCLE_BUFFER) {
337 (reinterpret_cast<BBuffer*>(entry->event.pointer))->Recycle();
338 } else if (cleanup == BTimedEventQueue::B_EXPIRE_TIMER) {
339 // TimerExpired() is invoked in BMediaEventLooper::DispatchEvent; nothing to do.
340 } else if (cleanup >= BTimedEventQueue::B_USER_CLEANUP) {
341 if (fCleanupHook != NULL)
342 (*fCleanupHook)(&entry->event, fCleanupHookContext);
343 } else {
344 ERROR("BTimedEventQueue: Unhandled cleanup! (type %" B_PRId32 ", "
345 "cleanup %" B_PRId32 ")\n", entry->event.type, entry->event.cleanup);
346 }
347
348 FreeEntry(entry);
349 }
350
351
352 void
SetCleanupHook(cleanup_hook hook,void * context)353 BTimedEventQueue::SetCleanupHook(cleanup_hook hook, void* context)
354 {
355 CALLED();
356
357 BAutolock lock(fData->fLock);
358 fData->fCleanupHook = hook;
359 fData->fCleanupHookContext = context;
360 }
361
362
363 bool
HasEvents() const364 BTimedEventQueue::HasEvents() const
365 {
366 CALLED();
367
368 BAutolock locker(fData->fLock);
369 return fData->fEventCount != 0;
370 }
371
372
373 int32
EventCount() const374 BTimedEventQueue::EventCount() const
375 {
376 CALLED();
377
378 BAutolock locker(fData->fLock);
379 return fData->fEventCount;
380 }
381
382
383 const media_timed_event*
FirstEvent() const384 BTimedEventQueue::FirstEvent() const
385 {
386 CALLED();
387 BAutolock locker(fData->fLock);
388
389 queue_entry* entry = fData->fEvents.First();
390 if (entry == NULL)
391 return NULL;
392 return &entry->event;
393 }
394
395
396 bigtime_t
FirstEventTime() const397 BTimedEventQueue::FirstEventTime() const
398 {
399 CALLED();
400 BAutolock locker(fData->fLock);
401
402 queue_entry* entry = fData->fEvents.First();
403 if (entry == NULL)
404 return B_INFINITE_TIMEOUT;
405 return entry->event.event_time;
406 }
407
408
409 const media_timed_event*
LastEvent() const410 BTimedEventQueue::LastEvent() const
411 {
412 CALLED();
413 BAutolock locker(fData->fLock);
414
415 queue_entry* entry = fData->fEvents.Last();
416 if (entry == NULL)
417 return NULL;
418 return &entry->event;
419 }
420
421
422 bigtime_t
LastEventTime() const423 BTimedEventQueue::LastEventTime() const
424 {
425 CALLED();
426 BAutolock locker(fData->fLock);
427
428 queue_entry* entry = fData->fEvents.Last();
429 if (entry == NULL)
430 return B_INFINITE_TIMEOUT;
431 return entry->event.event_time;
432 }
433
434
435 const media_timed_event*
FindFirstMatch(bigtime_t eventTime,time_direction direction,bool inclusive,int32 eventType)436 BTimedEventQueue::FindFirstMatch(bigtime_t eventTime,
437 time_direction direction, bool inclusive, int32 eventType)
438 {
439 CALLED();
440 BAutolock locker(fData->fLock);
441
442 QueueEntryList::Iterator it = fData->fEvents.GetIterator();
443 while (queue_entry* entry = it.Next()) {
444 int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
445 if (match == B_DONE)
446 break;
447 if (match == B_NO_ACTION)
448 continue;
449
450 return &entry->event;
451 }
452
453 return NULL;
454 }
455
456
457 status_t
DoForEach(for_each_hook hook,void * context,bigtime_t eventTime,time_direction direction,bool inclusive,int32 eventType)458 BTimedEventQueue::DoForEach(for_each_hook hook, void* context,
459 bigtime_t eventTime, time_direction direction,
460 bool inclusive, int32 eventType)
461 {
462 CALLED();
463 BAutolock locker(fData->fLock);
464
465 bool resort = false;
466
467 QueueEntryList::Iterator it = fData->fEvents.GetIterator();
468 while (queue_entry* entry = it.Next()) {
469 int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
470 if (match == B_DONE)
471 break;
472 if (match == B_NO_ACTION)
473 continue;
474
475 queue_action action = hook(&entry->event, context);
476 if (action == B_DONE)
477 break;
478
479 switch (action) {
480 case B_REMOVE_EVENT:
481 fData->RemoveEntry(entry);
482 fData->CleanupAndFree(entry);
483 break;
484
485 case B_RESORT_QUEUE:
486 resort = true;
487 break;
488
489 case B_NO_ACTION:
490 default:
491 break;
492 }
493 }
494
495 if (resort) {
496 QueueEntryList entries;
497 entries.MoveFrom(&fData->fEvents);
498 fData->fEventCount = 0;
499
500 while (queue_entry* entry = entries.RemoveHead())
501 fData->AddEntry(entry);
502 }
503
504 return B_OK;
505 }
506
507
508 status_t
FlushEvents(bigtime_t eventTime,time_direction direction,bool inclusive,int32 eventType)509 BTimedEventQueue::FlushEvents(bigtime_t eventTime, time_direction direction,
510 bool inclusive, int32 eventType)
511 {
512 CALLED();
513 BAutolock locker(fData->fLock);
514
515 QueueEntryList::Iterator it = fData->fEvents.GetIterator();
516 while (queue_entry* entry = it.Next()) {
517 int match = _Match(entry->event, eventTime, direction, inclusive, eventType);
518 if (match == B_DONE)
519 break;
520 if (match == B_NO_ACTION)
521 continue;
522
523 fData->RemoveEntry(entry);
524 fData->CleanupAndFree(entry);
525 }
526
527 return B_OK;
528 }
529
530
531 int
_Match(const media_timed_event & event,bigtime_t eventTime,time_direction direction,bool inclusive,int32 eventType)532 BTimedEventQueue::_Match(const media_timed_event& event,
533 bigtime_t eventTime, time_direction direction,
534 bool inclusive, int32 eventType)
535 {
536 if (direction == B_ALWAYS) {
537 // Nothing to check.
538 } else if (direction == B_BEFORE_TIME) {
539 if (event.event_time > eventTime)
540 return B_DONE;
541 if (event.event_time == eventTime && !inclusive)
542 return B_DONE;
543 } else if (direction == B_AT_TIME) {
544 if (event.event_time > eventTime)
545 return B_DONE;
546 if (event.event_time != eventTime)
547 return B_NO_ACTION;
548 } else if (direction == B_AFTER_TIME) {
549 if (event.event_time < eventTime)
550 return B_NO_ACTION;
551 if (event.event_time == eventTime && !inclusive)
552 return B_NO_ACTION;
553 }
554
555 if (eventType != B_ANY_EVENT && eventType != event.type)
556 return B_NO_ACTION;
557
558 return 1;
559 }
560
561
562 // #pragma mark - C++ binary compatibility
563
564
565 void*
operator new(size_t size)566 BTimedEventQueue::operator new(size_t size)
567 {
568 CALLED();
569 return ::operator new(size);
570 }
571
572
573 void
operator delete(void * ptr,size_t s)574 BTimedEventQueue::operator delete(void* ptr, size_t s)
575 {
576 CALLED();
577 return ::operator delete(ptr);
578 }
579
580
_ReservedTimedEventQueue0()581 void BTimedEventQueue::_ReservedTimedEventQueue0() {}
_ReservedTimedEventQueue1()582 void BTimedEventQueue::_ReservedTimedEventQueue1() {}
_ReservedTimedEventQueue2()583 void BTimedEventQueue::_ReservedTimedEventQueue2() {}
_ReservedTimedEventQueue3()584 void BTimedEventQueue::_ReservedTimedEventQueue3() {}
_ReservedTimedEventQueue4()585 void BTimedEventQueue::_ReservedTimedEventQueue4() {}
_ReservedTimedEventQueue5()586 void BTimedEventQueue::_ReservedTimedEventQueue5() {}
_ReservedTimedEventQueue6()587 void BTimedEventQueue::_ReservedTimedEventQueue6() {}
_ReservedTimedEventQueue7()588 void BTimedEventQueue::_ReservedTimedEventQueue7() {}
_ReservedTimedEventQueue8()589 void BTimedEventQueue::_ReservedTimedEventQueue8() {}
_ReservedTimedEventQueue9()590 void BTimedEventQueue::_ReservedTimedEventQueue9() {}
_ReservedTimedEventQueue10()591 void BTimedEventQueue::_ReservedTimedEventQueue10() {}
_ReservedTimedEventQueue11()592 void BTimedEventQueue::_ReservedTimedEventQueue11() {}
_ReservedTimedEventQueue12()593 void BTimedEventQueue::_ReservedTimedEventQueue12() {}
_ReservedTimedEventQueue13()594 void BTimedEventQueue::_ReservedTimedEventQueue13() {}
_ReservedTimedEventQueue14()595 void BTimedEventQueue::_ReservedTimedEventQueue14() {}
_ReservedTimedEventQueue15()596 void BTimedEventQueue::_ReservedTimedEventQueue15() {}
_ReservedTimedEventQueue16()597 void BTimedEventQueue::_ReservedTimedEventQueue16() {}
_ReservedTimedEventQueue17()598 void BTimedEventQueue::_ReservedTimedEventQueue17() {}
_ReservedTimedEventQueue18()599 void BTimedEventQueue::_ReservedTimedEventQueue18() {}
_ReservedTimedEventQueue19()600 void BTimedEventQueue::_ReservedTimedEventQueue19() {}
_ReservedTimedEventQueue20()601 void BTimedEventQueue::_ReservedTimedEventQueue20() {}
_ReservedTimedEventQueue21()602 void BTimedEventQueue::_ReservedTimedEventQueue21() {}
_ReservedTimedEventQueue22()603 void BTimedEventQueue::_ReservedTimedEventQueue22() {}
_ReservedTimedEventQueue23()604 void BTimedEventQueue::_ReservedTimedEventQueue23() {}
605