Delayed Actions Vs. Instantaneous Ones
I am currently finishing a system for managing my world entities and their AIs and stumbled upon an interesing problem that deals with delayed vs. instantaneous actions. What do I mean by that?
Let's take a simple messaging system: I want to be able to send messages to my entities (for instance when they collide, when one kills the other etc.) So I call my MessageHandler.SendMessage(...) function which puts the messages on a stack and when MessageHandler.Update() gets called in my main loop, it distributes all my messages to appropriate recepients. Another example is removing entities - instead of directly calling EntityManager.RemoveEntity(), I, instead, set a bool Destroyed value inside my entity to true and when EntityManager.Update() gets called in my main loop, it removes all entities marked as destroyed. This is what I mean by delayed actions - parsing a message or deleting entity will not take effect until the specified point in the game loop.
Now instantaneous examples of the above would be - instead of MessageHandler putting a message on a stack, it would directly call the recepient entity's HandleMessage() function. Similarly, when wanting to remove an entity I would call EntityManager.RemoveEntity() directly instead of marking Destroyed() as true and waiting for the loop.
I have gone with the former approach as I assumed that it would give me more control over what happens - for instance, with instant removal of entities I could not implement any death animations for them. Furthermore, it also cleanly divides the loop into segments (Updating Entities, Processing Messages, Rendering etc.) instead of mixing the procedures, thus leading to a clearer code and preventing lockups in case of cyclic messages (A->B->A->B etc.)
But then I ran into an interesting problem: I have my entities rely on states for AI management. An example is MakeDecision state (decides what to do next), EatNearestFood (eats nearest food) and AttackNearestEnemy(you get the idea). Furthermore, since I am using a neural network that I train, whenever my entity eats the object it decided to eat, the network gets reinforced as it positively accomplished its goal (conversly, if the entity gets killed by the enemy it tried to attack, the network gets inhibited). So a flow for eating and being rewarded for it looks like this:
EntityManager.Update() - updates all entities
-- MakeDecision.Execute() - using the neural net decides to eat nereast food, chaning state to EatNearest
-- EatNearest.Execute() - finds nearest food and pursues it
CollisionManager.Update() - sends message that entity collided with food to both
MessageHandler.Update() - Dispatches the message about collision
-- Food.HandleMessage() - sends a message that Entity just ate food and marks itself as Destroyed (not in collisionmanager as I want logic to stay in entities)
EntityManager.Update() - updates Entity and removes Food as it is marked as destroyed
MessageHandler.Update() - Dispatches the message about entity eating
-- Entity.HandleMessage() - forwards the message to its EatNearest state
-- EatNearest.HandleMessage() - notices it ate what it wanted and reinforces the neural net
EntityManager.Update() - updates all entities
-- EatNearest.Execute() - realizes that the food it was pursuing doesn't exist anymore and changes state to MakeDecision
The cycle repeats
All fine and dandy right? Except that the fact that my messages are delayed until MessageHandler.Update() call results in a problem - the food gets removed before the message about it being eaten gets processed. My EatNearest state realizes there is no "nearest food" before it receives the message and changes state to MakeDecision and never gets to process the message that it ate (and MakeDecision cannot process this Message as it doesn't know what EatNearest wanted to eat).
I am not looking at a solution to this problem, but merely wanted to use it as an anectode to spark a discussion about the topic. On one hand I could also delayed the changing of the state until the next main loop cycle, but I have a feeling that as I introduce more and more systems, I'll eventually end up with even more problems due to the order in which information gets processed. On the other hand, if I make everything instantaneous, I fee like I am losing control over the flow of execution of my program.
What do you guys (especially those with more experienc than me) think? Is delayed processing of information a good idea or would I be better switching to instantaneous type? Or is this simply something you need to mix and match carefully (such as, messages directly, entity removal at one point in the loop etc.) ?
[Edited by - Koobazaur on June 5, 2008 9:37:01 PM]
We use delayed actions for AI. Specifically:
* Set "next action", which includes all the details of the action such as targets and such.
* On the next update if next action != invalid && next action != current action, begin transition.
* While transitioning, lock certain attributes, queue future actions, and so forth. When queried, the current action is 'transition'.
* When transition is complete, unlock the attributes.
* Set "next action", which includes all the details of the action such as targets and such.
* On the next update if next action != invalid && next action != current action, begin transition.
* While transitioning, lock certain attributes, queue future actions, and so forth. When queried, the current action is 'transition'.
* When transition is complete, unlock the attributes.
Why not do a sort of reference count system. So instead of food getting deleted before the action of being eaten you have the eaten message tick up the reference count on the food item. So instead of deleting the food you put it into a pending to be deleted list if it has reference counts on it. So then once the eaten action takes place tick down the reference count on the food item. Then you just to regular checks on the pending deletion list to see if the item is ready to be deleted. This idea goes for everything btw.
Corman, that's an interesting idea but I think it would lead to it's own set of problems - while an item is pending deletion and not removed, it is still part of the game world. Thus, if my EatNearest checks and sees it still exists albeit is scheduled to be deleted - what should it do? Should it keep pursing an object that shouldn't exist? What if it gets to it and eats an object that has been already eaten? Or should it just ackowledge it as non existant and move onto another, which is exactly what it already does and root of the problem?
Basically it should become for a lack of a better term invisible to the world with the eat action the only thing able to see it. So the eat action basically becomes the link for the entity to see it that took or should take the eat action on it. So basically the food has undergone a loose removal and only is left to exist for a single purpose action thats already been queued and waiting to take place. Then when all said and done it can be truly removed from memory.
But you see, the pursuing of the item to be eaten (EatNearest.Execute()) and handlding of the "ate" message (EatNearest.HandleMessage()) is done at two different times, the first during the general update of my entity and the second during the message resolution. There is no "eat action" - it is handled via messages - Food sends message to entity that it "ate" it, but this message does not always gets processed before the food gets deleted and the entity executes its AI state which, upon seeing the food it was pursuing doesn't exist, switches to a different state.
If I remove the food from my list but keep a reference to it, switching the state in the HandleMessage() after the "ate" message is processed as you are saying, it would indeed work... as long as I have only one entity pursing the food. What if I have two? They both have references to it, so even if one eats it up and moves on, the second would still be trying to get to it. That's why I opted to check if the food my entity is pursuing exists every update call and switching to a different one of not, exactly to avoid this problem.
If I remove the food from my list but keep a reference to it, switching the state in the HandleMessage() after the "ate" message is processed as you are saying, it would indeed work... as long as I have only one entity pursing the food. What if I have two? They both have references to it, so even if one eats it up and moves on, the second would still be trying to get to it. That's why I opted to check if the food my entity is pursuing exists every update call and switching to a different one of not, exactly to avoid this problem.
Well it is a bit late here tonight and I do need to get to bed here. I will continue to talk with you on this tomorrow as soon as I can. So I am not giving up on you just yet instead I am just giving up the will to keep my eyes open and my head up. [grin]
Great, I'll be checking this tomorrow! I'm having a feeling that the reason I am not getting what you're saying is becauase your model is inherently different from what I implemented; I'm curious to learn more :)
Isn't the answer simple here? Some messages have a high priority, others don't. Messages about collisions seem to be pretty important so you don't want to delay them.
Also, you may want to process messages first, then update your entities. Otherwise they'll be running behind a frame. You may find this article interesting: programming responsiveness. Perhaps not entirely related but it's surely insightful.
Here's another idea: let your entity register itself to that apple, to directly receive messages whenever something happens to it. When the apple is removed, it sends a message to all it's listeners and then makes them unregister. When it collides with something, all listeners receive a message about it. It's something I played with a little for another game. I didn't get too far with it, but the approach showed some promise over a central messaging system.
Also, you may want to process messages first, then update your entities. Otherwise they'll be running behind a frame. You may find this article interesting: programming responsiveness. Perhaps not entirely related but it's surely insightful.
Here's another idea: let your entity register itself to that apple, to directly receive messages whenever something happens to it. When the apple is removed, it sends a message to all it's listeners and then makes them unregister. When it collides with something, all listeners receive a message about it. It's something I played with a little for another game. I didn't get too far with it, but the approach showed some promise over a central messaging system.
Thanks for the link and suggestion Captain P.
However, moving the messsage before entity check does not solve the problem - it's a bit hard to explain and I'm tired so I won't go into details but think of it this way: once the loop starts going, it goes in circles - there is no beginning or end. Hence, stuff that happens "before" something, also happens after it meaning that, in the long run, the order doesn't matter.
For the time being I fixed the issue by processing all messages on stack, including the newly added ones (thus when my food gets message about being collided with and sends a new message to the entity, that new message gets processed within the same call as the original one rather than waiting for the next loop iteration). I'm definitely rethinking this whole model for my next project, though.
However, moving the messsage before entity check does not solve the problem - it's a bit hard to explain and I'm tired so I won't go into details but think of it this way: once the loop starts going, it goes in circles - there is no beginning or end. Hence, stuff that happens "before" something, also happens after it meaning that, in the long run, the order doesn't matter.
For the time being I fixed the issue by processing all messages on stack, including the newly added ones (thus when my food gets message about being collided with and sends a new message to the entity, that new message gets processed within the same call as the original one rather than waiting for the next loop iteration). I'm definitely rethinking this whole model for my next project, though.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement