Skip to main content
gamedev.net
🔒 Locked

Event-driven simulation?

Started by Caminman 4 replies 1.4k views
Original Post
Caminman
Caminman
Hello peeps! I''ve recently started out with C++ and I''d like to do something nice with my newly found class-skills. I''ve seen a few examples on the net (mostly java) about Event-driven simulation, but it seems that I haven''t understood all of it yet, so I thought I asked you guys What I''ve seen, a common event-driven simulation would be to simulate a store or a bank of somekind, with customers, cashiers and queues. What I know is that all the Events are stored in a list of some kind (preferrably a linked-list), and as the times goes on, the Events are handeled and they usually cause more Events to be placed into the list. If we take that bank thing as an example, we''d have Cashiers, Customers and a Queue. So these could be the Classes needed for the simulation with the addition of an abstract Event-class with different subtypes for the different Classes. The Event-Classes would, in my opinion only have the method Action() aswell as a reference to the owner. Also internal variables that tell what Action is to be taken need to be added. The Queue Class. addToQueue() // Creates and adds a few Customers to the Queue // and adds a new Event into the Eventlist // telling when it''s time to add more customers getFirst() // removes and returns the first Customer The Cashier Class. status() // returns CLOSED, OCCUPIED or FREE handleCust() // starts handling the customer and creates an // Event that tells when it can take // the next customer releaseCust() // releases the current customer and takes a // new one Does this look right at all? What happens if the Queue becomes empty? AddToQueue() checking if any of the cashiers are available? Okay, but that''d mean that the Queue-class would need a linked list containing all of the cashiers and loop them through to check if any of them are available. And most importantly, do I have the right idea here, I mean not every interaction between the objects seem to go via Events. Maybe this is the way it''s supposed to be? Help me, teach me! I''m not looking for any source-code, unless it''s very abstract. I just need to understand the fundamentals of Event-driven simulations to get me going. Thanks
Nypyren
Nypyren
... yeah that seems about right.

You have queues for things that need to be done in order, and you also have direct calls/messages (whatever you wish to call them) for things that you need to get done immediately, or need an immediate response.

In C#, objects can have "event" members. other classes that may want to know when the event occurs will add themselves to a "notify-me" list handled by the event member. All they must do is provide a compatible function that matches the requirements to be called by the event. This function compatibility is defined by "delegates".

In a real-world example, the code for a cash register COULD be set up so that an object controls the display, and another is keeping track of store-wide inventory. Both classes would register with the barcode scanning class to be notified when an item is scanned. The screen would then list the item''s name and price, and the inventory would be able to decrement the store''s known count for that item.

500*1
Shannon Barber
Shannon Barber
This is actually quite relevant to game programming in several areas, cause & effect chains (e.g. player pushes a button, 1 second later a door opens, 10 seconds later it automatically closes) are a nice fit to this architecture. The AI concept of an "intelligent agent" can be driven by this architecture (and you will end-up building two types of intelligent agents for this simulation, the tellers and customers.)

If the queue is empty, then the Great Fizzle has occurred and the universe has come to an end. You could have special NoEvent and/or Idle processing in your state-machines that could spark a new event to prevent eternal stasis. Also, some events could always generate another one.

Names like "addToQueue" are a bit ambiguous, how about scheduleEvent?

I personally am not inclined to incorporate the trigger time into the event handler. I think that information should flow through the scheduleEvent method (as a virtual time parameter) and be implicitly stored in the priority queue - the priority is the time to trigger the event. Then you advance your virtual time at a fixed increment and poll the queue for events with expired triggers. The use of virtual time here is very useful - you can run things in slow motion, or at warp speed, or even step through time step-by-step. You can build in a break-point system to run at full speed until something of interest happens, then break and go step-by-step. (I think there is an article in “Game Programming Gems III” about virtual time, under RTS networking?).

The spurious events are the most interesting ones. I think there are two in this system; a new customer entering the line, and a teller & customer finishing their transaction(s). Without the former, there’s not much to simulate, and the later spawns events that lead back to a teller & customer transaction.

Here''s a "C++eske" Event interface

  
struct IEvent
{
virtual ~IEvent(){}
virtual operator()(StateOfTheWorld*)=0;
};


It''s a virtual functor, and now it''s up to you to provide "closure" for the implementations. If you are not familiar with Modern C++ (see Loki in sig), there''s a Generalized Functor library that can help do this. You can use functor adaptors to help automate the implementation of the event classes, things like std::mem_fun, std::bind1st, and you may want to download Boost and use boost::bind and boost:mem_fn as they handle more cases.

Here''s a C++ example of EDS if you were looking for one


- Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

[Look for information | GDNet Start Here | GDNet Search Tool | GDNet FAQ | MSDN RTF[L] | SGI STL Docs | STFW | Asking Smart Questions ]

[Free C++ Libraries | Boost | ACE | Loki | MTL | Blitz++ | wxWindows| Spirit(xBNF)]
[Free C Libraries | zlib ]

The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Caminman
Caminman
Thankyou for your replies! Unfortunately I've been/still-am quite busy with Uni, so I haven't been able to check in here that often the past few days.


Magmai Kai Holmlor:
I'm not absolutely sure if I understand everything you said about the virtual time (learnt all the terms in swedish because of the darn book). Anyway the question that I have in my mind regarding time is the following:
Shouldn't the event-trigger send the actual time to the called object? ie. event.Action(time). Otherwise there should be a reference to the actual simulationtime from every object that sometimes need to know of it.

I've done some operator-overloading, but I don't think I've written anything like..
virtual operator()(StateOfTheWorld*)=0;
.. before. Is that what I was talking about, but with the difference that "StateOfTheWorld" reference will follow through to the events owner "automatically", so that it more or less seems like a global variable?

Would there be any difference if it was written the other way:
class event
{
int time;
SomeObj *owner;

void Action()
{
owner->doStuff(time);
}
}

Guess I need a bit more clarification here

[edited by - Caminman on March 13, 2003 3:56:00 AM]
Shannon Barber
Shannon Barber
Think of virtual time as simulated time, instead of using the clock in the computer, you make one with code to control the flow of time - like a VCR.

That code overloads operator(), which is sorta turns the class into a function, called a functor. It''s just syntactic sugar.

Passing the time to the action routine suggest a drastically different design. Now it''s the action routine''s job to determine what to do given a time, instead of doing its thing when it''s told it''s time.

Suppose a million events piled up in the queue, using the Action(time) design I think we would have to make a million calls to determine which events are ready to fire. With a sorted queue, you can just call action routines until the first one is not ready (and then you know the remaining ones are not ready yet either).
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Caminman
Caminman
Yeap, no confusion with the virtual time then

Anyway,
quote:
Passing the time to the action routine suggest a drastically different design. Now it's the action routine's job to determine what to do given a time, instead of doing its thing when it's told it's time.


I'm not sure if we are talking about the exact same thing here.
Lets say that we have a Queue-class with the method addToQueue()
which creates a certain amount of objects (customers) and puts them last in the Queue. The Queue-object should also decide at this point when new customers should arrive, therefore creating another Event and adding it to the EventList. Now, this is why I'd like to send the Current Time to the Queue with the
owner->doStuff(time) routine, so that I, in the Queue can create a random delay (will probably be more complex later) and add it to the Current Time, and create a new Event that is called at new time.

simply:
doStuff(time)
{
createNewCustomers();
addThemToQueue();
int newTime = time + rand()..;
EventList->addEvent(new qEvent(newTime));

}

(all the objects that need to schedule events have a reference to the EventList)


What I'm done so far works, but I think it's somewhat messy, and would probably turn into a disaster if I wanted to do anything more complex. Something ain't clicking yet.. :/




[edited by - Caminman on March 14, 2003 6:01:54 AM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.