Who controls the controllers?
The Model-View-Controller pattern is quite a wonderful thing. I had some troubles accepting it because I was born at a time when the most powerful computer in the world had 8 Mb of RAM and a CPU beating at 80 MHz. I still feel a squeeze in my heart when I write an if statement inside a for loop, so I found it hard to accept replacing function calls with messages broadcast all over the place to components that don’t care. To me, it’s like taking all the axones away from a nervous system and ask the hormones to replace them in their job: a tremendous waste of memory and CPU. But memory and CPU are cheap now, and my ambitions are relatively modest from a performance point of view. So I gave in and am now a happy man.
I won’t spend much time describing it because others have done it quite nicely before me. I really urge you to read the following articles if you don’t really know what it’s about.
The idea is to separate the
- game logic –or Models– (physics engine, position of the characters, tiles in the map)
- from the way it is presented –by Views– (sprites on the screen, debugging console, a bit of network code that sends the info to the client who is the one doing the actual rendering)
- and from the way it is controlled –by Controllers– (keyboard, mouse, clock, network sending info from a client or a server).
This makes developing our game a bit like playing with Lego bricks: adding or removing a brick does not interfere with anything. Models Views and Controllers send Events to an Event Manager which takes care of forwarding them to other Models Views and Controllers.
So who controls the Controllers ? The Event Manager does. It also manages the Views and the Models.
For example, the server does not need to display anything, only the client does. So you just delete all the graphic Views from the server code. You can plug a console View that shows the server load if you want. An other example: if you want to replace my PyGame 2D view by some 3D OpenGL magic, go for it. The game logic does not care one bit. This is all very well described in the two articles I linked up there.
However, I would like to add a few things in this post.
Brown’s implementation does not scale very well. It works perfectly of course and has the immense advantage of being very easy to understand, which is exactly why Brown chose this implementation for writing his tutorial. But when you look at it, 1) every Event is sent to every Listener (Model View or Controllers) ; 2) from there, a bunch of if statements will decide how the Listener will react, if it reacts at all because in many occasions the Listener doesn’t even care about that type of Event. Stone me for optimizing early, but it could be interesting to have the Listener specify which kind of events they are interested in (solving problem 1), and give to the Event Manager a direct handle to the code that should react to it, instead of filtering with ifs (solving problem 2).
Both Brown and Koonsolo apply the traditional MVC pattern which allows the View to know something about the Model. For example, the DragonSprite View really has a pointer to the DragonCharacter Model. I don’t think I’ll do that.
The Model sure ignores the View but the View knows about the model. This asymmetry feels dirty to me. It’s almost an invitation to modify the Model from the View, which is a bad thing. But even if you behave, I am still annoyed by having to carry full objects in my Events. For example, the MonsterAppearedEvent will have to carry the DragonCharacter object so that the DragonSprite be created and points to it. That complicates the serialization when you need to carry Events over the network and that makes debugging a mess. First, too many pointers and references: you don’t know who owns who, and if you use WeakRefs in the View to avoid memory leaks you’ll end up with broken pointers. Second, when you print an event into a log file it doesn’t help you at all because the CharacterMovedEvent does not tell you where the character moved ; indeed the View just has to look into its model to get the info. Third, it becomes difficult to test the View without testing the Model itself.
Does the Sprite really need to know the whole Model ? Of course not. If the Model moves, then the sprite just needs to know the new position, and this position can be stored into the CharactedMovedEvent.
One advantage I can see when one sends the full Model is that the View can initialize itself in one go. When the View is created, it knows already everything about the model and can display it properly at the right place. But we can also do it with events. You can have the CharacterCreatedEvent contain enough info for the View to initialize itself. And if it’s still not enough, the View can post a CharacterPositionRequest Event and wait for the Model to answer with a CharacterPositionEvent Event.
What I’m going for is not traditional MVC, I’ll use Events all the way. Which means that there is no real difference between a Model, a View and a Controller, at least not from an implementation point of view. They’re all Listeners, waiting for events to tickle them and posting events when they feel like it. However, what I’m not doing, is having all the Models talking to each other through events. I want to separate Model, View and Controller, I don’t want to separate the model from itself unless there’s a very good reason to do so. Can’t think of any right now.
So I’ll be working on that. I’ll tell you when it’s pushed on GitHub!