Terrarium: Did we do the right thing with the creature event model?
History
Why did we use the eventing model? Well, because it made sense that creatures only needed to spend processing time on things they were curious about. It seemed like it might make things easier because a basic creature could hook only one event while a more complex creature could hook many events. It was also a great demonstration of how you would build pluggable apps within the .NET Framework. However, at some point we broke the model a bit and added some virtual methods for serialization and deserialization of creature data.
At no point did we consider revisiting the model and changing it out for another model that might be more appropriate. The backwards compatibility mindset was always in place and we didn't want people to have to recode their creatures entirely as versions changed. It also became rather difficult to change the pattern away from the 10 or so events and large eventing infrastructure that was in place. With that in mind we kept the pattern.
Pattern
Our pattern was to simulate the concept of discrete inputs. Certain things may or may not happen in a given round, and other things may not happen for the entire life-time of the creature. No reason to check for these conditions every tick of the game loop. We created several separate events that comprised certain worldy actions such as attacking, defending, eating, etc... We also needed the creatures to be notified even if nothing of importance happened. This ensures the creature gets some time to process irregardless of the state of the world. These are the Load and Idle events. The Load happens before all other events are called, while the Idle happens after all other events are called. Load allows you to set things up and prepare for action, Idle lets you operate after world notifications have been propagated. Easy enough right?
Complexity
We definitely made some mistakes. First, there are a lot of events. Many creature authors are curious about which events they need and the order the events happen in. We have some documentation, but that doesn't help when you are first picking up the Terrarium for the first time. You want things to be as plain and simple as possible. Another level of complexity is added in that many developers wind up doing work in each event that may or may not overwrite work done in other events. You could blame this on the person writing the code, I rather blame it on the model and blame myself for allowing it.
Additionally there is the problem of storing information about discrete events having happened. The author could store the information, but we already store it within the base class. This isn't immediately obvious. I mean why would we store the information for you and give you an event that gives you the same information? It makes more sense for you to be notified that it is your turn to process and then allow you to investigate the state of the world.
What about time-slicing? Since we time-slice you, each event we call is taking a bit more of your time. Additionally it is harder to load balance your time across ten events than it would be across a single event. You have to store the amount of time spent in each of your events in order to get an accurate measure of how much time you used and how much you have left to process. For path-finding algorithms you may want to make the most effective use of your time by knowing about how much time you have left, even if the measure is somehow relative.
No Guarantees
We don't guarantee that your events will fire. In fact, we can skip you if you are taking too long, and the only way you know is by examining a special property that tells you for how long you were skipped. Any in process events continue processing, so while you aren't being notified, your move may complete or you may get teleported. If you are relying on the eventing model to keep your private state in check you are in for a rude awakening.
Single Cast Events
Having single-cast events isn't very useful in our model as you can tell from some of the downsides. In fact, in trying to demonstrate a plug-in model, we desperately failed. Normally, plug-ins will hook up to some global events that exist on the game engine and not exhibit a bunch of local events that are only consumed by the plug-in.
Fixing the Model
The model is quite easy to fix. Simply don't use events. There is no reason to use events in our model, rather with a base-class already in place we simply supply a single callback method, virtual of course, for the creature to implement all logic. This drastically reduces the complexity of the code required to implement the creature into a single entry point program. While you might think that authors couldn't code creatures that were nearly as functional as before, they actually have more time to operate now and have a less complex model to program against. As I'm making these changes to the source it simplifies other portions of the engine and helps refine the layout of the interfaces between the creature base, the engine, and the UI.
Working With What You Have
You have some options already as a creature developer. By writing a simple base class that manages all of the events for you and stores local information in a very minimalistic format, you can write your derived creatures to use the new local information from within a single method that is fired by the Idle event. At one point we had a work-item open to ship this class within OrganismBase itself. The idea would have been to have the Animal class for anyone interested in programming the old model, but also a new SingleCallAnimal supporting the new, simpler syntax.