新年前最后一篇~
在之前,我写了一篇文章关于自定义的虚拟文件系统,但是这篇文章介绍的文件系统过于复杂,且在实际测试时效率不够高,关键问题在于需要从vblock一层层查找vnode来确定文件位置,而并没有全局的文件路径缓存。因此在研究学习Piccolo过程中,又重新整理了一下这部分的代码。
我先对文件系统需求进行分析。我希望可以从普通文件目录与压缩文件中读取文件内容,且该文件系统的配置可以利用Piccolo的反射系统来读取数据。还有就是可以通过类似追加的方式来给文件系统打补丁,比如对main.zip添加patch.zip的补丁,使文件系统可以读取patch中数据。最后就是希望文件系统的速度比上一个版本快。
最后设计的代码如下,完整代码在这里:
#pragma once
#include "runtime/core/meta/reflection/reflection.h"
#include <vector>
#include <string>
namespace Piccolo
{
REFLECTION_TYPE(FSConfig)
CLASS(FSConfig, Fields)
{
REFLECTION_BODY(FSConfig)
public:
std::string m_vpath;
std::string m_rpath;
std::string m_type;
};
// stroe mount points
REFLECTION_TYPE(VFSConfig)
CLASS(VFSConfig, Fields)
{
REFLECTION_BODY(VFSConfig)
public:
std::vector<FSConfig> m_configs;
};
}
#pragma once
#include "runtime/platform/file_system/basic/file.h"
#include "runtime/platform/file_system/basic/file_system.h"
#include "runtime/platform/file_system/vfs_config.h"
#include <unordered_map>
namespace Piccolo
{
class VFS
{
public:
void mount(const VFSConfig& config);
void unmountAll();
FilePtr open(const std::string& vpath, uint32_t mode);
bool close(FilePtr file);
size_t read(FilePtr file, std::vector<std::byte>& buffer);
size_t write(FilePtr file, const std::vector<std::byte>& buffer);
std::future<size_t> readAsync(std::shared_ptr<thread_pool> tp, FilePtr file, std::vector<std::byte>& buffer);
std::future<size_t> writeAsync(std::shared_ptr<thread_pool> tp, FilePtr file, const std::vector<std::byte>& buffer);
private:
void mountFS(const FSConfig& fs);
void unmountFS(const FSConfig& fs);
void buildVFSCache();
private:
std::vector<FileSystemPtr> m_fs;
std::unordered_map<std::string, FileSystemPtr> m_fileCache;
std::unordered_map<std::string, FileSystemPtr> m_dirCache;
};
extern VFS g_vfs;
} // namespace Piccolo
#include "runtime/platform/file_system/vfs.h"
#include "runtime/platform/file_system/basic/file_utils.h"
#include "runtime/platform/file_system/memory_file/memory_file.h"
#include "runtime/platform/file_system/memory_file/memory_file_system.h"
#include "runtime/platform/file_system/native_file/native_file.h"
#include "runtime/platform/file_system/native_file/native_file_system.h"
#include "runtime/platform/file_system/zip_file/zip_file.h"
#include "runtime/platform/file_system/zip_file/zip_file_system.h"
#include "runtime/function/global/global_context.h"
#include "runtime/resource/config_manager/config_manager.h"
#include <algorithm>
namespace Piccolo
{
VFS g_vfs;
void VFS::mount(const VFSConfig& config)
{
for (auto& fs : config.m_configs)
{
mountFS(fs);
}
buildVFSCache();
}
void VFS::mountFS(const FSConfig& fs)
{
if (fs.m_type == "native")
{
auto root = g_runtime_global_context.m_config_manager->getRootFolder().string();
auto rpath = combine_path(root, fs.m_rpath);
m_fs.emplace_back(std::make_shared<NativeFileSystem>(fs.m_vpath, rpath));
}
else if (fs.m_type == "memory")
{
m_fs.emplace_back(std::make_shared<MemoryFileSystem>(fs.m_vpath, fs.m_rpath));
}
else if (fs.m_type == "compress-zip")
{
auto root = g_runtime_global_context.m_config_manager->getRootFolder().string();
auto rpath = combine_path(root, fs.m_rpath);
m_fs.emplace_back(std::make_shared<ZipFileSystem>(fs.m_vpath, rpath));
}
}
void VFS::unmountFS(const FSConfig& config)
{
auto iter =
std::find_if(m_fs.begin(), m_fs.end(), [config](FileSystemPtr fs) { return fs->m_rpath == config.m_rpath && fs->m_vpath == config.m_vpath; });
if (iter != m_fs.end())
{
m_fs.erase(iter);
}
}
void VFS::unmountAll()
{
m_fs.clear();
m_fileCache.clear();
m_dirCache.clear();
}
void VFS::buildVFSCache()
{
for (auto fs : m_fs)
{
fs->buildFSCache();
for (auto file : fs->m_vfiles)
{
m_fileCache[file] = fs;
}
for (auto dir : fs->m_vdirs)
{
m_dirCache[dir] = fs;
}
}
}
FilePtr VFS::open(const std::string& vpath, uint32_t mode)
{
auto fs = m_fileCache.find(vpath);
if (fs == m_fileCache.end())
{
return nullptr;
}
else
{
return fs->second->open(vpath, mode);
}
}
bool VFS::close(FilePtr file) { return file->close(); }
size_t VFS::read(FilePtr file, std::vector<std::byte>& buffer) { return file->read(buffer); }
size_t VFS::write(FilePtr file, const std::vector<std::byte>& buffer) { return file->write(buffer); }
std::future<size_t> VFS::readAsync(std::shared_ptr<thread_pool> tp, FilePtr file, std::vector<std::byte>& buffer)
{
return tp->enqueue_task(&VFS::read, this, file, buffer);
}
std::future<size_t> VFS::writeAsync(std::shared_ptr<thread_pool> tp, FilePtr file, const std::vector<std::byte>& buffer)
{
return tp->enqueue_task(&VFS::write, this, file, buffer);
}
} // namespace Piccolo
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
namespace Piccolo
{
using FileBuffer = std::vector<std::byte>;
class FileSystem;
class File
{
public:
enum Mode : uint32_t
{
read_bin = 1 << 0,
write_bin = 1 << 1,
readwrite_bin = read_bin | write_bin,
read_text = 1 << 2,
write_text = 1 << 3,
readwrite_text = read_text | write_text,
append = 1 << 4,
truncate = 1 << 5,
};
enum Origin : uint32_t
{
beg = 0,
end = 1,
set = 2,
};
public:
File(const std::string& vpath, const std::string& rpath) : m_vpath {vpath}, m_rpath {rpath} {}
virtual ~File() = default;
virtual bool open(uint32_t mode) = 0;
virtual bool close() = 0;
virtual bool isOpened() const = 0;
virtual bool isReadOnly() const = 0;
virtual size_t size() const = 0;
virtual size_t seek(size_t offset, Origin origin) = 0;
virtual size_t tell() = 0;
// If Read Text, will read [size - 1] bytes, because last will be ['\0'] for string
virtual size_t read(std::vector<std::byte>& data) = 0;
virtual size_t write(const std::vector<std::byte>& data) = 0;
virtual size_t read(std::string& data) = 0;
virtual size_t write(const std::string& data) = 0;
public:
uint32_t m_mode;
std::string m_vpath;
std::string m_rpath;
std::shared_ptr<FileSystem> m_fs;
};
using FilePtr = std::shared_ptr<File>;
} // namespace Piccolo
#pragma once
#include "runtime/platform/file_system/basic/file.h"
#include "runtime/core/thread/thread_pool.h"
#include <algorithm>
#include <future>
namespace Piccolo
{
class FileSystem
{
public:
FileSystem(const std::string& vpath, const std::string& rpath) : m_vpath {vpath}, m_rpath {rpath} {}
virtual ~FileSystem() = default;
bool isFileExist(const std::string& file_name) const
{
auto iter = std::find_if(m_vfiles.begin(), m_vfiles.end(), [file_name](const std::string& file) { return file_name == file; });
return iter != m_vfiles.end();
}
bool isDirExist(const std::string& dir_name) const
{
auto iter = std::find_if(m_vdirs.begin(), m_vdirs.end(), [dir_name](const std::string& file) { return dir_name == file; });
return iter != m_vdirs.end();
}
size_t read(FilePtr file, std::vector<std::byte>& buffer) { return file->read(buffer); }
size_t write(FilePtr file, const std::vector<std::byte>& buffer) { return file->write(buffer); }
size_t read(FilePtr file, std::string& buffer) { return file->read(buffer); }
size_t write(FilePtr file, const std::string& buffer) { return file->write(buffer); }
std::future<size_t> readAsync(std::shared_ptr<thread_pool> tp, FilePtr file, std::vector<std::byte>& buffer)
{
return tp->enqueue_task([file, &buffer]() { return file->read(buffer); });
}
std::future<size_t> writeAsync(std::shared_ptr<thread_pool> tp, FilePtr file, const std::vector<std::byte>& buffer)
{
return tp->enqueue_task([file, &buffer]() { return file->write(buffer); });
}
std::future<size_t> readAsync(std::shared_ptr<thread_pool> tp, FilePtr file, std::string& buffer)
{
return tp->enqueue_task([file, &buffer]() { return file->read(buffer); });
}
std::future<size_t> writeAsync(std::shared_ptr<thread_pool> tp, FilePtr file, const std::string& buffer)
{
return tp->enqueue_task([file, &buffer]() { return file->write(buffer); });
}
// TODO
// virtual bool createFile(const std::string& file_path) = 0;
// virtual bool deleteFile(const std::string& file_path) = 0;
// virtual bool moveFile(const std::string& src, const std::string& dst) = 0;
// virtual bool copyFile(const std::string& src, const std::string& dst) = 0;
// TODO
// virtual bool createDir(const std::string& dir_path) = 0;
// virtual bool deleteDir(const std::string& dir_path) = 0;
// virtual bool moveDir(const std::string& src, const std::string& dst) = 0;
// virtual bool copyDir(const std::string& src, const std::string& dst) = 0;
// -------------------------------------------------------------------
// -------------------------------------------------------------------
// -------------------------------------------------------------------
virtual void buildFSCache() = 0;
virtual FilePtr open(const std::string& vpath, uint32_t mode) = 0;
virtual bool close(FilePtr file) = 0;
// -------------------------------------------------------------------
// -------------------------------------------------------------------
// -------------------------------------------------------------------
public:
std::string m_vpath;
std::string m_rpath;
std::vector<std::string> m_vfiles;
std::vector<std::string> m_vdirs;
std::vector<std::string> m_rfiles;
std::vector<std::string> m_rdirs;
};
using FileSystemPtr = std::shared_ptr<FileSystem>;
} // namespace Piccolo
在这里vfs使用的是全局变量,根据需求也可以改成单例模式。除此之外,vfs当前只支持读取功能,对写入的支持比较有限,只支持普通文件目录已经存在的文件写入。后续可以考虑升级这个部分的功能,不过由于运行的游戏无需修改游戏资源文件,对于现在的场景来说,基本够用了。