Search code examples
c#multithreadingopenglopentk

Objects added to Dictionary by different thread not rendered by OpenTK


I am currently working on a minecraft clone, and I am currently trying to implement multithreading which I have never worked with (I've read some of the docs by Microsoft but that's it) to generate chunks without making the game hang for a second.

My World class stores a Dictionary (System.Collections.Generic) of Chunks which each store a 3d array of block ids. In my Update method I add the new chunks to be generated to a queue (working as intended) and I have a different thread that is supposed to work down the queue and generate the chunks one by one:

(In World class:)

Dictionary<Vector2, Chunk> chunks = new Dictionary<Vector2, Chunk>();
Queue<Vector2> chunksToCreate = new Queue<Vector2>();

public World(Chunk.ChunkGen generator, int program, int seed) {
    generatorFunc = generator;
    Program = program;
    Seed = seed;
    chunksCreator = new Thread(CreateChunks);
    chunksCreator.Start();
}

private void CreateChunks() {
    while (true) {
        if (chunksToCreate.Count == 0)
            continue;
        Vector2 vec = chunksToCreate.First();
        chunks[vec] = generatorFunc(this, (int)vec.X, (int)vec.Y);
        chunksToCreate.TryDequeue(out Vector2 outVec);
    }
}

My problem is that, when I create chunks simply by calling the generatorFunc (delegate) they are added to the dictionary and rendered by opentk as expected, but when I create with this other thread, they also get added to the dictionary and in the VS Debugger everything inside the objects looks correct, BUT they aren't rendered at all.

I have already tried using ConcurrentQueue and ConcurrentDictionary but with no changes.

I'd really appreciate it if someone would help me with this as I am completely new to all this multithreading stuff and all the articles and books seem really overwhelming.


Solution

  • The UI does not like graphics objects created in other threads. E.g. if your chunks contain brushes, they might not work in the UI thread if they were created in a working thread.

    In your case it makes no difference whether you are using the normal or the thread-safe collections, because you created only one thread. To experience a speed gain would have to create and run several threads. Using thread-safe collections would be crucial in that case and the CreateChunks method would have to look like

    private void CreateChunks() {
        while (chunksToCreate.TryDequeue(out Vector2 vec)) {
            chunks.TryAdd(vec, generatorFunc(this, (int)vec.X, (int)vec.Y));
        }
    }
    

    But creating the right number of threads and managing them yourself can be avoided by using the Task Parallel Library (TPL).

    Parallel.ForEach(chunksToCreate, vec =>
        chunks.TryAdd(vec, generatorFunc(this, (int)vec.X, (int)vec.Y))
    );
    

    Note that the source chunksToCreate needs not be thread safe, since Parallel.ForEach assigns the items to the threads before starting them; however, the dictionary chunks must, as it will be accessed by multiple threads in parallel.


    ConcurrentDictionary<Vector2, Chunk> chunks = new ConcurrentDictionary<Vector2, Chunk>();
    List<Vector2> chunksToCreate = new List<Vector2>();
    
    public World(Chunk.ChunkGen generator, int program, int seed) {
        Program = program;
        Seed = seed;
        Parallel.ForEach(chunksToCreate, vec =>
            chunks.TryAdd(vec, generator(this, (int)vec.X, (int)vec.Y))
        );
    }