Resource Management in MAME
If you're writing a driver, or adding save state support to a driver, it's important to understand how resources are managed in MAME.
In the old days, resources in MAME weren't tracked at all: if you did a malloc()
, you had to do a free()
; if you did a timer_alloc()
you had to do a timer_free()
, etc. Except that you probably didn't bother in a lot of cases because the standard command-line version of MAME only runs a single game at a time, and so it didn't really hurt to leave a bunch of extra resources allocated when you quit. Of course, those other ports like MacMAME — which allow you to stop one game and start another without quitting — would find that they would eventually crash due to running out of memory or some other resource thanks to all the leftovers.
Enforcing good resource management in the core is pretty straightforward: it is a relatively small amount of code, and doesn't change very quickly. However, enforcing good resource management across the hundreds of drivers is pretty much a nightmare. Drivers are hooked into the main system by providing callbacks. Below is a list of the common ones:
DRIVER_INIT MACHINE_INIT MACHINE_STOP SOUND_START SOUND_STOP VIDEO_START VIDEO_STOP
Most drivers allocate resources in either MACHINE_INIT or VIDEO_START, and in theory are supposed to provide a MACHINE_STOP or VIDEO_STOP callback to free those resources. In practice, this was used inconsistently, and it's also kind of a pain. Furthermore, some systems would allocate memory in DRIVER_INIT, but there was nowhere to properly free the memory. I suppose we could have added a DRIVER_STOP callback, but ultimately a different approach was taken.
If you think about it logically, 99% of the time, you would want MACHINE_STOP to free up all the resources allocated by MACHINE_INIT. Similarly, you would want VIDEO_STOP to free up all the resources allocated by VIDEO_START. So what if, instead of requiring each driver to write code to release the resources, the core simply kept track of which resources were allocated when and automatically released them at the appropriate time? Turns out this works pretty well.
If you look at the ordering of when the callbacks are called, it looks like this (excuse the pseudo-code):
init { DRIVER_INIT VIDEO_START reset: { MACHINE_INIT run-the-game-until-exit-or-reset MACHINE_STOP } if we-exited-due-to-reset then loop back to reset: VIDEO_STOP } exit
You'll notice that I have a couple of sets of curly braces embedded in the above pseudo-code. This gives you a hint as to how the automatic resource tracking works — it's very much like local variables in C/C++. As you leave each scope, all the tracked resources that were allocated within that scope go away automatically. When you hit the closing curly brace between MACHINE_STOP and VIDEO_STOP, the core will release all resources that were allocated since the corresponding open curly brace, which includes anything that was allocated at MACHINE_INIT time, as well as any tracked resources the core allocated at that time. Similarly, when you hit the second closing curly brace, all tracked resources allocated by DRIVER_INIT and VIDEO_START will be released.
Note that I said tracked resources. Only certain resources are tracked, and some of them need to be explicit. Timers, for example, are always tracked. Anytime you allocate a timer in MACHINE_INIT, it will be released when you exit the inner scope. In fact, timers are tracked in such a way that there is no explicit timer_free()
call anymore; you have to rely on resource tracking to get rid of them.
The most obvious resource is not automatically tracked, and that is memory. The reason it is not automatically tracked is that there are some legitimate reasons for doing a malloc()
and having it survive the boundaries of a scope. You can manually have MAME track memory by using auto_malloc()
instead of malloc()
. auto_malloc()
works identically to malloc()
except that any memory allocated will automatically be freed when you exit your current scope. You must be a little careful here because if you do an auto_malloc()
in MACHINE_INIT and store that pointer in a global variable, the memory will be freed when the game is reset and MACHINE_INIT will be called again. Your global variable will still have that same pointer value, but the memory will be gone. The right approach is to never look at the old value of a global pointer like that, and simply always auto_malloc()
in your MACHINE_INIT callback.
Another common resource that needs to be manually tracked is bitmaps. There are auto_bitmap_alloc()
functions that create bitmaps which are automatically reclaimed when leaving the current scope.
The last major "resource" that is tracked in this fashion is a recent addition: saved state registrations. Prior to recent changes in the system, you could only register data to be saved in the outer scope (DRIVER_INIT/VIDEO_START) and no later. This was because MACHINE_INIT may be called multiple times if you hit F3 and reset the game, and the saved state system cannot handle duplicate reigstrations (I know, it doesn't seem that hard, but there were some major complications). In order to manage this, when you exit a scope, all saved state data that you registered within the scope will be forgotten, meaning you will need to re-register it again. Since the most likely case is that you registered it in your MACHINE_INIT callback, and since after a reset you will get another call to MACHINE_INIT, this doesn't really mean you need to do anything special. Simply perform your registrations there like nothing happened and it will all work out fine.
If this sounds confusing, please continue by reading the articles on adding save state support to drivers, which should help clarify some things.
Back to How MAME Works