Managing the File List
The Loader maintains a list of all files that it knows about. This list serves two purposes:
To keep track of where various files are located so that when the caller requests a resource, the Loader can immediately find and load that resource.
To hold on to loaded file objects so that multiple requesters can share them. We refer to this as the cache. When the caller requests a cached object, the Loader immediately returns the object with its reference count incremented.
There are several methods of the IDirectMusicLoader interface that give you control over the file list.
ScanDirectory() searches a directory for a specific type of file, as defined by its class ID and file extension (the * wildcard is supported.) For example, you might use it to search for all Style files within a particular directory. This does not load any of the files. Instead, it adds all their location information to the file list. To do this, it parses each file header, looking for all pertinent information to fill the DMUS_OBJECTDESC record for each file. Therefore, internal information, including object name and GUID, is retrieved and stored in the list.
ScanDirectory() is useful primarily in conjunction with EnumObject(), which lists the objects one by one. This is useful for applications that need to display all available files of a particular type. This is great for media playback and authoring applications but not needed for games that usually have dedicated soundtracks. As long as file names are unchanged and SetSearchDirectory() is used appropriately to establish where to find them, ScanDirectory() incurs unnecessary overhead, so avoid using it when possible.
EnumObject() scans through the file list and retrieves information about all files of a specific file type, identified by its class ID. For example, an application might use this to present the user with a list of all playable media files. It would do so by calling ScanDirectory() to scan for all Segment files (which can include wave and MIDI files as well) and then calling EnumObject() to list them.
EnableCache() and ClearCache() are very important for effective memory management.
When the Loader opens a file, it keeps an internal reference to the loaded object in its file list. The Loader effectively caches the file, so a subsequent request simply receives a pointer to the original object. This is all fine and well, but it has its cost. Some file types, like Segments, are rarely, if ever, referenced by anything else. Therefore, it is usually just a memory waste to cache them. EnableCache() lets the application decide which file types the Loader should cache. File types that typically are referenced by others, like Styles and DLS Collections, are great candidates for Loader caching since it ensures that just one instance of a Style (for example) is created and then referenced by all objects that use it. Segments are not.
It is not always the case that there are no objects referencing Segments. Both Script objects and Segment Trigger Tracks can reference Segments. In those situations, enabling caching may have merit. Even then, it is typically rare to have multiple Scripts or Segments referencing the same Segment.
ClearCache() provides a quick mechanism for clearing out all objects of a particular type. For example, you might call ClearCache() for some or even all file types when moving from one scene to another in a game.
ClearCache() does not remove an object from memory unless the Loader is the only entity referencing it. For example, if you call ClearCache() for all DLS Collections, it will remove all references from the Loader, but any DLS Collections currently in use by loaded Segments will continue to stay in memory until the last Segment is released, which in turn does the final release on the DLS Collection. This means that music and sound effects will continue to play without a glitch. Ah, the beauty of COM….
ReleaseObject() and ReleaseObjectByUnknown() can be used in lieu of ClearCache() to release a specific object from the cache. Use these when you need to remove one but not all objects from the cache.
These methods are identical in functionality. ReleaseObject() requires the IDirectMusicObject interface, which all loadable objects must support. However, it is obnoxious to have to call QueryInterface() to get that interface in order to then call ReleaseObject(). So, with DirectX 8.0, ReleaseObjectByUnknown() was added to the API. It lets you use the regular interface for the object, and it internally queries for the IDirectMusicObject interface.