godot_voxel/util/file_locker.h

99 lines
2.0 KiB
C
Raw Normal View History

2022-04-11 02:10:44 +01:00
#ifndef ZN_FILE_LOCKER_H
#define ZN_FILE_LOCKER_H
#include "errors.h"
#include <core/io/file_access.h>
#include <core/os/mutex.h>
#include <core/os/rw_lock.h>
#include <core/templates/hash_map.h>
namespace zylann {
// Performs software locking on paths,
// so that multiple threads (controlled by this module) wanting to access the same file will lock a shared mutex.
class FileLocker {
public:
~FileLocker() {
const String *key = nullptr;
while ((key = _files.next(key))) {
File *f = _files[*key];
memdelete(f);
}
}
void lock_read(String fpath) {
lock(fpath, true);
}
void lock_write(String fpath) {
lock(fpath, false);
}
void unlock(String fpath) {
unlock_internal(fpath);
}
private:
struct File {
RWLock lock;
bool read_only;
};
void lock(String fpath, bool read_only) {
File *fp = nullptr;
{
MutexLock lock(_files_mutex);
File **fpp = _files.getptr(fpath);
if (fpp == nullptr) {
fp = memnew(File);
_files.set(fpath, fp);
} else {
fp = *fpp;
}
}
if (read_only) {
fp->lock.read_lock();
// The read lock was acquired. It means nobody is writing.
fp->read_only = true;
} else {
fp->lock.write_lock();
// The write lock was acquired. It means only one thread is writing.
fp->read_only = false;
}
}
void unlock_internal(String fpath) {
File *fp = nullptr;
// I assume `get_path` returns the same string that was used to open it
{
MutexLock lock(_files_mutex);
File **fpp = _files.getptr(fpath);
if (fpp != nullptr) {
fp = *fpp;
}
}
2022-04-11 02:10:44 +01:00
ZN_ASSERT_RETURN(fp != nullptr);
// TODO FileAccess::reopen can have been called, nullifying my efforts to enforce thread sync :|
// So for now please don't do that
if (fp->read_only) {
fp->lock.read_unlock();
} else {
fp->lock.write_unlock();
}
}
private:
Mutex _files_mutex;
// Had to use dynamic allocs because HashMap does not implement move semantics
HashMap<String, File *> _files;
};
} // namespace zylann
2022-04-11 02:10:44 +01:00
#endif // ZN_FILE_LOCKER_H