Here's the engine explanation I typed out. It's fairly stream-of-consciousness and conversational, but it's hopefully readable:
The main idea of the engine is to be pluggable. The idea being as little specific interaction between parts as possible. I'm going to go through my application class, explaining everything that I come to. That should cover everything.
The application class is derived from EventCatcher, which will be explained in a minute.
The main function instantiates the application, and calls its Init function. This is arbitrary, not inherited from anything. The EventCatcher class derives from MMObject (memory managed object) so it's a smart pointer. the main function also calls a global function (:() which starts debugging, and at the end calls MMObject::CollectGarbage()/CollectAll() to clean up auto-managed pointers.
So we're in Application::Init. This function sets up the engine and passes control to the kernel.
The way my system works is that anything derived from EventCatcher (here you see why Application derives from it) can be plugged into the kernel as a child. In theory, there's no reason why you couldn't plug something into a child, and then that child into a kernel: the kernel is an EventCatcher itself. This would allow me to do a server/client model, by simply adding code into a NetworkKernel say, which adds itself in such ways. The Child/Kernel interface is abstract enough to allow me to put networking in there easily.
Now what Children can do, is flag themselves as desiring any number of different events. Then any events that the kernel is given to distribute to the various children (you can think of them as modules in the ode) it distributes to those who wish it. Then children can send messages themselves. One possible problem is that all these calls are recursive: a child receives message x, then sends message y, which is retrieved by another and so on. Only once the first child stops processing x does the second child get x. I can't see any immediate problems, but I suspect some might emerge. Perhaps this would be a good place for threading? I don't know, I haven't threaded much.
To start the whole thing off the kernel always sends out an update and a render message each frame, in that order.
Note: events can have properties, specific to the type of event. Think SDL_Event.
So App::Init is instantiating new modules for the kernel to use (creating a Kernel first). However, it doesn't add them to the kernel: they do this themselves, so they can set up their message-accept flags. So essentially all that happens is you call new, pass the kernel, and let themselves sort themselves out.
Then the App adds itself as a child, setting up the appropriate message flags (mostly everything). I'm not sure if in the final situation this would be necessary. Perhaps game processing/logic will be in another generic module? ie. everything would be generic in code, and things are scripted/taken from game resources? I don't know if that's the best way to go, but its' a possibility.
At the moment, the App controls not only game processing but also rendering, because my generic GLRender module doesn't have the proper guts in it yet to do rendering. Ideally you'd instantiate the GLRender, give it a scene and tell it to go off and do its thing. At the moment it just sets up the window and flips buffers every frame.
Then, App passes control over to kernel.
The kernel gets itself a timer. A timer is an example of a non-high level class. It's not an eventcatcher or anything fancy like that, it's not designed to govern a module, it just performs timing functions.
Then the kernel fires off various pre-set messages in a pre-set order, then gets into the main loop until told to get out of it (normally by the update message sender returning false - ie some update message has told the kernel to quit, but possibly the render message too). After that it sends off various "closing down" time messages and finishes.
As a side note, the kernel keeps a note of all the possible message flags, and a list of children who use it, so that sending events is a simple iteration, not an iteration/conditional loop.
That's mostly it as far as the high-level view goes. Most of the other classes are designed to be isolated and used whenever needed - a texture class, matrix, vector, log class, lighting, glsl loader/user, font class, model loader, scripting engine (which will hopefully get tighter integration into the engine eventually), terrain generation etc. I have a fairly solid GL GUI system which works nicely, but has plenty of nice things I can still do to it. The texture class has some nice features, for example although it can be used as just Texture tex; tex.load; tex.bind; you can also load the texture into an intermediate floating-point buffer, and perform things like gaussian blur and perlin noise, along with blending. More nice features could be added as needed.
The only main addition to the high-level modules that I can see is the GLRender module actually doing rendering. I envisioned adding a scene graph here so that the application would just fire off what it wanted to render, and control it via game logic perhaps, and the GLRender module would take care of getting it on the screen right way up and looking pretty.
Diagrams to follow!