FileSystem specializations for Disk & VFS operations.

This commit is contained in:
Curle 2023-03-12 16:44:10 +00:00
parent 5e60726a2b
commit b723a0778f
7 changed files with 362 additions and 19 deletions

View File

@ -0,0 +1,244 @@
#include <fs/file.h>
#include <vector>
#include "management/synchronization.h"
#include "../../../../../../cmake-build-debug/_deps/spdlog-src/include/spdlog/spdlog.h"
#include <filesystem>
#include <fs/path.h>
#include <map>
namespace ShadowEngine {
/**
* An async operation to be performed.
* For reading files from disk into memory.
*/
struct AsyncRead {
enum class Flags : uint32_t {
FAILED = 0, // The read failed due to some error.
CANCELLED // The read was cancelled due to the resource not being needed any more.
};
AsyncRead() : data() {}
bool isFailed() const { return flags == Flags::FAILED; }
bool isCancelled() const { return flags == Flags::CANCELLED; }
FileSystem::ContentCallback callback;
OutputMemoryStream data;
std::string path;
uint32_t id = 0;
Flags flags;
};
// The FileSystem that operates on raw on-disk files.
struct DiskFS;
struct DiskFS : FileSystem {
explicit DiskFS(std::string& path) : sem(0, 0xffff) {
setBasePath(path);
}
bool hasWork() override {
return workCounter != 0;
}
std::string const& getBasePath() const override { return basePath; }
void setBasePath(std::string& path) final {
basePath = Path::normalise(path);
if (!basePath.ends_with('/') && !basePath.ends_with('\\'))
basePath.append("/");
}
bool saveSync(const Path& path, const uint8_t* data, const size_t size) override {
FileOutput file;
std::string fullPath(basePath.append(path.c_str()));
if (!file.open(fullPath)) return false;
bool res = file.write(data, size);
file.close();
return res;
}
bool readSync(const Path& path, struct OutputMemoryStream& content) override {
FileInput file;
std::string fullPath(basePath.append(path.c_str()));
if (!file.open(fullPath)) return false;
content.resize(file.size());
if (!file.read(content.dataMut(), content.size())) {
file.close();
return false;
}
file.close();
return true;
}
AsyncHandle readAsync(const Path& file, const ContentCallback& callback) override {
if (!file.isEmpty()) return AsyncHandle::invalid();
MutexGuard lock(mutex);
workCounter++;
AsyncRead& read = queue.emplace_back();
if (++lastID == 0) lastID++;
read.id = lastID;
read.path = file.c_str();
read.callback = callback;
sem.raise();
return AsyncHandle(read.id);
}
void cancelAsync(AsyncHandle& handle) override {
MutexGuard lock(mutex);
for (AsyncRead& read : queue) {
if (read.id == handle.value) {
read.flags = AsyncRead::Flags::CANCELLED;
workCounter--;
return;
}
}
for (AsyncRead& read : finished) {
if (read.id == handle.value) {
read.flags = AsyncRead::Flags::CANCELLED;
return;
}
}
}
bool open(std::string& path, FileInput& file) override {
return file.open(basePath.append(path));
}
bool open(std::string& path, FileOutput& file) override {
return file.open(basePath.append(path));
}
bool deleteFile(std::string& path) override {
return std::remove((basePath.append(path).c_str()));
}
bool moveFile(std::string& from, std::string& to) override {
try {
std::rename(basePath.append(from).c_str(), basePath.append(to).c_str());
} catch (std::filesystem::filesystem_error& e) {
return false;
}
return true;
}
bool copyFile(std::string& from, std::string& to) override {
try {
std::filesystem::copy(basePath.append(from).c_str(), basePath.append(to).c_str());
} catch (std::filesystem::filesystem_error& e) {
return false;
}
return true;
}
bool fileExists(std::string& path) override {
return std::filesystem::exists(path);
}
size_t getLastModified(std::string& path) override {
return std::filesystem::last_write_time(path).time_since_epoch().count();
}
// TODO: File iterators
void processCallbacks() override {
// TODO: Timeout this function!
for (;;) {
mutex.enter();
if (finished.empty() || workCounter == 0) {
mutex.exit();
break;
}
AsyncRead item = finished[0];
finished.erase(finished.begin());
--workCounter;
mutex.exit();
if (!item.isCancelled())
item.callback.invoke(item.data.size(), (const uint8_t*) item.data.data(), !item.isFailed());
}
}
// TODO: Task Management
std::string basePath;
std::vector<AsyncRead> queue;
uint64_t workCounter;
std::vector<AsyncRead> finished;
Mutex mutex;
Semaphore sem;
uint32_t lastID;
};
struct VFS : DiskFS {
VFS(std::string& root_pack_path) : DiskFS((std::string &) "vfs:/") {
if (!pack.open(root_pack_path)) {
spdlog::error("Unable to open " + root_pack_path + ", please check paths");
return;
}
const auto count = pack.read<size_t>();
for (size_t i = 0; i < count; i++) {
const auto hash = pack.read<PathHash>();
PackFile& file = packFiles[hash];
file.offset = pack.read<size_t>();
file.size = pack.read<size_t>();
}
}
~VFS() { pack.close(); }
bool readSync(const Path& path, OutputMemoryStream& content) override {
std::string basename = Path::getFilename(const_cast<std::string &>(path.get()));
PathHash hash = path.getHash();
auto i = packFiles.find(hash);
if (i == packFiles.end()) return false;
content.resize(i->second.size);
MutexGuard lock(mutex);
const size_t headerSize = sizeof(uint32_t) + packFiles.size() * (3 * sizeof(size_t));
if (pack.seek(i->second.offset + headerSize) || !pack.read(content.dataMut(), content.size())) {
spdlog::error("Could not read file " + path.get() + " from the pack file.");
return false;
}
return true;
}
struct PackFile {
size_t offset;
size_t size;
};
std::map<PathHash, PackFile> packFiles;
FileInput pack;
};
std::unique_ptr<FileSystem> FileSystem::createDiskFS(std::string &basePath) {
return std::make_unique<DiskFS>(basePath);
}
std::unique_ptr<FileSystem> FileSystem::createVFS(std::string& basePath) {
return std::make_unique<VFS>(basePath);
}
}

View File

@ -1,7 +1,11 @@
#pragma once
#include <fs/iostream.h>
#include <fs/path.h>
#include <management/delegate.h>
#include <memory>
template <class T> struct Delegate;
namespace ShadowEngine {
// An input stream that can read a file on disk.
@ -48,7 +52,6 @@ namespace ShadowEngine {
std::string filename;
};
template <class T> struct Delegate;
/**
* A generic Filesystem API.
@ -68,9 +71,9 @@ namespace ShadowEngine {
};
// Create a Filesystem that interacts with files on disk.
static std::unique_ptr<FileSystem>& createDiskFS(std::string& basePath);
static std::unique_ptr<FileSystem> createDiskFS(std::string& basePath);
// Create a Virtual Filesystem based on the given path.
static std::unique_ptr<FileSystem>& createVFS(std::string& basePath);
static std::unique_ptr<FileSystem> createVFS(std::string& basePath);
virtual ~FileSystem() {}
@ -90,7 +93,7 @@ namespace ShadowEngine {
virtual bool deleteFile(std::string& path) = 0;
// Get the path that this FileSystem originates at. The default is "/" for VFS, and whatever the Executable Path is for Disk FS.
virtual std::string& getBasePath() = 0;
virtual std::string const& getBasePath() const = 0;
// Set a new base path for the FileSystem. Any operations involving file paths will be relative to this new path.
virtual void setBasePath(std::string& path) = 0;
@ -100,9 +103,9 @@ namespace ShadowEngine {
virtual bool hasWork() = 0;
// Write new content to a file synchronously. The thread will be blocked when doing this.
virtual bool saveSync(const struct Path& file, const uint8_t* content) = 0;
virtual bool saveSync(const Path& file, const uint8_t* content, const size_t size) = 0;
// Read content from a file synchronously. The thread will be blocked when doing this.
virtual bool readSync(const struct Path& file, struct OutputMemoryStream& content) = 0;
virtual bool readSync(const Path& file, struct OutputMemoryStream& content) = 0;
// Read a file asynchronously. The given callback will be called with the file content once it is available.
virtual AsyncHandle readAsync(const Path& file, const ContentCallback& callback) = 0;

View File

@ -59,14 +59,14 @@ namespace ShadowEngine {
struct StableHash {
static StableHash fromLong(size_t data);
StableHash() = default;
StableHash(std::string& str);
explicit StableHash(std::string& str);
StableHash(const void* data, uint32_t length);
bool operator!= (StableHash& other) const { return hash != other.hash; }
bool operator== (StableHash& other) const { return hash == other.hash; }
bool operator< (StableHash& other) const { return hash < other.hash; }
bool operator!= (const StableHash& other) const { return hash != other.hash; }
bool operator== (const StableHash& other) const { return hash == other.hash; }
bool operator< (const StableHash& other) const { return hash < other.hash; }
size_t getHash() const { return hash; }
[[nodiscard]] size_t getHash() const { return hash; }
private:
size_t hash = 0;

View File

@ -34,13 +34,14 @@ namespace ShadowEngine {
// A custom OutputStream that writes to memory.
struct OutputMemoryStream final : OutputStream {
OutputMemoryStream();
OutputMemoryStream(void* data, size_t size);
OutputMemoryStream(OutputMemoryStream&& str) noexcept;
OutputMemoryStream(const OutputMemoryStream& rhs) noexcept;
~OutputMemoryStream();
void operator= (const OutputMemoryStream& rhs) noexcept;
void operator= (OutputMemoryStream&& rhs) noexcept;
OutputMemoryStream& operator= (const OutputMemoryStream& rhs) noexcept;
OutputMemoryStream& operator= (OutputMemoryStream&& rhs) noexcept;
uint8_t operator[] (size_t index) const;
uint8_t& operator[] (size_t index);
@ -79,6 +80,7 @@ namespace ShadowEngine {
write(&v, sizeof(v));
}
// A custom InputStream that writes from memory.
struct InputMemoryStream final : InputStream {
InputMemoryStream(const void* data, size_t size);
explicit InputMemoryStream(const OutputMemoryStream& blob);

View File

@ -20,8 +20,9 @@ namespace ShadowEngine {
path = str;
}
void Path::operator=(const std::string &rhs) {
Path& Path::operator=(const std::string &rhs) {
set(rhs);
return *this;
}
bool Path::operator==(const std::string &rhs) {

View File

@ -47,7 +47,7 @@ namespace ShadowEngine {
Path();
explicit Path(const std::string& str);
void operator=(const std::string& rhs);
Path& operator=(const std::string& rhs);
bool operator==(const std::string& rhs);
bool operator==(const Path& rhs);
bool operator!=(const Path& rhs);
@ -55,10 +55,11 @@ namespace ShadowEngine {
// Use this to set a new value into the path; it handles the hash too.
void set(const std::string& path);
uint32_t length() const { return path.length(); };
PathHash getHash() { return hash; }
const char* c_str() const { return path.data(); }
bool isEmpty() const { return path.length() == 0; }
[[nodiscard]] uint32_t length() const { return path.length(); };
[[nodiscard]] PathHash getHash() const { return hash; }
[[nodiscard]] const char* c_str() const { return path.data(); }
[[nodiscard]] std::string const& get() const { return path; }
[[nodiscard]] bool isEmpty() const { return path.length() == 0; }
private:
std::string path;
PathHash hash;

View File

@ -0,0 +1,92 @@
#pragma once
namespace ShadowEngine {
template <typename T> struct Delegate;
/**
* A simple, generic callback template.
* A Delegate takes the form of a std::function that can be called, redirected and bound freely.
* Advantages over std::function includes the ability to reference types that aren't yet fully qualified.
*
* Idea taken from https://blog.molecular-matters.com/2011/09/19/generic-type-safe-delegates-and-events-in-c/
*
* @tparam R the return type
* @tparam Args the arguments to the function
*/
template <typename R, typename... Args> struct Delegate<R(Args...)> {
private:
using InstancePtr = void*;
using InternalFunction = R (*)(InstancePtr, Args...);
struct Stub {
InstancePtr first;
InternalFunction second;
};
template <R (*Function)(Args...)> static R FunctionStub(InstancePtr, Args... args) {
return (Function)(args...);
}
template <typename C, R(C::*Function)(Args...)>
static R ClassMethodStub(InstancePtr instance, Args... args) {
return (static_cast<C*>(instance)->*Function)(args...);
}
template <typename C, R(C::*Function)(Args...) const>
static R ClassMethodStub(InstancePtr instance, Args... args) {
return (static_cast<C*>(instance)->*Function)(args...);
}
public:
Delegate() {
m_stub.first = nullptr;
m_stub.second = nullptr;
}
template <typename T>
Delegate(const T& obj) {
m_stub.first = (InstancePtr)&obj;
m_stub.second = [](InstancePtr inst, Args... args) -> R {
const T& obj = *(const T*)inst;
return obj(args...);
};
}
bool isValid() { return m_stub.second != nullptr; }
template <R (*Function)(Args...)> void bind() {
m_stub.first = nullptr;
m_stub.second = &FunctionStub<Function>;
}
template <auto F, typename C> void bind(C* instance) {
m_stub.first = instance;
m_stub.second = &ClassMethodStub<C, F>;
}
R invoke(Args... args) const {
return m_stub.second(m_stub.first, args...);
}
bool operator==(const Delegate<R(Args...)>& rhs) {
return m_stub.first == rhs.m_stub.first && m_stub.second == rhs.m_stub.second;
}
private:
Stub m_stub;
};
template <typename T> struct ToDelegate_T;
template <typename R, typename C, typename... Args> struct ToDelegate_T<R (C::*)(Args...)> {
using Type = Delegate<R (Args...)>;
};
template <typename T> using ToDelegate = typename ToDelegate_T<T>::Type;
template <auto M, typename C>
auto makeDelegate(C* inst) {
ToDelegate<decltype(M)> res;
res.template bind<M, C>(inst);
return res;
};
}