xref: /haiku/src/kits/media/TimedEventQueue.cpp (revision 4055af5143236b53cf20809bbfe411e977ecf13c)
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