# 5. White Papers | Asynchronous I/O

## Asynchronous I/O and deferred file reading

### Introduction

This tutorial will describe how to defer file reading in FMOD so that you don't have to immediately satisfy FMOD's requests for data.
This sort of behavior is highly desirable in game streaming engines that do not have access to the data yet, or for when accessing data out of order or in a non sequential fashion would greatly degrade performance.
FMOD's asynchronous I/O callbacks will allow you to receive an FMOD read request and defer it to a later time when the game is ready. FMOD will use priorities to notify the game engine how urgent the read request is, as sometimes deferring a music stream read for example could result in stuttering audio.

### Setup : Override FMOD's file system with callbacks

The idea is that you are wanting to override the file I/O that FMOD normally performs internally. You may have done this before with the System::setFileSystem by overriding the following callbacks:

    FMOD_FILE_OPENCALLBACK  useropen
    FMOD_FILE_CLOSECALLBACK  userclose
    FMOD_FILE_READCALLBACK  userread
    FMOD_FILE_SEEKCALLBACK  userseek

The normal behavior here is that you would need to satisfy FMOD's read and seek requests immediately in a blocking fashion.
In the open callback, you open your internal file handle and return it to FMOD, along with the file size.
You would have to set all callbacks or file system override would not work. Any callback that is null in the above callback list will cause FMOD to use the default internal system and ignore your callbacks. All callbacks must be set.

With async I/O, there are 2 new callbacks which you can use to replace the 'userread' and 'userseek' callbacks:

    FMOD_FILE_ASYNCREADCALLBACK  userasyncread
    FMOD_FILE_ASYNCCANCELCALLBACK  userasynccancel

If these callbacks are set, the 'userread' and 'userseek' callbacks are made redundant. You can of course keep 'userread' and 'userseek' defined if you want to switch between the 2 systems for some reason, but when 'userasyncread' is defined, the normal read/seek callbacks will never be called.

### Defining the basics - opening and closing the file handle.

Before we start, we'll just define the open and close callback. A very simple implementation using stdio is provided below:

    FMOD_RESULT F_CALLBACK myopen(const char *name, unsigned int *filesize, void **handle, void **userdata)
    {
        if (name)
        {
            FILE *fp;

            fp = fopen(name, "rb");
            if (!fp)
            {
                return FMOD_ERR_FILE_NOTFOUND;
            }

            fseek(fp, 0, SEEK_END);
            *filesize = ftell(fp);
            fseek(fp, 0, SEEK_SET);

            *userdata = (void *)0x12345678;
            *handle = fp;
        }

        return FMOD_OK;
    }

    FMOD_RESULT F_CALLBACK myclose(void *handle, void *userdata)
    {
        if (!handle)
        {
            return FMOD_ERR_INVALID_PARAM;
        }

        fclose((FILE *)handle);

        return FMOD_OK;
    }

### Defining 'userasyncread'

The idea for asynchronous reading, is that FMOD will request data (note, possibly from any thread - so be wary of thread safety in your code!), but you don't have to give the data to FMOD immediately. You can return from the callback without giving FMOD any data. This is deferred I/O.

For example, here is a definition of an async read callback:

    FMOD_RESULT F_CALLBACK myasyncread(FMOD_ASYNCREADINFO *info, void *userdata)
    {
        return PutReadRequestOntoQueue(info);
    }

Note that we didnt actually do any read here. You can return immediately and FMOD will internally wait until the read request is satisfied. Note that if FMOD decides to wait from the main thread (which it will do often), then you cannot satisfy the queue from the main thread, you will get a deadlock. Just put the request onto a queue. We'll discuss how to let FMOD know that the data is ready in the next section.

There are a few things to consider here:

- The callback could come from any thread inside FMOD's system. Usually this means FMOD's streaming thread, FMOD's file I/O thread, the main thread, or the FMOD_NONBLOCKING thread. Be thread safe! Use criticalsections around linked list/queue operations to avoid corruption of data.
- Return code. This is usually a fatal, non disk related error such as not being able to add to the queue. This could be an out of memory error for example. Use FMOD_ERR_MEMORY as the return value if this is the case. Return FMOD_OK in normal cases. It normally won't be a return code related to a disk error. You have to set the 'result' code in the FMOD_ASYNCREADINFO structure to let FMOD know about a file based error.
- Be wary that your queued command may need to be cancelled if the user decides to release the FMOD resource that is using that file, such as a sound. See the next section about myasynccancel in that case.
- The FMOD_ASYNCREADINFO structure is where you fill in the data requested by FMOD. See below for a more detailed description of this structure and what is required to complete the read.

### Defining 'userasynccancel'

If you have queued up a lot of read requests, and have not satisfied them yet, then it is possible that the user may want to release a sound before the request has been fulfilled (ie Sound::release is called).
In that case FMOD will call the async cancel callback to let you cancel any operations you may have pending, that are related to this file.

    FMOD_RESULT F_CALLBACK myasynccancel(void *handle, void *userdata)
    {
        return SearchQueueForFileHandleAndRemove(info);
    }

Note that the above callback implementation will search through our internal linked list (in a thread safe fashion), removing any requests from the queue so that they don't get processed after the Sound is released. If it is in the middle of reading, then the callback will wait until the read is finished and then return.
Do not return while a read is happening, or before a read happens, as the memory for the read destination will be freed and the deferred read will read into an invalid pointer.

### Filling out the FMOD_ASYNCREADINFO structure when performing a deferred read

The FMOD_ASYNCREADINFO is the structure you will pass to your deferred I/O system, and will be the structure that you read and fill out when fulfilling the requests.

The structure exposes the features of the async read system. These are:

- Priority is supported. FMOD will let the user know if the read is not important, mildly important, or extremely important. This will allow the user to reshuffle the queue to make important reads happen before non important reads.
- Read completion is signalled by simply setting the 'result' code of FMOD_ASYNCREADINFO.
- Memory does not need to be copied anywhere, you can read directly into FMOD's pointers which point directly to the internal file buffers.
- You do not have to give FMOD all of the data, you can give a partial read result to the callback and FMOD will most likely just issue another read request later with a smaller byte value.

    typedef struct {
      void *  handle;
      unsigned int  offset;
      unsigned int  sizebytes;
      int  priority;
      void *  buffer;
      unsigned int  bytesread;
      FMOD_RESULT  result;
      void *  userdata;
    } FMOD_ASYNCREADINFO;

The first 4 members (handle, offset, sizebytes, priority) are read only values, which tell you about the file handle in question, where in the file it wants to read from (so no seek callbacks required!) and how many bytes it wants. The priority value tells you how important the read is as discussed previously.

The next 3 members (buffer, bytesread and result) are values you will fill in, and to let FMOD know that you have read the data.
Read your file data into buffer. sizebytes is how much you should be reading. bytesread is how much you actually read (this could be less than sizebytes).
If you hit the 'end of file' condition and need to return less bytes than were requested - set bytesread to less than sizebytes, and then set the result to FMOD_ERR_FILE_EOF.

#### Set the result last!

Note! Do not set the result before setting the bytesread value and reading the data into buffer.
The initial value for result, is going to be FMOD_ERR_NOTREADY. When you set the value to FMOD_OK (or appropriate error code) then internally FMOD will immediately see this as an indication to continue, so if the bytesread or buffer contents are not ready, you will get corruption, errors or unexpected behavior.
So to summarize, the last thing you will do before finishing your queue process is to set result. You will not set it before setting bytesread or filling in buffer.

### Threading issues & read priorities

As mentioned earlier in this tutorial, FMOD can call the read callback from various different threads, so it is common sense to protect your I/O system from operations happening simultaneously from different threads.

A system that would use FMOD's async I/O feature would most likely be running in its own thread. This is so the blocking wait loops in FMOD's loading calls are not forever waiting for data because the user can't provide it to FMOD.
If the system runs in another thread, it can detect the queue insert, and process the data while FMOD is waiting.

It is actually possible to complete the read as if it wasn't deferred, and do a direct file read into the buffer and set sizebytes/result values from the FMOD async read callback. This is a possible way to reduce delays for extremely urgent FMOD reads.

Currently there are 3 different categories of read priority.

- 0 = low priority. These reads are usually blocking style reads that come from a user load command, and there are no real negative side effects of delaying the read except that the load function takes longer. These reads are going to be issued from a System::createSound call for example.
- 50 = medium priority. These reads are important, and usually come from the FMOD stream system. They can be delayed, but not for too long. If the delay is too long, then audio streams will starve, and possibly stutter. If you need to delay the read longer, the FMOD file buffer size can be increased with System::setStreamBufferSize
- 100 = high priority. Currently the highest priority read issued by FMOD is when an audio stream loops. It must internally flush the stream buffer after a seek to loop start, and do so before the stream 'decode buffer' (the PCM double-buffer that the stream decoder decodes into) loops around and starts stuttering (this is a different buffer to the previously mentioned stream buffer. That one contains compressed file data. The decode buffer contains decompressed PCM data). The decode buffer is usually small so it is important to get the read done fast, but the user can also increase these buffers with FMOD_CREATESOUNDEXINFO::decodebuffersize. FMOD_ADVANCEDSETTINGS::defaultDecodeBufferSize can also be used to set all future decode buffer sizes for all streams without having to set it every time, and is going to be used for the Event System because decode buffer size is not something you can set for events individually.