Skip to content

Commit

Permalink
Merge pull request mrkite#168 from EtlamGit/asynchronous-chunk-loading
Browse files Browse the repository at this point in the history
Asynchronous chunk loading & rendering (using Thread Pool)
  • Loading branch information
mrkite authored Aug 5, 2019
2 parents 5c30aea + 1caaae0 commit 7c35a2f
Show file tree
Hide file tree
Showing 13 changed files with 409 additions and 246 deletions.
1 change: 1 addition & 0 deletions chunk.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Chunk : public QObject {
int chunkX;
int chunkZ;
friend class MapView;
friend class ChunkRenderer;
friend class ChunkCache;
friend class WorldSave;
};
Expand Down
53 changes: 38 additions & 15 deletions chunkcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
#include <windows.h>
#endif

ChunkID::ChunkID(int x, int z) : x(x), z(z) {
ChunkID::ChunkID(int cx, int cz) : cx(cx), cz(cz) {
}
bool ChunkID::operator==(const ChunkID &other) const {
return (other.x == x) && (other.z == z);
return (other.cx == cx) && (other.cz == cz);
}
uint qHash(const ChunkID &c) {
return (c.x << 16) ^ (c.z & 0xffff); // safe way to hash a pair of integers
return (c.cx << 16) ^ (c.cz & 0xffff); // safe way to hash a pair of integers
}

ChunkCache::ChunkCache() {
Expand All @@ -36,11 +36,21 @@ ChunkCache::ChunkCache() {
cache.setMaxCost(chunks);
maxcache = 2 * chunks; // most chunks are less than half filled with sections

// determain optimal thread pool size for "loading"
// as this contains disk access, use less than number of cores
int tmax = loaderThreadPool.maxThreadCount();
loaderThreadPool.setMaxThreadCount(tmax / 2);

qRegisterMetaType<QSharedPointer<GeneratedStructure>>("QSharedPointer<GeneratedStructure>");
}

ChunkCache::~ChunkCache() {
loaderThreadPool.waitForDone();
}

ChunkCache& ChunkCache::Instance() {
static ChunkCache singleton;
return singleton;
}

void ChunkCache::clear() {
Expand All @@ -51,16 +61,29 @@ void ChunkCache::clear() {
}

void ChunkCache::setPath(QString path) {
if (this->path != path)
clear();
this->path = path;
}
QString ChunkCache::getPath() {
QString ChunkCache::getPath() const {
return path;
}

Chunk *ChunkCache::fetch(int x, int z) {
ChunkID id(x, z);
Chunk *ChunkCache::fetchCached(int cx, int cz) {
// try to get Chunk from Cache
ChunkID id(cx, cz);
mutex.lock();
Chunk *chunk = cache[id]; // const operation
mutex.unlock();

return chunk;
}

Chunk *ChunkCache::fetch(int cx, int cz) {
// try to get Chunk from Cache
ChunkID id(cx, cz);
mutex.lock();
Chunk *chunk = cache[id];
Chunk *chunk = cache[id]; // const operation
mutex.unlock();
if (chunk != NULL) {
if (chunk->loaded)
Expand All @@ -72,25 +95,25 @@ Chunk *ChunkCache::fetch(int x, int z) {
connect(chunk, SIGNAL(structureFound(QSharedPointer<GeneratedStructure>)),
this, SLOT (routeStructure(QSharedPointer<GeneratedStructure>)));
mutex.lock();
cache.insert(id, chunk);
cache.insert(id, chunk); // non-const operation !
mutex.unlock();
ChunkLoader *loader = new ChunkLoader(path, x, z, cache, &mutex);
ChunkLoader *loader = new ChunkLoader(path, cx, cz);
connect(loader, SIGNAL(loaded(int, int)),
this, SLOT(gotChunk(int, int)));
QThreadPool::globalInstance()->start(loader);
this, SLOT(gotChunk(int, int)));
loaderThreadPool.start(loader);
return NULL;
}

void ChunkCache::gotChunk(int x, int z) {
emit chunkLoaded(x, z);
void ChunkCache::gotChunk(int cx, int cz) {
emit chunkLoaded(cx, cz);
}

void ChunkCache::routeStructure(QSharedPointer<GeneratedStructure> structure) {
emit structureFound(structure);
}

void ChunkCache::adaptCacheToWindow(int x, int y) {
int chunks = ((x + 15) >> 4) * ((y + 15) >> 4); // number of chunks visible
void ChunkCache::adaptCacheToWindow(int wx, int wy) {
int chunks = ((wx + 15) >> 4) * ((wy + 15) >> 4); // number of chunks visible
chunks *= 1.10; // add 10%
cache.setMaxCost(qMin(chunks, maxcache));
}
32 changes: 21 additions & 11 deletions chunkcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,50 @@

class ChunkID {
public:
ChunkID(int x, int z);
ChunkID(int cx, int cz);
bool operator==(const ChunkID &) const;
friend uint qHash(const ChunkID &);
protected:
int x, z;
int cx, cz;
};

class ChunkCache : public QObject {
Q_OBJECT

public:
// singleton: access to global usable instance
static ChunkCache &Instance();
private:
// singleton: prevent access to constructor and copyconstructor
ChunkCache();
~ChunkCache();
ChunkCache(const ChunkCache &);
ChunkCache &operator=(const ChunkCache &);

public:
void clear();
void setPath(QString path);
QString getPath();
Chunk *fetch(int x, int z);
QString getPath() const;
Chunk *fetch(int cx, int cz); // fetch Chunk and load when not found
Chunk *fetchCached(int cx, int cz); // fetch Chunk only if cached

signals:
void chunkLoaded(int x, int z);
void chunkLoaded(int cx, int cz);
void structureFound(QSharedPointer<GeneratedStructure> structure);

public slots:
void adaptCacheToWindow(int x, int y);
void adaptCacheToWindow(int wx, int wy);

private slots:
void gotChunk(int x, int z);
void gotChunk(int cx, int cz);
void routeStructure(QSharedPointer<GeneratedStructure> structure);

private:
QString path;
QCache<ChunkID, Chunk> cache;
QMutex mutex;
int maxcache;
QString path; // path to folder with region files
QCache<ChunkID, Chunk> cache; // real Cache
QMutex mutex; // Mutex for accessing the Cache
int maxcache; // number of Chunks that fit into Cache
QThreadPool loaderThreadPool; // extra thread pool for loading
};

#endif // CHUNKCACHE_H_
42 changes: 22 additions & 20 deletions chunkloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,55 @@
#include "./chunkcache.h"
#include "./chunk.h"

ChunkLoader::ChunkLoader(QString path, int x, int z,
const QCache<ChunkID, Chunk> &cache,
QMutex *mutex) : path(path), x(x), z(z),
cache(cache), mutex(mutex) {
}
ChunkLoader::~ChunkLoader() {
}
ChunkLoader::ChunkLoader(QString path, int cx, int cz)
: path(path)
, cx(cx), cz(cz)
, cache(ChunkCache::Instance())
{}
ChunkLoader::~ChunkLoader()
{}

void ChunkLoader::run() {
int rx = x >> 5;
int rz = z >> 5;
// get coordinates of Region file
int rx = cx >> 5;
int rz = cz >> 5;

QFile f(path + "/region/r." + QString::number(rx) + "." +
QString::number(rz) + ".mca");
if (!f.open(QIODevice::ReadOnly)) { // no chunks in this region
emit loaded(x, z);
emit loaded(cx, cz);
return;
}
// map header into memory
uchar *header = f.map(0, 4096);
int offset = 4 * ((x & 31) + (z & 31) * 32);
int offset = 4 * ((cx & 31) + (cz & 31) * 32);
int coffset = (header[offset] << 16) | (header[offset + 1] << 8) |
header[offset + 2];
int numSectors = header[offset+3];
f.unmap(header);

if (coffset == 0) { // no chunk
f.close();
emit loaded(x, z);
emit loaded(cx, cz);
return;
}

uchar *raw = f.map(coffset * 4096, numSectors * 4096);
if (raw == NULL) {
f.close();
emit loaded(x, z);
emit loaded(cx, cz);
return;
}
NBT nbt(raw);
ChunkID id(x, z);
mutex->lock();
Chunk *chunk = cache[id];
if (chunk)
// get existing Chunk entry from Cache
Chunk *chunk = cache.fetchCached(cx, cz);
// parse Chunk data
// Chunk will be flagged "loaded" in a thread save way
if (chunk) {
NBT nbt(raw);
chunk->load(nbt);
mutex->unlock();
}
f.unmap(raw);
f.close();

emit loaded(x, z);
emit loaded(cx, cz);
}
17 changes: 8 additions & 9 deletions chunkloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,25 @@

#include <QObject>
#include <QRunnable>
class Chunk;
class ChunkID;
class QMutex;
#include "chunkcache.h"

class ChunkLoader : public QObject, public QRunnable {
Q_OBJECT

public:
ChunkLoader(QString path, int x, int z, const QCache<ChunkID, Chunk> &cache,
QMutex *mutex);
ChunkLoader(QString path, int cx, int cz);
~ChunkLoader();

signals:
void loaded(int x, int z);
void loaded(int cx, int cz);

protected:
void run();

private:
QString path;
int x, z;
const QCache<ChunkID, Chunk> &cache;
QMutex *mutex;
int cx, cz;
ChunkCache &cache;
};

#endif // CHUNKLOADER_H_
Loading

0 comments on commit 7c35a2f

Please sign in to comment.