//===========================================================================// // File: event.cc // // Project: MUNGA Brick: Event Manager // // Contents: implementation details of event processing // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 10/19/94 JMA Initial coding. // // 11/03/94 ECH Made compatible with BC4.0 // // 11/05/94 JMA Made compatible with GNU C++ // // 11/30/94 JMA Adapted to new style conventions // // 12/01/94 JMA Made compatible with SGI CC // //---------------------------------------------------------------------------// // Copyright (C) 1994, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// #include "AdeptHeaders.hpp" #include "EventStatistics.hpp" #include "Network.hpp" //############################################################################# //########################### AbstractEvent ############################# //############################################################################# //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent::AbstractEvent( ClassData *class_data, const Receiver::Message *message, Time time ): Plug(class_data) { Verify(message); alarmTime = time; // //-------------------------------------------------------- // Allocate some memory to store the message parameters in //-------------------------------------------------------- // size_t long_size = (message->messageLength+3)>>2; messageToSend = Cast_Pointer(Receiver::Message*, new long[long_size]); Register_Object(messageToSend); Mem_Copy( messageToSend, message, message->messageLength, long_size*sizeof(long) ); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent::~AbstractEvent() { if (messageToSend) { Unregister_Pointer(messageToSend); delete messageToSend; } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void AbstractEvent::Process() { STOP(("AbstractEvent::Process should not be called!\n")); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void AbstractEvent::DumpData() { STOP(("AbstractEvent::DumpData should not be called!\n")); } //############################################################################# //############################### Event ################################# //############################################################################# MemoryBlock* Event::AllocatedMemory = NULL; Event::ClassData* Event::DefaultData = NULL; static DWORD Event_Count; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::InitializeClass( size_t block_count, size_t block_delta ) { Verify(!AllocatedMemory); AllocatedMemory = new MemoryBlock( sizeof(Event), block_count, block_delta, "Event", g_LibraryHeap ); Register_Object(AllocatedMemory); Verify(!DefaultData); DefaultData = new ClassData( EventClassID, "Adept::Event", AbstractEvent::DefaultData ); Register_Object(DefaultData); Event_Count = 0; #if !defined(NO_STATS) AddStatistic("Queued Events", "events", gos_DWORD, &Event_Count, 0); #endif } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::TerminateClass() { Unregister_Object(DefaultData); delete DefaultData; DefaultData = NULL; Unregister_Object(AllocatedMemory); delete AllocatedMemory; AllocatedMemory = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Event::Event( Receiver *target, const Receiver::Message *message, Time time ): AbstractEvent(DefaultData, message, time) { targetReceiver.Add(target); Set_Statistic(Event_Count, Event_Count+1); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Event::~Event() { Set_Statistic(Event_Count, Event_Count-1); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::Repost( EventQueue* queue, Time new_time ) { Check_Object(this); Check_Object(queue); // //------------------------------------------- // Delete the link to the current event queue //------------------------------------------- // STOP(("Not functional")); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::Process() { Check_Object(this); Receiver *target = targetReceiver.GetCurrent(); if (target) { Check_Object(target); target->Receive(this); } else { delete this; } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::DumpData() { Check_Object(this); #if defined(_ARMOR) Receiver *target = targetReceiver.GetCurrent(); if (target) { Check_Object(target); Receiver::ClassData *class_data = target->GetClassData(); Check_Object(class_data); const Receiver::MessageEntry *entry = target->GetMessageEntry(messageToSend->messageID); Check_Object(entry); Check_Pointer(entry->messageName); SPEW(( GROUP_ADEPT_EVENT, "%s::%s", class_data->GetClassName(), entry->messageName )); } #endif } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Event::TestInstance() { Verify(IsDerivedFrom(DefaultData)); } //############################################################################# //########################### NetworkEvent ############################## //############################################################################# NetworkEvent::ClassData* NetworkEvent::DefaultData = NULL; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void NetworkEvent::InitializeClass() { Verify(!DefaultData); DefaultData = new ClassData( NetworkEventClassID, "Adept::NetworkEvent", AbstractEvent::DefaultData ); Register_Object(DefaultData); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void NetworkEvent::TerminateClass() { Unregister_Object(DefaultData); delete DefaultData; DefaultData = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // NetworkEvent::NetworkEvent( Connection *connection, InBox *box, EventStyle event_style, const Receiver::Message *message, Time time ): AbstractEvent(DefaultData, message, time) { eventConnection = connection; eventBox = box; eventStyle = event_style; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // NetworkEvent::~NetworkEvent() { } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void NetworkEvent::Process() { Check_Object(this); switch (eventStyle) { case SendStyle: Check_Object(Network::GetInstance()); STOP(("Not implemented")); // Network::GetInstance()->Send(messageToSend, hostID); break; case BroadcastStyle: Check_Object(Network::GetInstance()); Network::GetInstance()->Broadcast(messageToSend); break; case ExclusiveBroadcastStyle: Check_Object(Network::GetInstance()); Network::GetInstance()->ExclusiveBroadcast(messageToSend); break; } Unregister_Object(this); delete this; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void NetworkEvent::TestInstance() { Verify(IsDerivedFrom(DefaultData)); } //############################################################################# //####################### GeneralEventQueue ############################ //############################################################################# // // Shared Data Support // GeneralEventQueue::ClassData* GeneralEventQueue::DefaultData = NULL; GeneralEventQueue* GeneralEventQueue::Instance = NULL; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::InitializeClass() { Verify(!DefaultData); DefaultData = new ClassData( GeneralEventQueueClassID, "Adept::GeneralEventQueue", Plug::DefaultData ); Register_Object(DefaultData); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::TerminateClass() { Unregister_Object(DefaultData); delete DefaultData; DefaultData = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // GeneralEventQueue::GeneralEventQueue(ClassData *class_data): Plug(class_data) { priorityLevels = -1; eventCount = 0; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // GeneralEventQueue::~GeneralEventQueue() { pendingEvents.DeletePlugs(); timedEvents.DeletePlugs(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // GeneralEventQueue* GeneralEventQueue::Make( int priorities, const char* count_trace_name, const char* delay_trace_name ) { gos_PushCurrentHeap(g_Heap); GeneralEventQueue *queue = new GeneralEventQueue[priorities]; Register_Object(queue); queue->priorityLevels = priorities; gos_PopCurrentHeap(); return queue; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent* GeneralEventQueue::Post( Receiver *target, const Receiver::Message *message, Time time ) { Check_Object(this); Check_Object(target); Check_Object(message); // //------------------------------------------------------ // Create a new event, then insert it in the event queue //------------------------------------------------------ // Event *event = new Event(target, message, time); Register_Object(event); Enqueue(event); return event; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent* GeneralEventQueue::SendEvent( Connection *connection, InBox *box, const Receiver::Message *message, Time when ) { Check_Object(this); Check_Object(message); NetworkEvent *event = new NetworkEvent( connection, box, NetworkEvent::SendStyle, message, when ); Register_Object(event); Enqueue(event); return event; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent* GeneralEventQueue::BroadcastEvent( InBox *box, const Receiver::Message *message, Time when ) { Check_Object(this); Check_Object(message); // // HACK - What is a null network address? // NetworkEvent *event = new NetworkEvent( NULL, box, NetworkEvent::BroadcastStyle, message, when ); Register_Object(event); Enqueue(event); return event; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent* GeneralEventQueue::ExclusiveBroadcastEvent( InBox *box, const Receiver::Message *message, Time when ) { Check_Object(this); Check_Object(message); // // HACK - What is a null network address? // NetworkEvent *event = new NetworkEvent( NULL, box, NetworkEvent::ExclusiveBroadcastStyle, message, when ); Register_Object(event); Enqueue(event); return event; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::Enqueue(AbstractEvent *event) { Check_Object(event); Check_Pointer(event->messageToSend); int priority = event->messageToSend->messagePriority; Verify(static_cast(priority) < priorityLevels); // //------------------------------------------------------------ // If this is not a timed event, stuff it in the pending queue //------------------------------------------------------------ // if (event->alarmTime == -1.0) { this[priority].pendingEvents.Add(event); } // //---------------------------------------- // Otherwise, sort it into the timed queue //---------------------------------------- // else { ChainIteratorOf timed_events(&this[priority].timedEvents); timed_events.Last(); AbstractEvent* next_event = timed_events.GetCurrent(); // //--------------------------------------------------------------------- // If the timed queue is empty, or if we are later than the last entry, // just add the event to the end //--------------------------------------------------------------------- // if (!next_event || next_event->alarmTime <= event->alarmTime) { this[priority].timedEvents.Add(event); } // //-------------------------------------------------------------------- // Find the event to insert before. If we run off the beginning, stop // checking and set the iterator to the first event in the queue //-------------------------------------------------------------------- // else { do { timed_events.Previous(); next_event = timed_events.GetCurrent(); if (!next_event) { timed_events.First(); break; } } while (next_event->alarmTime > event->alarmTime); // //-------------------------------------------------------------------- // If we didn't run off the queue, we need to insert after the current // event, so bump the iterator forward one event //-------------------------------------------------------------------- // if (next_event) { timed_events.Next(); } // //------------------------------------------------------------------ // Insert the new event ahead of where the iterator currently points //------------------------------------------------------------------ // timed_events.Insert(event); } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // AbstractEvent* GeneralEventQueue::PeekAtNextEvent(int min_priority) { Check_Object(this); // //-------------------------------------------------------------------------- // Start at the top of the priority queue, searching backwards until we find // an available event or run off the minimum queue priority //-------------------------------------------------------------------------- // Verify(min_priority >= 0); int priority = priorityLevels; while (--priority >= min_priority) { // //------------------------------------------------------------------- // See if the first event in the timed queue is ready to go. If not, // none of the other timed events will be ready to go either //------------------------------------------------------------------- // ChainIteratorOf timed_events(&this[priority].timedEvents); AbstractEvent *event = timed_events.GetCurrent(); if (event) { Check_Object(event); if (event->alarmTime <= gos_GetElapsedTime()) { return event; } } // //------------------------------ // Now look in the untimed queue //------------------------------ // ChainIteratorOf events(&this[priority].pendingEvents); event = events.GetCurrent(); if (event) { Check_Object(event); return event; } } return NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // bool GeneralEventQueue::IsPriorityEmpty(int priority) { Check_Object(this); Verify(priority >= 0 && priority < priorityLevels); ChainIteratorOf i(&this[priority].pendingEvents); ChainIteratorOf j(&this[priority].timedEvents); return !i.GetCurrent() && !j.GetCurrent(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // bool GeneralEventQueue::ProcessOneEvent(int min_priority) { Check_Object(this); Verify(min_priority >= 0); AbstractEvent *event=PeekAtNextEvent(min_priority); if (event) { event->Process(); return true; } return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::ProcessAllEvents(int min_priority) { Check_Object(this); Verify(min_priority >= 0); while (ProcessOneEvent(min_priority)); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::FlushAllEvents(int max_priority) { Check_Object(this); Verify(max_priority < priorityLevels); // //----------------------------------------- // Make sure that max_priority is set right //----------------------------------------- // if (max_priority < 0) { max_priority = priorityLevels - 1; } // //--------------------------------------------------------------------- // Step through each of the priority queues and delete any events found // within //--------------------------------------------------------------------- // for (int priority=max_priority; priority>=0; --priority) { // //--------------------------------------- // Delete the events from the timed queue //--------------------------------------- // ChainIteratorOf i(&this[priority].timedEvents); AbstractEvent *event; while ((event = i.ReadAndNext()) != NULL) { Unregister_Object(event); delete event; } // //----------------------------------------- // Delete the events from the regular queue //----------------------------------------- // ChainIteratorOf j(&this[priority].pendingEvents); while ((event = j.ReadAndNext()) != NULL) { Unregister_Object(event); delete event; } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::FlushMatchingEvents( Receiver::MessageID target_message, int max_priority ) { Check_Object(this); Verify(max_priority < priorityLevels); // //----------------------------------------- // Make sure that max_priority is set right //----------------------------------------- // if (max_priority < 0) { max_priority = priorityLevels - 1; } // //--------------------------------------------------------------------- // Step through each of the priority queues and delete any events found // within //--------------------------------------------------------------------- // for (int p=max_priority; p>=0; --p) { // //------------------------------------------- // Delete events from the regular queue first //------------------------------------------- // ChainIteratorOf i(&this[p].pendingEvents); AbstractEvent *event; while ((event = i.ReadAndNext()) != NULL) { // //------------------------------ // Make sure the message matches //------------------------------ // if ( target_message == Receiver::AnyMessageID || target_message == event->messageToSend->messageID ) { Unregister_Object(event); delete event; } } // //----------------------------------- // Delete events from the timed queue //----------------------------------- // ChainIteratorOf j(&this[p].timedEvents); while ((event = j.ReadAndNext()) != NULL) { // //------------------------------ // Make sure the message matches //------------------------------ // if ( target_message == Receiver::AnyMessageID || target_message == event->messageToSend->messageID ) { Unregister_Object(event); delete event; } } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::DumpEventQueue() { Check_Object(this); // //---------------------------------------------------------------------- // Step through each of the priority queues and display any events found // within //---------------------------------------------------------------------- // unsigned total_count = 0; for (int priority=priorityLevels - 1; priority>=0; --priority) { SPEW((GROUP_ADEPT_EVENT, "EventQueue priority %d", priority)); SPEW(( GROUP_ADEPT_EVENT, "--------------------------------------------------------------" )); ChainIteratorOf i(&this[priority].pendingEvents); AbstractEvent *event; size_t count=0; while ((event = i.ReadAndNext()) != NULL) { ++count; event->DumpData(); } SPEW(( GROUP_ADEPT_EVENT, "%d priority %d events", count, priority )); count = 0; ChainIteratorOf j(&this[priority].timedEvents); while ((event = j.ReadAndNext()) != NULL) { ++count; event->DumpData(); } SPEW(( GROUP_ADEPT_EVENT, "%d priority %d timed events", count, priority )); total_count += count; } SPEW(( GROUP_ADEPT_EVENT, "%d total events in queues", total_count)); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void GeneralEventQueue::TestInstance() { Verify(IsDerivedFrom(DefaultData)); } //############################################################################# //############################ EventQueue ############################### //############################################################################# // // Shared Data Support // EventQueue::ClassData* EventQueue::DefaultData = NULL; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void EventQueue::InitializeClass() { Verify(!DefaultData); DefaultData = new ClassData( EventQueueClassID, "Adept::EventQueue", GeneralEventQueue::DefaultData ); Register_Object(DefaultData); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void EventQueue::TerminateClass() { Unregister_Object(DefaultData); delete DefaultData; DefaultData = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // EventQueue::EventQueue(): GeneralEventQueue(DefaultData) { } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // EventQueue::~EventQueue() { } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // EventQueue* EventQueue::Make( int priorities, const char* trace_name, const char* delay_trace_name ) { gos_PushCurrentHeap(g_Heap); EventQueue *queue = new EventQueue[priorities]; Register_Object(queue); queue->priorityLevels = priorities; gos_PopCurrentHeap(); return queue; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void EventQueue::ProcessMatchingEvents( Receiver *receiver, Receiver::MessageID message_ID ) { Check_Object(this); Check_Object(receiver); // //---------------------------------------------------------------- // Step through each of the priority queues and process any events // found within //---------------------------------------------------------------- // for (int p=priorityLevels-1; p>=0; --p) { Event *event; AbstractEvent *abstract_event; ChainIteratorOf i(&this[p].pendingEvents); while ((abstract_event = i.ReadAndNext()) != NULL) { Check_Object(abstract_event); event = Cast_Object(Event*, abstract_event); // //------------------------------ // Make sure the message matches //------------------------------ // if ( (event->targetReceiver.GetCurrent() == receiver) && (message_ID == Receiver::AnyMessageID || message_ID == event->messageToSend->messageID) ) { event->Process(); } } ChainIteratorOf j(&this[p].timedEvents); while ((abstract_event = j.ReadAndNext()) != NULL) { Check_Object(abstract_event); event = Cast_Object(Event*, abstract_event); // //------------------------------ // Make sure the message matches //------------------------------ // if ( (event->targetReceiver.GetCurrent() == receiver) && (message_ID == Receiver::AnyMessageID || message_ID == event->messageToSend->messageID) ) { event->Process(); } } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void EventQueue::TestInstance() { Verify(IsDerivedFrom(DefaultData)); }