2
\$\begingroup\$

I'm making a block game in c++ that stores voxels in a octree and because, at least in my implementation, editing the octree is slow, I'm generating the chunks of land in a separate thread.

I manage the thread with my ThreadManager class. Every frame I loop through the threads and see if they have finished and remove them. When the ThreadManager object goes out of scope(it is the first game object to go out of scope) it waits for every thread to finish in the destructor. My problem is that when O2 or O3 compiler optimizations are turned on(O and O1 are fine) the threads never finish and I have to force quit the program.

I know that making variables volatile can stop the compiler from optimizing away variables used as conditions that are changed in another thread, but that doesn't seem to be the problem here. What fixes it is printing something in the loop of the generateChunk function or making the height value a constant instead of using Worldgenerator::getTerrainHeight() (see below) Could anyone think of a way to make sure that I can keep the loops from getting the call to Worldgenerator::getTerrainHeight() from being "optimized" this way without using O or O1? (without at least O2 the chunk generation is quite slow and I'd not like to remove possibility of further increasing speed everywhere else for the sake of this one part)

ThreadManager:

class ThreadManager{
public:

    ~ThreadManager(){
        std::cout << "destroying threads\n";
        for(int i = 0;i < threads.size();i++){
            std::cout << i << "\n"; //never makes it past index 0 with O2 or O3 flags
            threads[i].get();
        }
        std::cout << "threads destroyed\n";
    };

    void addThread(std::future<void> newThread){
        threads.push_back(std::move(newThread));
    }

    void pollThreads(){//called every frame
        for(int i = 0;i < threads.size();i++){
            if(threads[i].wait_for(std::chrono::milliseconds(0)) != std::future_status::timeout){
                threads[i].get();
                threads.erase(threads.begin()+i);
                i--;
            }
        }
    }

    int getNumThreads(){
        return threads.size();
    }

private:
    std::vector< std::future<void> > threads;

};

This is the function executed in a another thread:

void Game::generateChunk(Chunk& chunk){
    //std::cout << "generate\n";
    Octree& tree = chunk.getTree();

    for(double x = tree.getX();x < tree.getX()+CHUNK_WIDTH;x+=1.0){

        for(double z = tree.getZ();z < tree.getZ()+CHUNK_WIDTH;z+=1.0){
            //std::cout << "height\n"; when this is uncommented the threads end when .get() is called on it
            double height = worldGenerator.getTerrainHeight(x*100000,z*100000)*40;
            //double height = 40; if this line replaces the preceding line it works as well

            for(double y = tree.getY()+CHUNK_WIDTH-1.0;y >= tree.getY() && y > height;y-= 1.0){
                tree.setBlock(tree.coordsToLoc(x,y,z,1.0), Voxel{BLOCK_TYPE_AIR});
            }

        }
    }

    chunk.generated = true;//std::atomic<bool>

    voxelInstanceDataMutex.lock();

    quickMesh(chunk.getPos(), vulkanApp);

    needsVoxelInstanceBufferUpdate = true;//std::atomic<bool>
    voxelInstanceDataMutex.unlock();
    return;
}

How the threads are created:

//future objects are std::move()ed to put them into the ThreadManager
for(auto newPos : newChunkPos){
    chunks.emplace(newPos, Chunk{newPos,CHUNK_WIDTH});
    Chunk& c = chunks.find(newPos)->second;
    threadManager.addThread(std::async(std::launch::async,&Game::generateChunk, this, std::ref(c)));
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.