Re: Lahman, how ya doing?
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Sat, 14 May 2005 19:27:31 GMT
Responding to Hansen...
Backing up a little, I want to return to some technical details.
And you said you were winding down!
class EventElement { private: Object* recipient; int event_id; int tick_count; public: EventElement (Object* r, int e, int t) {recipient = r; event_id = e; tick_count = t;}; int getTickCount() {return tick_count;}; }
class Timer { private: EventQueue* queueManager; EventElement eventList[MAX_EVENT_COUNT]; int tick_count; int next_event; public: Timer (EvetnQueue* e) {queueManager = e; next_event = 0; tick_count = 0;}; void add_event (Object* o, int e, int t); void tick() void reset() {tick_count = 0;}; }
void Timer::add_event (Object* o, int e, int t) { EventElement event = new [] (o, e, t); eventList[next_event] = event; next_event++; if (next_event == MAX_EVENT_COUNT) // signal exception. };
void Timer::tick() { tick_count++; for (int i = 0; i < next_event; i++) { int eventTickCount = eventList[i].getTickCount(); if ((tick_count MOD eventTickCount) == 0) queueManager->push(&eventList[i]); } }
Here in Timer::tick() you're pushing pointers on to the queue from a pool of events held by Timer. Not relevant to my simulation, but generalizing to a case where any amount of time can pass between when an event is pushed on to the queue and when it is popped off and processed, it seems you can have two identical events going to the same object. I guess that still doesn't matter the way you've set it up, but if I'm passing data with an event (and you've opined on that practice!) I can imagine the content of the first being modified by preparing the second. So I'd want to make a copy for the queue, and destroy the copy at the end. Fire and forget, but somewhat different queue behavior, and all events must be processed that way if any of them are.
Actually, I wouldn't have the same event going to the same object on a given tick. I might send the same event to multiple objects on a tick, though. And I would certainly send the same events to the same objects on different ticks.
Remember an event is just a message and it is defined by {message ID, data packet}. The message ID will be chosen to differentiate the semantics of what the event announces (e.g., a ubiquitous 1-tick event vs. a special 60th-tick event). The data packet signature (content and format) is also fixed for the event.
As a practical matter one includes the target object address with the event when it is pushed on the event queue. But that is really an orthogonal issue related to the nature of the collaboration. That is, collaboration is about deciding what message is sent, who sends it, and who receives it. There is nothing to prevent having the same event be associated with multiple {sender, receiver} pairs (just as there is nothing to prevent the same event triggering different transitions in a state machine). In particular, broadcasts where a sender sends an event to multiple receivers are not uncommon.
However, when sending the same event between the same two objects arises, there is another consideration. One sends an event when a condition prevails. In an asynchronous model that only happens as a postcondition of executing a state machine action. One only needs one announcement to the receiver for that. So to send the event to the same receiver a second time the condition must prevail /again/ in the sender. That is, the sender must transition to the state again for the sending action to execute a second time to send a second event. Since reflexive transitions are possible, an object might send the same event multiple times in what is effectively a tight loop over the same action. But that reflexive transition still needs to be triggered by consuming an event in the sender state machine.
This is one view of the nature of handshaking protocols. The collaborating objects "walk" through their state machines in a synchronized manner. Such a "walk" may form a large scale iteration over the same actions and event generation. But that "walk" is triggered at each step by an event from the other object and that provides the lockstep synchronization. [Hence the handshaking metaphor: first the right hands shake, then the left hands shake, then the right hands,...]
In your case the simulation tick is the basis for synchronization and registering the events is a mechanism for synchronizing the Timer state machine with everyone else's state machines. So Timer may broadcast the same event to multiple objects on a tick, but it won't repeat sending the same event to the same object until synchronization through the next tick (i.e., the Timer.tick() action is triggered by consuming another event).
To put it another way, the event queue manager stack is effectively doing the copy/save you are talking about. Timer issues the right events to the right objects in the right order. The event queue's FIFO stack preserves the order while the software executes the <serialized> responses to the events.
And if I don't pass data in the event, but data still needs to be passed, I don't know how to pass it. Using Timer as a specific example of the more generic question, it *could* be given an accessor function get_time(), a receiving object *could* get the time directly through that. But if it's to process event1 using time1 and then event2 using time2, and Timer has time3 by the time event1 hits the object and time4 by the time event2 hits it, I can't think of an easy alternative other than to stuff the data you need into the event so that it's all there.
You only need to pass data on an event if data integrity requires that when the event is consumed the value accessed be the value when the event was sent (as opposed to it current value). That's because the software must already support a means for the responding action to access the current value synchronously. That's because in the OOA/D paradigm one accesses knowledge synchronously on an as-needed basis. So one uses event data packets when one /doesn't/ want the current value.
I assume what you are angling for here is the issue in the other message: providing an absolute value of current time for computing a slope. If absolute time is measured in terms of number of ticks, then you could deal with the skip problem by having Timer increment a tick count for actual ticks processed. Whoever needs the absolute time could then ask Timer for the current value of that tick count.
But for the slope calculation Thermometer would still have to save the "current" value with the Sample value because it needs the value for the the sample ~60 ticks previous. In my solution in the other message, I organized the state machines so they used a local tick count in Thermometer between first/last samples. But one could substitute a synchronous call to a Timer.getTickCount() getter to do the same thing as the local count increment.
There really isn't much difference in those solutions. I prefer mine slightly because it localizes the semantics of what absolute elapsed time means. I want to avoid any chance of Timer needing to keep track of multiple elapsed times. OTOH, it is hard to imagine a situation where the receiver couldn't do its own time-stamping from a single Timer absolute tick count just like Thermometer.
MODing it is an interesting approach that I hadn't thought of. And that makes more sense of some of the other things you'd said-- I imagined filling the event queue with 2 million events before the simulation even began!
Presumably Timer.tick() is invoked when all the processing for the current simulation tick is completed. (The easy way to do that is for the event queue manager to invoke tick() when it is empty since tick() will fill it up with the events for the current tick and those all have to be processed.)
In my non-multitasking system, where would control pass to a queue manager that would pop events and distribute them?
Once the application is initialized (presumably in main() or equivalent for the language) a "seed" event is put on the queue and the Queue is started. It pops events until the queue is empty, in which case the application is done. Note that the state machine actions put events on the queue and the pop is not complete until the action completes, so the queue will not empty out until all the processing is completed.
So what happens if the queue empties before...
Things are more complicated in a truly asynchronous system where the queue may have to wait for an external event. (Or for events generated by a real- or scaled-time timer.) But then the push just needs to restart the queue operation if the event count is exactly 1 after the push. Since you are using simulation ticks rather than scaling, this
Oh, okay. I suppose somewhere in the bowels of the queue it will be looping until it finds a quit event directed to queue?
Not exactly. There can be times when the queue is completely comatose. As a simplified version of an queue manager consider:
class EventQueue
{
private:
int event_count;
Event* next_pop_event;
Event* nex_push_event;
Event* (event_list[MAX_EVENTS]);
void pop ()
void start ()
public:
void push (Event* e);
}EventQueue::pop()
{
// do processing to invoke responding action
// I think I already went over this part in another message
// This will essentially be a synchronous cal to the
// responding state action that will return when the action
// is complete.event_count = event_count - 1;
// update next_pop_event for FIFO in event_list rung buffer }
EventQueue::start()
{
// launch thread for popping events around following code
// so that start and push can return while queue is emptied
while (event_count > 0)
pop();
}EventQueue::push(Event* e)
{
// push e into event_list ring buffer event_count = event_count + 1;
if (event_count == 1)
start();
}The point here is that when the events have been exhausted, the thread initiated from start terminates and the queue is completely idle until somebody pushes an event.
[Caveat. This is oversimplified because to be thread-safe in concurrent processing one would have to pause the pop thread, provide some temporary buffer for pushed events that pop checks, or provide some other means of synchronizing the update of event_count.]
doesn't matter because you just just need an event to Timer to trigger tick() when all the tick's processing is completed. In your case there are a couple of easy options: (1) have Timer put a self-directed event on the queue as the last event or (2) have the event queue manager call tick() when it is empty. (Tick() can generate an event for a graceful exit if, say, the maximum simulation ticks have executed to provide an exit.)
Hey, I just told you about (1)! Some of what you've been telling me is starting to make more sense in retrospect.
While (2) is valid, it has a problem. Once one gets the event queue manager right, one wants to reuse that effort across applications. If the event queue gets into the business of generating special events to particular application objects, it won't be reusable.
There is also option (3) where somebody else puts the event to Timer on the queue. That would actually be better if there is a clear place in the processing (given the ordering defined in registration) when one can be sure the essential processing for a tick has been completed. That is more consistent with the notion that the event announces the condition that the essential tick processing is done, which intuitively seems like something the processing itself should know rather than Timer.
However, that is a pretty purist view and in your case it is hard to imagine the self-directed event would come to grief, especially when it is defined using the same registration technique as the way the rest of the simulation ordering is done.
************* There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@xxxxxxxxxxxxxxxxx Pathfinder Solutions -- Put MDA to Work http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman (888)OOA-PATH
.
- Follow-Ups:
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- References:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- Prev by Date: Re: Lahman, how ya doing?
- Next by Date: Re: A Java Brainteaser - a Static Factory method Narrative
- Previous by thread: Re: Lahman, how ya doing?
- Next by thread: Re: Lahman, how ya doing?
- Index(es):
Relevant Pages
|
|