1/*
2 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files or portions
6 * thereof (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so, subject
10 * to the following conditions:
11 *
12 *  * Redistributions of source code must retain the above copyright notice,
13 *    this list of conditions and the following disclaimer.
14 *
15 *  * Redistributions in binary form must reproduce the above copyright notice
16 *    in the  binary, as well as this list of conditions and the following
17 *    disclaimer in the documentation and/or other materials provided with
18 *    the distribution.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 *
28 */
29
30/* Implements _event_queue_imp used by BTimedEventQueue, not thread save!
31 */
32#include <string.h>
33
34#include <Autolock.h>
35#include <Buffer.h>
36#include <InterfaceDefs.h> //defines B_DELETE
37#include <TimedEventQueue.h>
38#include <TimeSource.h>
39
40#include "TimedEventQueuePrivate.h"
41
42#include "Debug.h"
43#include "debug.h"
44
45_event_queue_imp::_event_queue_imp() :
46	fLock(new BLocker("BTimedEventQueue locker")),
47	fEventCount(0),
48	fFirstEntry(NULL),
49	fLastEntry(NULL),
50	fCleanupHookContext(NULL),
51	fCleanupHook(0)
52{
53}
54
55
56_event_queue_imp::~_event_queue_imp()
57{
58	event_queue_entry *entry;
59	entry = fFirstEntry;
60	while (entry) {
61		event_queue_entry *deleteme;
62		deleteme = entry;
63		entry = entry->next;
64		delete deleteme;
65	}
66	delete fLock;
67}
68
69
70status_t
71_event_queue_imp::AddEvent(const media_timed_event &event)
72{
73	BAutolock lock(fLock);
74
75//	printf("        adding      %12Ld  at %12Ld\n", event.event_time, system_time());
76
77	if (event.type <= 0) {
78		 return B_BAD_VALUE;
79	}
80
81	*(bigtime_t *)&event.queued_time = BTimeSource::RealTime();
82
83	//create a new queue
84	if (fFirstEntry == NULL) {
85		ASSERT(fEventCount == 0);
86		ASSERT(fLastEntry == NULL);
87		fFirstEntry = fLastEntry = new event_queue_entry;
88		fFirstEntry->event = event;
89		fFirstEntry->prev = NULL;
90		fFirstEntry->next = NULL;
91		fEventCount++;
92		return B_OK;
93	}
94
95	//insert at queue begin
96	if (fFirstEntry->event.event_time >= event.event_time) {
97		event_queue_entry *newentry = new event_queue_entry;
98		newentry->event = event;
99		newentry->prev = NULL;
100		newentry->next = fFirstEntry;
101		fFirstEntry->prev = newentry;
102		fFirstEntry = newentry;
103		fEventCount++;
104		return B_OK;
105	}
106
107	//insert at queue end
108	if (fLastEntry->event.event_time <= event.event_time) {
109		event_queue_entry *newentry = new event_queue_entry;
110		newentry->event = event;
111		newentry->prev = fLastEntry;
112		newentry->next = NULL;
113		fLastEntry->next = newentry;
114		fLastEntry = newentry;
115		fEventCount++;
116		return B_OK;
117	}
118
119	//insert into the queue
120	for (event_queue_entry *entry = fLastEntry; entry; entry = entry->prev) {
121		if (entry->event.event_time <= event.event_time) {
122			//insert after entry
123			event_queue_entry *newentry = new event_queue_entry;
124			newentry->event = event;
125			newentry->prev = entry;
126			newentry->next = entry->next;
127			(entry->next)->prev = newentry;
128			entry->next = newentry;
129			fEventCount++;
130			return B_OK;
131		}
132	}
133
134	debugger("_event_queue_imp::AddEvent failed, should not be here\n");
135	return B_OK;
136}
137
138
139status_t
140_event_queue_imp::RemoveEvent(const media_timed_event *event)
141{
142	BAutolock lock(fLock);
143
144	for (event_queue_entry *entry = fFirstEntry; entry; entry = entry->next) {
145		if (entry->event == *event) {
146			// No cleanup here
147			RemoveEntry(entry);
148			return B_OK;
149		}
150	}
151
152	return B_ERROR;
153}
154
155
156status_t
157_event_queue_imp::RemoveFirstEvent(media_timed_event * outEvent)
158{
159	BAutolock lock(fLock);
160
161	if (fFirstEntry == 0)
162		return B_ERROR;
163
164	if (outEvent != 0) {
165		// No cleanup here
166		*outEvent = fFirstEntry->event;
167	} else {
168		CleanupEvent(&fFirstEntry->event);
169	}
170
171	RemoveEntry(fFirstEntry);
172
173	return B_OK;
174}
175
176
177bool
178_event_queue_imp::HasEvents() const
179{
180	return fEventCount != 0;
181}
182
183
184int32
185_event_queue_imp::EventCount() const
186{
187	#if DEBUG > 1
188		Dump();
189	#endif
190	return fEventCount;
191}
192
193
194const media_timed_event *
195_event_queue_imp::FirstEvent() const
196{
197	return fFirstEntry ? &fFirstEntry->event : NULL;
198}
199
200
201bigtime_t
202_event_queue_imp::FirstEventTime() const
203{
204	return fFirstEntry ? fFirstEntry->event.event_time : B_INFINITE_TIMEOUT;
205}
206
207
208const media_timed_event *
209_event_queue_imp::LastEvent() const
210{
211	return fLastEntry ? &fLastEntry->event : NULL;
212}
213
214
215bigtime_t
216_event_queue_imp::LastEventTime() const
217{
218	return fLastEntry ? fLastEntry->event.event_time : B_INFINITE_TIMEOUT;
219}
220
221
222const media_timed_event *
223_event_queue_imp::FindFirstMatch(bigtime_t eventTime,
224								 BTimedEventQueue::time_direction direction,
225								 bool inclusive,
226								 int32 eventType)
227{
228	event_queue_entry *end;
229	event_queue_entry *entry;
230
231	BAutolock lock(fLock);
232
233	switch (direction) {
234		case BTimedEventQueue::B_ALWAYS:
235			for (entry = fFirstEntry; entry; entry = entry->next) {
236				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type)
237					return &entry->event;
238			}
239			return NULL;
240
241		case BTimedEventQueue::B_BEFORE_TIME:
242			end = GetEnd_BeforeTime(eventTime, inclusive);
243			if (end == NULL)
244				return NULL;
245			end = end->next;
246			for (entry = fFirstEntry; entry != end; entry = entry->next) {
247				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
248					return &entry->event;
249				}
250			}
251			return NULL;
252
253		case BTimedEventQueue::B_AT_TIME:
254			{
255				bool found_time = false;
256				for (entry = fFirstEntry; entry; entry = entry->next) {
257					if (eventTime == entry->event.event_time) {
258						found_time = true;
259						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type)
260							return &entry->event;
261					} else if (found_time)
262						return NULL;
263				}
264				return NULL;
265			}
266
267		case BTimedEventQueue::B_AFTER_TIME:
268			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; entry = entry->next) {
269				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
270					return &entry->event;
271				}
272			}
273			return NULL;
274	}
275
276	return NULL;
277}
278
279
280status_t
281_event_queue_imp::DoForEach(BTimedEventQueue::for_each_hook hook,
282							void *context,
283							bigtime_t eventTime,
284							BTimedEventQueue::time_direction direction,
285							bool inclusive,
286							int32 eventType)
287{
288	event_queue_entry *end;
289	event_queue_entry *entry;
290	event_queue_entry *remove;
291	BTimedEventQueue::queue_action action;
292
293	BAutolock lock(fLock);
294
295	switch (direction) {
296		case BTimedEventQueue::B_ALWAYS:
297			for (entry = fFirstEntry; entry; ) {
298				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
299					action = (*hook)(&entry->event, context);
300					switch (action) {
301						case BTimedEventQueue::B_DONE:
302							return B_OK;
303						case BTimedEventQueue::B_REMOVE_EVENT:
304							CleanupEvent(&entry->event);
305							remove = entry;
306							entry = entry->next;
307							RemoveEntry(remove);
308							break;
309						case BTimedEventQueue::B_NO_ACTION:
310						case BTimedEventQueue::B_RESORT_QUEUE:
311						default:
312							entry = entry->next;
313							break;
314					}
315				} else {
316					entry = entry->next;
317				}
318			}
319			return B_OK;
320
321		case BTimedEventQueue::B_BEFORE_TIME:
322			end = GetEnd_BeforeTime(eventTime, inclusive);
323			if (end == NULL)
324				return B_OK;
325			end = end->next;
326			for (entry = fFirstEntry; entry != end; ) {
327				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
328					action = (*hook)(&entry->event, context);
329					switch (action) {
330						case BTimedEventQueue::B_DONE:
331							return B_OK;
332						case BTimedEventQueue::B_REMOVE_EVENT:
333							CleanupEvent(&entry->event);
334							remove = entry;
335							entry = entry->next;
336							RemoveEntry(remove);
337							break;
338						case BTimedEventQueue::B_NO_ACTION:
339						case BTimedEventQueue::B_RESORT_QUEUE:
340						default:
341							entry = entry->next;
342							break;
343					}
344				} else {
345					entry = entry->next;
346				}
347			}
348			return B_OK;
349
350		case BTimedEventQueue::B_AT_TIME:
351			{
352				bool found_time = false;
353				for (entry = fFirstEntry; entry; ) {
354					if (eventTime == entry->event.event_time) {
355						found_time = true;
356						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
357							action = (*hook)(&entry->event, context);
358							switch (action) {
359								case BTimedEventQueue::B_DONE:
360									return B_OK;
361								case BTimedEventQueue::B_REMOVE_EVENT:
362									CleanupEvent(&entry->event);
363									remove = entry;
364									entry = entry->next;
365									RemoveEntry(remove);
366									break;
367								case BTimedEventQueue::B_NO_ACTION:
368								case BTimedEventQueue::B_RESORT_QUEUE:
369								default:
370									entry = entry->next;
371									break;
372							}
373						} else {
374							entry = entry->next;
375						}
376					} else if (found_time) {
377						break;
378					} else {
379						entry = entry->next;
380					}
381				}
382				return B_OK;
383			}
384
385		case BTimedEventQueue::B_AFTER_TIME:
386			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; ) {
387				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
388					action = (*hook)(&entry->event, context);
389					switch (action) {
390						case BTimedEventQueue::B_DONE:
391							return B_OK;
392						case BTimedEventQueue::B_REMOVE_EVENT:
393							CleanupEvent(&entry->event);
394							remove = entry;
395							entry = entry->next;
396							RemoveEntry(remove);
397							break;
398						case BTimedEventQueue::B_NO_ACTION:
399						case BTimedEventQueue::B_RESORT_QUEUE:
400						default:
401							entry = entry->next;
402							break;
403					}
404				} else {
405					entry = entry->next;
406				}
407			}
408			return B_OK;
409	}
410
411	return B_ERROR;
412}
413
414
415status_t
416_event_queue_imp::FlushEvents(bigtime_t eventTime,
417							  BTimedEventQueue::time_direction direction,
418							  bool inclusive,
419							  int32 eventType)
420{
421	event_queue_entry *end;
422	event_queue_entry *entry;
423	event_queue_entry *remove;
424
425	BAutolock lock(fLock);
426
427	switch (direction) {
428		case BTimedEventQueue::B_ALWAYS:
429			for (entry = fFirstEntry; entry; ) {
430				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
431					CleanupEvent(&entry->event);
432					remove = entry;
433					entry = entry->next;
434					RemoveEntry(remove);
435				} else {
436					entry = entry->next;
437				}
438			}
439			return B_OK;
440
441		case BTimedEventQueue::B_BEFORE_TIME:
442			end = GetEnd_BeforeTime(eventTime, inclusive);
443			if (end == NULL)
444				return B_OK;
445			end = end->next;
446			for (entry = fFirstEntry; entry != end; ) {
447				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
448					CleanupEvent(&entry->event);
449					remove = entry;
450					entry = entry->next;
451					RemoveEntry(remove);
452				} else {
453					entry = entry->next;
454				}
455			}
456			return B_OK;
457
458		case BTimedEventQueue::B_AT_TIME:
459			{
460				bool found_time = false;
461				for (entry = fFirstEntry; entry; ) {
462					if (eventTime == entry->event.event_time) {
463						found_time = true;
464						if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
465							CleanupEvent(&entry->event);
466							remove = entry;
467							entry = entry->next;
468							RemoveEntry(remove);
469						} else {
470							entry = entry->next;
471						}
472					} else if (found_time) {
473						break;
474					} else {
475						entry = entry->next;
476					}
477				}
478				return B_OK;
479			}
480
481		case BTimedEventQueue::B_AFTER_TIME:
482			for (entry = GetStart_AfterTime(eventTime, inclusive); entry; ) {
483				if (eventType == BTimedEventQueue::B_ANY_EVENT || eventType == entry->event.type) {
484					CleanupEvent(&entry->event);
485					remove = entry;
486					entry = entry->next;
487					RemoveEntry(remove);
488				} else {
489					entry = entry->next;
490				}
491			}
492			return B_OK;
493	}
494
495	return B_ERROR;
496}
497
498
499void
500_event_queue_imp::SetCleanupHook(BTimedEventQueue::cleanup_hook hook, void *context)
501{
502	BAutolock lock(fLock);
503	fCleanupHookContext = context;
504	fCleanupHook = hook;
505}
506
507
508void
509_event_queue_imp::RemoveEntry(event_queue_entry *entry)
510{
511	//remove the entry from double-linked list
512	//and delete it
513	if (entry->prev != NULL)
514		(entry->prev)->next = entry->next;
515	if (entry->next != NULL)
516		(entry->next)->prev = entry->prev;
517	if (entry == fFirstEntry) {
518		fFirstEntry = entry->next;
519		if (fFirstEntry != NULL)
520			fFirstEntry->prev = NULL;
521	}
522	if (entry == fLastEntry) {
523		fLastEntry = entry->prev;
524		if (fLastEntry != NULL)
525			fLastEntry->next = NULL;
526	}
527	delete entry;
528	fEventCount--;
529	ASSERT(fEventCount >= 0);
530	ASSERT(fEventCount != 0 || ((fFirstEntry == NULL) && (fLastEntry == NULL)));
531}
532
533
534void
535_event_queue_imp::CleanupEvent(media_timed_event *event)
536{
537	//perform the cleanup action required
538	//when deleting an event from the queue
539
540	//BeBook says:
541	//	Each event has a cleanup flag associated with it that indicates
542	//  what sort of special action needs to be performed when the event is
543	//  removed from the queue. If this value is B_NO_CLEANUP, nothing is done.
544	//  If it's B_RECYCLE, and the event is a B_HANDLE_BUFFER event, BTimedEventQueue
545	//  will automatically recycle the buffer associated with the event.
546	//  If the cleanup flag is B_DELETE or is B_USER_CLEANUP or greater,
547	//  the cleanup hook function will be called.
548	//and:
549	//  cleanup hook function specified by hook to be called for events
550	//  as they're removed from the queue. The hook will be called only
551	//  for events with cleanup_flag values of B_DELETE or B_USER_CLEANUP or greater.
552	//and:
553	//  These values define how BTimedEventQueue should handle removing
554	//  events from the queue. If the flag is B_USER_CLEANUP or greater,
555	//  the cleanup hook function is called when the event is removed.
556	//Problems:
557	//  B_DELETE is a keyboard code! (seems to have existed in early
558	//  sample code as a cleanup flag)
559	//
560	//  exiting cleanup flags are:
561	//		B_NO_CLEANUP = 0,
562	//		B_RECYCLE_BUFFER,		// recycle buffers handled by BTimedEventQueue
563	//		B_EXPIRE_TIMER,			// call TimerExpired() on the event->data
564	//		B_USER_CLEANUP = 0x4000	// others go to the cleanup func
565	//
566
567	if (event->cleanup == BTimedEventQueue::B_NO_CLEANUP) {
568		// do nothing
569	} else if (event->type == BTimedEventQueue::B_HANDLE_BUFFER && event->cleanup == BTimedEventQueue::B_RECYCLE_BUFFER) {
570		((BBuffer *)event->pointer)->Recycle();
571		DEBUG_ONLY(*const_cast<void **>(&event->pointer) = NULL);
572	} else if (event->cleanup == BTimedEventQueue::B_EXPIRE_TIMER) {
573		// call TimerExpired() on the event->data
574		debugger("BTimedEventQueue cleanup: calling TimerExpired() should be implemented here\n");
575	} else if (event->cleanup == B_DELETE || event->cleanup >= BTimedEventQueue::B_USER_CLEANUP) {
576		if (fCleanupHook)
577			(*fCleanupHook)(event,fCleanupHookContext);
578	} else {
579		ERROR("BTimedEventQueue cleanup unhandled! type = %" B_PRId32
580			", cleanup = %" B_PRId32 "\n", event->type, event->cleanup);
581	}
582}
583
584
585_event_queue_imp::event_queue_entry *
586_event_queue_imp::GetEnd_BeforeTime(bigtime_t eventTime, bool inclusive)
587{
588	event_queue_entry *entry;
589
590	entry = fLastEntry;
591	while (entry) {
592		if ((entry->event.event_time > eventTime) || (!inclusive && entry->event.event_time == eventTime))
593			entry = entry->prev;
594		else
595			break;
596	}
597	return entry;
598}
599
600
601_event_queue_imp::event_queue_entry *
602_event_queue_imp::GetStart_AfterTime(bigtime_t eventTime, bool inclusive)
603{
604	event_queue_entry *entry;
605
606	entry = fFirstEntry;
607	while (entry) {
608		if ((entry->event.event_time < eventTime) || (!inclusive && entry->event.event_time == eventTime))
609			entry = entry->next;
610		else
611			break;
612	}
613	return entry;
614}
615
616
617#if DEBUG > 1
618void
619_event_queue_imp::Dump() const
620{
621	TRACE("fEventCount = 0x%x\n",(int)fEventCount);
622	TRACE("fFirstEntry = 0x%x\n",(int)fFirstEntry);
623	TRACE("fLastEntry  = 0x%x\n",(int)fLastEntry);
624	for (event_queue_entry *entry = fFirstEntry; entry; entry = entry->next) {
625		TRACE("entry = 0x%x\n",(int)entry);
626		TRACE("  entry.prev = 0x%x\n",(int)entry->prev);
627		TRACE("  entry.next = 0x%x\n",(int)entry->next);
628		TRACE("  entry.event.event_time = 0x%x\n",(int)entry->event.event_time);
629	}
630}
631#endif
632