Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Using std::futures for creating chunks  (Read 1405 times)

0 Members and 1 Guest are viewing this topic.

stubbie

  • Newbie
  • *
  • Posts: 5
    • View Profile
Using std::futures for creating chunks
« on: July 21, 2024, 06:50:54 pm »
I am currently working on creating an isometric terrain generation program and was wondering if it would be possible to optimize my code by using futures when creating chunks. The chunks get created and destroyed pretty often, so allowing this to run in parallel would optimize my code by a ton. I also modify the blocks in the individual chunks, so being able to split this process up into multiple threads would render be big performance gains. I just cannot figure out how to actually put this into code. What I have been trying to do is using these data structures, where chunks to load are the chunks created asynchronously to be merged with chunks, chunks is the map of all the chunks currently visible, and futures are just a collection of futures I used to create the chunks:
    std::map<std::pair<int,int>, Chunk> chunks;
    std::map<std::pair<int,int>, Chunk> chunksToLoad; //used threads
    std::vector<std::future<void>> futures;
Then I attempted to create the chunks and update them in these two functions:
 static std::mutex mtx;
static void generateChunk(int chunkX, int chunkY, std::map<std::pair<int,int>, Chunk> *chunkToLoad,
    const siv::PerlinNoise &perlin, int octaves, float persistence, float frequency) {
    sf::Vector2i blockCoords(chunkX * Chunks::size, chunkY * Chunks::size);
    Chunk chunk;
    chunk.setVisibleBlocks(blockCoords, perlin, octaves, persistence, frequency);
    std::lock_guard<std::mutex> lock(mtx);
    chunkToLoad->insert({{blockCoords.x, blockCoords.y}, chunk});
}

void chunkManager::updateChunks() {
  for(int i = -Manage::renderDistance; i <= Manage::renderDistance; ++i){
    for(int j = -Manage::renderDistance; j <= Manage::renderDistance; ++j){
      futures.push_back(std::async(std::launch::async, generateChunk, chunkPosition.x + i, chunkPosition.y + j,
          &chunkToLoad, perlin, octaves, persistence, frequency));
    }
  }
  for(auto &pair : chunkToLoad){
    if(chunks.find(pair.first) == chunks.end()){
      chunks[pair.first] = pair.second;
    }
  }
  chunkToLoad.clear();
  futures.clear();
}
 
This whole process proves to be super slow for creating the world, much slower than when I run just on one thread. Does anyone have any advice for how I could take a better approach at creating these chunks in parallel? Is it even possible in this case? Thank you very much to anyone who can help.

fallahn

  • Hero Member
  • *****
  • Posts: 504
  • Buns.
    • View Profile
    • Trederia
Re: Using std::futures for creating chunks
« Reply #1 on: July 23, 2024, 08:43:51 am »
Without running your code I can't say for sure, but I'm guessing the bottleneck is the single shared mutex. What is probably happening is that all the worker threads finish at more or less the same time, but then have to queue as each one waits for the mutex to lock and unlock for each thread.

A possible solution would be to have each worker thread have its own mutex then the main thread can check and gather results from finished threads by locking each of those individually - that way none of the other threads are interacting with each other, just the main one.

std::future is possibly also not the magic bullet you're looking for here, and you may have to get your hands dirty with threads directly - although there is the std::execution policy in C++17 (except on Apple clang, where it's not mplemented) https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t which allows running stl algorithms in parallel.

Overall this is more generally called (or is a variant of) the producer-consumer problem: https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem - which is worth reading up on and might help you find a more suitable solution. HTH 😁