Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ZEngine/ZEngine/Core/Containers/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,33 @@ namespace ZEngine::Core::Containers
return back();
}

void insert(size_type index, const T& value)
{
ZENGINE_VALIDATE_ASSERT(index <= m_size, "Index out of range")
if (m_size == m_capacity)
{
size_type new_capacity = m_capacity == 0 ? 4 : m_capacity * 2;
reserve(new_capacity);
}
for (size_type i = m_size; i > index; --i)
{
m_data[i] = std::move(m_data[i - 1]);
}
m_data[index] = value;
++m_size;
}

// Remove the element at `index`, shifting later elements one slot left.
void erase(size_type index)
{
ZENGINE_VALIDATE_ASSERT(index < m_size, "Index out of range")
for (size_type i = index; i + 1 < m_size; ++i)
{
m_data[i] = std::move(m_data[i + 1]);
}
--m_size;
}

void pop()
{
ZENGINE_VALIDATE_ASSERT(m_size > 0, "Index out of range")
Expand Down
65 changes: 65 additions & 0 deletions ZEngine/ZEngine/Core/VFS/IVFSBackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once
#include <Core/Containers/Array.h>
#include <Core/Memory/Allocator.h>
#include <Core/VFS/IVFSFile.h>
#include <Core/VFS/VFSPath.h>
#include <cstdint>

namespace ZEngine::Core::VFS
{
struct VFSDirEntry
{
VFSPath Path = {};
VFSFileStat Stat = {};
bool IsDirectory = false;
};

enum class VFSBackendCaps : uint32_t
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
List = 1 << 2,
MemoryMap = 1 << 3,
Watch = 1 << 4,
};
inline VFSBackendCaps operator|(VFSBackendCaps a, VFSBackendCaps b)
{
return static_cast<VFSBackendCaps>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
}
inline bool HasCap(VFSBackendCaps caps, VFSBackendCaps test)
{
return (static_cast<uint32_t>(caps) & static_cast<uint32_t>(test)) != 0;
}

struct IVFSBackend
{
virtual ~IVFSBackend() = default;

[[nodiscard]] virtual VFSResult<IVFSFile*> Open(const VFSPath& path, VFSOpenFlags flags) = 0;
virtual void Close(IVFSFile* file) = 0;
[[nodiscard]] virtual VFSResult<VFSFileStat> Stat(const VFSPath& path) const = 0;
virtual bool Exists(const VFSPath& path) const = 0;

// `arena` used to allocate the returned Array — lifetime tied to arena.
[[nodiscard]] virtual VFSResult<Core::Containers::Array<VFSDirEntry>> List(Core::Memory::ArenaAllocator* arena, const VFSPath& dir) const = 0;

// Write operations. Return Unsupported on read-only backends.
[[nodiscard]] virtual VFSResult<void> CreateDir(const VFSPath& path)
{
return VFSResult<void>::Fail(VFSError::Unsupported);
}
[[nodiscard]] virtual VFSResult<void> Remove(const VFSPath& path)
{
return VFSResult<void>::Fail(VFSError::Unsupported);
}
[[nodiscard]] virtual VFSResult<void> Rename(const VFSPath& from, const VFSPath& to)
{
return VFSResult<void>::Fail(VFSError::Unsupported);
}

virtual cstring BackendType() const = 0;
virtual VFSBackendCaps Capabilities() const = 0;
};

} // namespace ZEngine::Core::VFS
40 changes: 14 additions & 26 deletions ZEngine/ZEngine/Core/VFS/IVFSContext.h
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
#pragma once
#include <Core/Containers/Array.h>
#include <Core/Memory/Allocator.h>
#include <IVFSBackend.h>
#include <IVFSFile.h>
#include <VFSPath.h>

namespace ZEngine::Core::VFS
{
struct VFSDirEntry
{
VFSPath Path = {};
VFSFileStat Stat = {};
bool IsDirectory = false;
};

struct IVFSContext
{
virtual ~IVFSContext() = default;
virtual ~IVFSContext() = default;

virtual VFSResult<IVFSFile*> Open(const VFSPath& path, VFSOpenFlags flags) = 0;
virtual void Close(IVFSFile* file) = 0;
virtual VFSResult<VFSFileStat> Stat(const VFSPath& path) const = 0;
virtual bool Exists(const VFSPath& path) const = 0;
virtual VFSResult<Core::Containers::Array<VFSDirEntry>> List(Core::Memory::ArenaAllocator* arena, const VFSPath& dir) const = 0;
};
[[nodiscard]] virtual VFSResult<IVFSFile*> Open(const VFSPath& absolute_path, VFSOpenFlags flags) = 0;
[[nodiscard]] virtual VFSResult<Containers::Array<VFSDirEntry>> List(const VFSPath& absolute_dir, Memory::ArenaAllocator* out_arena) = 0;

struct VFSDiskContext final : public IVFSContext
{
explicit VFSDiskContext(cstring native_root);
~VFSDiskContext() override;
[[nodiscard]] virtual VFSResult<VFSFileStat> Stat(const VFSPath& absolute_path) = 0;

[[nodiscard]] virtual VFSResult<bool> Exists(const VFSPath& absolute_path) = 0;

[[nodiscard]] virtual VFSResult<void> Mount(IVFSBackend* backend, const VFSPath& logical_root, int priority) = 0;

[[nodiscard]] virtual VFSResult<void> Unmount(const VFSPath& logical_root) = 0;

VFSResult<IVFSFile*> Open(const VFSPath& path, VFSOpenFlags flags) override;
void Close(IVFSFile* file) override;
VFSResult<VFSFileStat> Stat(const VFSPath& path) const override;
bool Exists(const VFSPath& path) const override;
VFSResult<Core::Containers::Array<VFSDirEntry>> List(Core::Memory::ArenaAllocator* arena, const VFSPath& dir) const override;
[[nodiscard]] virtual VFSResult<void> CreateDir(const VFSPath& absolute_path) = 0;

private:
bool ToNativePath(const VFSPath& vfs_path, char* out_buffer, size_t out_size) const;
[[nodiscard]] virtual VFSResult<void> Remove(const VFSPath& absolute_path) = 0;

char m_native_root[VFS_MAX_PATH] = {};
size_t m_native_root_len = 0;
[[nodiscard]] virtual VFSResult<void> Rename(const VFSPath& src, const VFSPath& dst) = 0;
};

} // namespace ZEngine::Core::VFS
1 change: 1 addition & 0 deletions ZEngine/ZEngine/Core/VFS/IVFSFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace ZEngine::Core::VFS
virtual VFSResult<VFSFileStat> Stat() const = 0;
virtual VFSResult<void> Flush() = 0;
virtual const VFSPath& Path() const = 0;
virtual VFSResult<void> Close() = 0;

// Read the whole file into out_buffer
VFSResult<size_t> ReadAll(Core::Containers::ArrayView<uint8_t> out_buffer)
Expand Down
203 changes: 203 additions & 0 deletions ZEngine/ZEngine/Core/VFS/VFSContext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include <Helpers/MemoryOperations.h>
#include <VFSContext.h>

namespace ZEngine::Core::VFS
{
void VFSContext::Initialize(Memory::ArenaAllocator* arena, size_t mount_table_capacity)
{
m_arena = arena;
m_mount_table.Initialize(m_arena, mount_table_capacity);
}

VFSResult<IVFSFile*> VFSContext::Open(const VFSPath& absolute_path, VFSOpenFlags flags)
{
VFSResult<ResolveResult> resolved = m_mount_table.Resolve(absolute_path);
if (resolved.Failed())
{
return VFSResult<IVFSFile*>::Fail(resolved.Error());
}

const ResolveResult& hit = resolved.Value();
return hit.Backend->Open(hit.RelativePath, flags);
}

VFSResult<VFSFileStat> VFSContext::Stat(const VFSPath& absolute_path)
{
VFSResult<ResolveResult> resolved = m_mount_table.Resolve(absolute_path);
if (resolved.Failed())
{
return VFSResult<VFSFileStat>::Fail(resolved.Error());
}

const ResolveResult& hit = resolved.Value();
return hit.Backend->Stat(hit.RelativePath);
}

VFSResult<bool> VFSContext::Exists(const VFSPath& absolute_path)
{
VFSResult<ResolveResult> resolved = m_mount_table.Resolve(absolute_path);
if (resolved.Failed())
{
return VFSResult<bool>::Fail(resolved.Error());
}

const ResolveResult& hit = resolved.Value();
return VFSResult<bool>::Ok(hit.Backend->Exists(hit.RelativePath));
}

VFSResult<Containers::Array<VFSDirEntry>> VFSContext::List(const VFSPath& absolute_dir, Memory::ArenaAllocator* out_arena)
{
using ResultT = VFSResult<Containers::Array<VFSDirEntry>>;

auto scratch = ZGetScratch(m_arena);

Containers::Array<ResolveResult> matches;
matches.init(scratch.Arena, 8);

VFSResult<void> resolved = m_mount_table.ResolveAll(absolute_dir, matches);
if (resolved.Failed())
{
ZReleaseScratch(scratch);
return ResultT::Fail(resolved.Error());
}
if (matches.empty())
{
ZReleaseScratch(scratch);
return ResultT::Fail(VFSError::NotFound);
}

Containers::Array<VFSDirEntry> merged;
merged.init(out_arena, 16);

for (size_t m = 0; m < matches.size(); ++m)
{
VFSResult<Containers::Array<VFSDirEntry>> sub = matches[m].Backend->List(scratch.Arena, matches[m].RelativePath);
if (sub.Failed())
{
continue;
}

Containers::Array<VFSDirEntry>& entries = sub.Value();
for (size_t e = 0; e < entries.size(); ++e)
{
VFSPathComponent name = entries[e].Path.Filename();

char filename[VFS_MAX_PATH];
Helpers::secure_memcpy(filename, sizeof(filename), name.Data, name.Length);
filename[name.Length] = '\0';

VFSResult<VFSPath> child = absolute_dir.Append(filename);
if (child.Failed())
{
continue;
}

bool already_present = false;
for (size_t k = 0; k < merged.size(); ++k)
{
if (merged[k].Path == child.Value())
{
already_present = true;
break;
}
}
if (already_present)
{
continue;
}

VFSDirEntry entry = entries[e];
entry.Path = child.Value();
merged.push(entry);
}
}

ZReleaseScratch(scratch);
return ResultT::Ok(std::move(merged));
}

VFSResult<void> VFSContext::Mount(IVFSBackend* backend, const VFSPath& logical_root, int priority)
{
return m_mount_table.Mount(backend, logical_root, priority);
}

VFSResult<void> VFSContext::Unmount(const VFSPath& logical_root)
{
return m_mount_table.Unmount(logical_root);
}

VFSResult<void> VFSContext::CreateDir(const VFSPath& absolute_path)
{
VFSResult<ResolveResult> writable = ResolveWritable(absolute_path);
if (writable.Failed())
{
return VFSResult<void>::Fail(writable.Error());
}

const ResolveResult& hit = writable.Value();
return hit.Backend->CreateDir(hit.RelativePath);
}

VFSResult<void> VFSContext::Remove(const VFSPath& absolute_path)
{
VFSResult<ResolveResult> writable = ResolveWritable(absolute_path);
if (writable.Failed())
{
return VFSResult<void>::Fail(writable.Error());
}

const ResolveResult& hit = writable.Value();
return hit.Backend->Remove(hit.RelativePath);
}

VFSResult<void> VFSContext::Rename(const VFSPath& src, const VFSPath& dst)
{
VFSResult<ResolveResult> src_hit = ResolveWritable(src);
if (src_hit.Failed())
{
return VFSResult<void>::Fail(src_hit.Error());
}

VFSResult<ResolveResult> dst_hit = ResolveWritable(dst);
if (dst_hit.Failed())
{
return VFSResult<void>::Fail(dst_hit.Error());
}

if (src_hit.Value().Backend != dst_hit.Value().Backend)
{
return VFSResult<void>::Fail(VFSError::Unsupported);
}

return src_hit.Value().Backend->Rename(src_hit.Value().RelativePath, dst_hit.Value().RelativePath);
}

VFSResult<ResolveResult> VFSContext::ResolveWritable(const VFSPath& path) const
{
auto scratch = ZGetScratch(m_arena);

Containers::Array<ResolveResult> matches;
matches.init(scratch.Arena, 8);

VFSResult<void> resolved = m_mount_table.ResolveAll(path, matches);
if (resolved.Failed())
{
ZReleaseScratch(scratch);
return VFSResult<ResolveResult>::Fail(resolved.Error());
}

for (size_t i = 0; i < matches.size(); ++i)
{
if (HasCap(matches[i].Backend->Capabilities(), VFSBackendCaps::Write))
{
ResolveResult hit = matches[i]; // copy out before scratch is released
ZReleaseScratch(scratch);
return VFSResult<ResolveResult>::Ok(hit);
}
}

ZReleaseScratch(scratch);
return VFSResult<ResolveResult>::Fail(VFSError::PermissionDenied);
}

} // namespace ZEngine::Core::VFS
Loading
Loading