Restructuring Data

In the original implementation, we just had one set of data that we worked on. That was the data upon which we did our three interactions, which (to recap) were:

  1. Systems sample data from the field.
  2. Agents add data to the field.
  3. The propagation (or flow update) occurs.

We will now split our data up into 3 sets of data that correspond to the actions:

  1. Results of the algorithm (used for sampling).
  2. Pending changes (used for adding).
  3. Working data (various data used for the propagation).

Results

This is snapshot of the state of your data, i.e. the flow field. Both the main thread and the child keep their own versions. The child thread will update its version of the data, which is then copied into the main thread every frame.

Important: Use value types or perform deep copies for both the results and the pending changes. Copying references to objects in the world is not OK, unless you can guarantee their data will not change.

Pending Changes

The main thread does not write to its copy of the data, as this would just be overwritten during the copy operation. Instead, it generates a list of pending changes, to be given to the child thread. This will require some changes, if you are used to modifying the data on the fly, but it should be easy to identify the minimal data you need for your changes.

For the flow field, the changes ends up being a list of “liquid applied” by each agent. What your changes are is very application-specific. It will be all the data you need from the world for your algorithm to update correctly, as the child thread will not read from world data.

Working Data

This is going to be some combination of results, pending changes, and any other state required for producing the results.

Update Order

At this point I won’t get into the specifics of what the data structures looks like, because that is application-specific, but the order of operations in the main thread Update() is now:

    void Update()
    {
        MainThreadWait.WaitOne();
        MainThreadWait.Reset();

        // Copy Results out of the thread
        // Copy pending changes into the thread

        ChildThreadWait.Set();
    }

Which gives a more complicated thread diagram:

Remember that the time axis is not to scale and your copy operations should be extremely fast. I use the main thread to do the copying in and out of child thread, because it’s easy to know that won’t cause any conflicts and it allows the main thread to act as a master, controlling the key sync operations.

In general, we don’t expect the flow update to take longer than the main thread, we just artificially extended it as a thought experiment. But, if it does, we no longer have any conflicts between the threads. The child thread is only reading and writing from it’s own data, and the two threads are properly synchronised. We are now multithreaded and thread-safe!

Typically, if the flow update is quick, we should have each frame looking like the following:

Simple Multithreading for Unity

Return to Blog