Compare commits

...

2 Commits

Author SHA1 Message Date
alistair 0de837edd2 vfs 11 months ago
alistair 673b531d40 vfs initial 11 months ago
  1. 36
      CMakeLists.txt
  2. 2646
      compile_commands.json
  3. 6
      source/expected.hpp
  4. 3
      source/main.cpp
  5. 32
      source/random.hpp
  6. 150
      source/tests.cpp
  7. 335
      source/vfs.hpp
  8. 24
      test/vfs/testfile

36
CMakeLists.txt

@ -65,6 +65,12 @@ target_compile_features(enttge_lib PUBLIC cxx_std_20) @@ -65,6 +65,12 @@ target_compile_features(enttge_lib PUBLIC cxx_std_20)
set(ASSIMP_WARNINGS_AS_ERRORS OFF)
CPMAddPackage("gh:onqtam/doctest@2.4.9")
CPMAddPackage("gh:TartanLlama/expected@1.1.0")
CPMAddPackage("gh:microsoft/GSL@4.0.0")
CPMAddPackage("gh:assimp/assimp@5.2.5")
CPMAddPackage("gh:nothings/stb#master")
@ -142,6 +148,11 @@ target_link_libraries(imgui PUBLIC SDL2::SDL2-static) @@ -142,6 +148,11 @@ target_link_libraries(imgui PUBLIC SDL2::SDL2-static)
# ---- Declare executable ----
add_executable(test_exe
source/tests.cpp
)
add_executable(enttge_exe
source/mesh.cpp
source/mesh.h
@ -156,11 +167,11 @@ add_executable(enttge_exe @@ -156,11 +167,11 @@ add_executable(enttge_exe
source/glheader.hpp
source/shaders.h
source/stb_image.cpp
source/vfs.hpp
)
add_executable(enttge::exe ALIAS enttge_exe)
set_property(TARGET enttge_exe PROPERTY OUTPUT_NAME enttge)
target_include_directories(enttge_exe PUBLIC ${stb_SOURCE_DIR})
@ -179,7 +190,25 @@ target_link_libraries(enttge_exe PRIVATE enttge_lib @@ -179,7 +190,25 @@ target_link_libraries(enttge_exe PRIVATE enttge_lib
spdlog
imgui
assimp
expected
Microsoft.GSL::GSL
)
target_link_libraries(test_exe PRIVATE enttge_lib
doctest
fmt::fmt
EnTT
glm::glm
glad
refl-cpp
SDL2::SDL2-static
spdlog
imgui
assimp
expected
Microsoft.GSL::GSL
)
if (WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-static -fstack-protector")
@ -205,3 +234,8 @@ elseif(NOT PROJECT_IS_TOP_LEVEL) @@ -205,3 +234,8 @@ elseif(NOT PROJECT_IS_TOP_LEVEL)
endif()
include(cmake/dev-mode.cmake)
# Testing
enable_testing()
add_test(test_exe test_exe)

2646
compile_commands.json

File diff suppressed because it is too large Load Diff

6
source/expected.hpp

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
#pragma once
#include <tl/expected.hpp>
using namespace tl;

3
source/main.cpp

@ -19,7 +19,6 @@ @@ -19,7 +19,6 @@
#include "glheader.hpp"
// clang-format on
#include "uniform-buffer.hpp"
#include <backends/imgui_impl_opengl3.h>
#include <backends/imgui_impl_sdl2.h>
#include <glm/glm.hpp>
@ -31,6 +30,8 @@ @@ -31,6 +30,8 @@
#include "camera.hpp"
#include "mesh.h"
#include "shaders.h"
#include "uniform-buffer.hpp"
#include "vfs.hpp"
#include "window.hpp"
/**

32
source/random.hpp

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
#include <optional>
#include <random>
enum random_generator {
INODE,
};
template <auto instance> struct singleton {
std::optional<decltype(instance)> inner;
singleton() {
if (!inner.has_value()) {
inner = instance;
}
}
};
namespace rng {
template <typename T> struct mersenne_rng {
static std::random_device device;
static std::mt19937_64 generator;
mersenne_rng() { generator = std::mt19937_64(device); }
T next() { return generator(); }
};
singleton<mersenne_rng<int64_t>{}> rand;
}; // namespace rng

150
source/tests.cpp

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>
#include <iostream>
#include <string.h>
TEST_CASE("Dummy test") { CHECK(1 == 1); }
#include "vfs.hpp"
/* debugging */
template <typename T> std::string hexdump(const std::span<const T> data) {
size_t i, j = 0;
std::string out;
for (i = 0; i < data.size(); i += j) {
out += fmt::format("{:#04x}", i);
// printf("%4zu: ", i);
for (j = 0; j < 16 && i + j < data.size(); j++)
out += fmt::format("{:02x} ", data[i + j]);
// printf("%02x ", data[i + j]);
while (j++ < 16)
out += (" ");
out += "|";
for (j = 0; j < 16 && i + j < data.size(); j++)
out += (isprint(data[i + j]) ? data[i + j] : '.');
out += "|\n";
}
out.pop_back(); // remove last newline
return out;
}
template <typename T, typename U>
bool buffers_differ(const std::span<const T> &A, const std::span<const U> &B) {
std::span<const char> a{A};
std::span<const char> b{B};
bool differ = false;
int i = 0;
for (; i < a.size(); i++) {
if (b.size() == i)
break;
if (a[i] != b[i]) {
break;
}
}
if (i != a.size() || i != b.size()) {
differ = true;
spdlog::info("Differ at position {}", i);
spdlog::info("{}", hexdump(a));
spdlog::info("{}", hexdump(b));
}
return differ;
}
TEST_CASE("zpath from string") {
vfs::zpath_view p{("hello")};
CHECK(p.size() == 5);
CHECK(p.actual_size() == 6);
CHECK_EQ(p.front(), 'h');
CHECK_EQ(p.back(), '\0');
CHECK_EQ(*(p.begin() + 1), 'e');
CHECK(p.get_span()[5] == '\0');
INFO(std::string(p.c_str()));
for (auto c : p.get_span()) {
INFO(c);
}
CHECK(strcmp(p.c_str(), "hello") == 0);
}
TEST_CASE("zpath join string") {
SUBCASE("basic") {
vfs::zpath_view p{"hello"};
auto n = p / "world";
CHECK(strcmp(p.c_str(), "hello/world") == 0);
INFO(n.c_str());
CHECK(strcmp(n.c_str(), "hello/world") == 0);
CHECK(!buffers_differ(n.get_span(),
vfs::zpath_view("hello/world").get_span()));
CHECK_EQ(n.str(), std::string("hello/world"));
CHECK_EQ(p.str(), std::string("hello/world"));
}
SUBCASE("const zpath") {
const vfs::zpath_view p{"hello"};
auto n = p / "world";
CHECK(strcmp(p.c_str(), "hello") == 0);
INFO(n.c_str());
CHECK(strcmp(n.c_str(), "hello/world") == 0);
CHECK(!buffers_differ(n.get_span(),
vfs::zpath_view("hello/world").get_span()));
CHECK(!buffers_differ(p.get_span(), vfs::zpath_view("hello").get_span()));
CHECK_EQ(n.str(), std::string("hello/world"));
CHECK_EQ(p.str(), std::string("hello"));
}
SUBCASE("divop") {
const vfs::zpath_view p =
vfs::zpath_view("root") / "home" / "user" / "beans";
CHECK_EQ(p.str(), std::string("root/home/user/beans"));
vfs::zpath_view q = p / "Documents";
CHECK_EQ(p.str(), std::string("root/home/user/beans"));
CHECK_EQ(q.str(), std::string("root/home/user/beans/Documents"));
}
}
TEST_CASE("merge method") {
std::string first = "hello";
std::string second = "world";
std::vector<char> hello1;
std::vector<char> hello2;
std::vector<char> res;
hello1.insert(hello1.begin(), first.begin(), first.end() + 1);
hello2.insert(hello2.begin(), second.begin(), second.end() + 1);
vfs::zpath_view::merge_paths(hello1, hello2);
std::string s{hello1.data(), hello1.size()};
CHECK_EQ(strcmp(s.c_str(), std::string("hello/world").c_str()), 0);
CHECK(
!buffers_differ(std::span<const char>(hello1),
vfs::zpath_view(std::string("hello/world")).get_span()));
}
TEST_CASE("vfs") {
vfs::vfmount_sdlfile<char> test{"test/vfs"};
auto fl = test.open_read("testfile");
REQUIRE(fl.has_value());
auto repsize = (*fl)->size();
REQUIRE(repsize.has_value());
REQUIRE(*repsize > 0);
auto res = (*fl)->read(0, 10);
REQUIRE(res.has_value());
auto s = std::string(res->data(), res->size());
INFO(s);
REQUIRE(res->size() != 0);
REQUIRE((*fl)->close() == false);
auto flw = test.open_readwrite("testfilew");
REQUIRE(flw.has_value());
auto testw = std::string("alskdahldha");
REQUIRE((*flw)->write(std::span<char>(testw.data(), testw.size())) == false);
REQUIRE((*flw)->close() == false);
}

335
source/vfs.hpp

@ -0,0 +1,335 @@ @@ -0,0 +1,335 @@
#pragma once
#include "expected.hpp"
#include <SDL2/SDL_filesystem.h>
#include <SDL2/SDL_rwops.h>
#include <filesystem>
#include <fmt/format.h>
#include <gsl/assert>
#include <inttypes.h>
#include <optional>
#include <random>
#include <span>
#include <spdlog/spdlog.h>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace vfs {
struct error {
std::string message;
error(std::string message) : message(message) { spdlog::error(message); }
};
/**
* A null-terminated path object.
* This mainly exists to allow a zero-copy view of a null-terminated string.
*
* A copy is created when paths are concatenated, and stored in this object.
*
*/
class zpath_view {
// in case we need to own some data for join
std::vector<char> storage{}; // must be first
std::span<const char> view;
zpath_view(std::vector<char> data) : storage(data), view(storage) {
Expects(data.back() == '\0');
}
public:
zpath_view(std::span<const char> data) : view(data) {
Expects(data.back() == '\0');
}
zpath_view(zpath_view &&o) : storage(std::move(o.storage)), view(o.view) {}
zpath_view(
const zpath_view &other) // copy constructor
// Make a copy of storage only if it is set, then
// make our view a view into that instance
: storage(other.storage),
view(storage.size() > 0 ? storage : other.view) {}
zpath_view &operator=(zpath_view &&o) {
storage = std::move(o.storage);
view = o.view;
return *this;
}
zpath_view &operator=(const zpath_view &other) { // copy assignment
if (this == &other) {
return *this;
}
storage = other.storage;
if (storage.size() > 0) {
view = std::span(storage);
} else {
view = other.view;
}
return *this;
}
zpath_view(const char *data) : view(data, strlen(data) + 1) {}
zpath_view(const std::string &path)
: view(std::span(path.c_str(), path.size() + 1)) {}
// zpath(const std::filesystem::path &path) : zpath(path.string()) {}
size_t size() const { return view.size() - 1; }
size_t actual_size() const { return view.size(); }
const std::span<const char> get_span() const { return view; }
const char *c_str() const { return static_cast<const char *>(view.data()); }
const std::string_view str() const {
if (view.size() <= 1) {
return std::string_view();
}
return std::string_view(view.begin(), view.end() - 1); // remove the null
}
auto begin() { return view.begin(); }
auto end() { return view.end(); }
auto rbegin() { return view.rbegin(); }
auto rend() { return view.rend(); }
auto front() { return view.front(); }
auto back() { return view.back(); }
auto data() { return view.data(); }
auto empty() { return view.empty(); }
zpath_view operator/(zpath_view other) { return join_into(other); }
zpath_view operator/(auto other) { return join_into(zpath_view(other)); }
zpath_view operator/(zpath_view other) const { return join(other); }
zpath_view operator/(auto other) const { return join(zpath_view(other)); }
static void merge_paths(std::vector<char> &first_inout,
const std::span<const char> other) {
Expects(other.back() == '\0');
// first_inout.reserve(first_inout.size() + other.size() + 2);
while (first_inout.back() == '\0' || first_inout.back() == '/') {
first_inout.pop_back();
}
if (other.front() != '/')
first_inout.push_back('/');
first_inout.insert(std::end(first_inout), other.begin(), other.end());
}
zpath_view &join_into(const zpath_view other) {
if (storage.size() != view.size()) {
storage.clear();
storage.insert(storage.begin(), view.begin(), view.end());
}
merge_paths(storage, other.get_span());
view = std::span(storage);
return *this;
}
zpath_view join(const zpath_view other) const {
std::vector<char> inner{};
inner.insert(inner.begin(), view.begin(), view.end());
merge_paths(inner, other.get_span());
return zpath_view(inner);
}
};
template <typename T> class file_node {
private:
typedef std::vector<T> vfs_buffer;
public:
virtual expected<size_t, error> size() = 0;
virtual expected<size_t, error> tell() = 0;
virtual bool valid() = 0;
virtual bool can_read() = 0;
virtual bool can_write() = 0;
/**
* Seek to \offset bytes from the end of the file (offset can be negative).
*/
virtual expected<size_t, error> seek_from_end(size_t offset) = 0;
/**
* Seek to \offset bytes from the start of the file.
*/
virtual expected<size_t, error> seek_from_start(size_t offset) = 0;
/**
* Seek to \offset bytes from the current offset.
*/
virtual expected<size_t, error> seek_from_current(size_t offset) = 0;
/**
* Read part of the file
*/
virtual expected<vfs_buffer, error> read(size_t offset_from_start,
size_t number) = 0;
/**
* Read the entire file
*/
virtual expected<vfs_buffer, error> read() = 0;
virtual bool write(std::span<T> data) = 0;
virtual bool close() = 0;
};
/**
* File handle using SDL's filesystem abstraction.
*/
template <typename T> class sdl_file_node : public file_node<T> {
private:
typedef std::vector<T> vfs_buffer;
typedef std::unique_ptr<file_node<T>> file_ptr;
// https://wiki.libsdl.org/SDL2/SDL_RWops
SDL_RWops *ops;
const char *last_error = "";
expected<size_t, error> seek_sdl(size_t offset, int whence) {
if (ops == nullptr)
return unexpected(error("Invalid file"));
int ret = ops->seek(ops, offset, RW_SEEK_END);
if (ret == -1) {
last_error = SDL_GetError();
return unexpected(error(fmt::format("Error {}", whence)));
}
return ret;
}
public:
const char *get_last_error() { return last_error; }
sdl_file_node(const char *path, const char *mode) {
ops = SDL_RWFromFile(path, mode);
if (ops == nullptr)
last_error = SDL_GetError();
}
sdl_file_node(const std::span<T> &memory) {
// https://wiki.libsdl.org/SDL2/SDL_RWFromMem
ops = SDL_RWFromMem(memory.data(), memory.size_bytes());
if (ops == nullptr)
last_error = SDL_GetError();
}
bool valid() { return ops != nullptr; }
expected<size_t, error> size() {
if (ops == nullptr)
return unexpected(
error(fmt::format("Invalid file {}", std::string(last_error))));
int ret = ops->size(ops);
if (ret == -1) {
last_error = SDL_GetError();
return unexpected(error(fmt::format("Error {}", last_error)));
}
return ret;
}
expected<size_t, error> seek_from_end(size_t offset) {
return seek_sdl(offset, RW_SEEK_END);
}
expected<size_t, error> seek_from_current(size_t offset) {
return seek_sdl(offset, RW_SEEK_CUR);
}
expected<size_t, error> seek_from_start(size_t offset) {
return seek_sdl(offset, RW_SEEK_SET);
}
expected<vfs_buffer, error> read(size_t offset_from_start, size_t number) {
if (ops == nullptr)
return unexpected(error("Invalid file"));
vfs_buffer chunks{};
chunks.reserve(number);
int chunks_read = ops->read(ops, chunks.data(), sizeof(T), number);
if (chunks_read < number) {
return unexpected(error("Short read provided."));
}
// may be less than number
chunks.resize(chunks_read);
auto t = std::span(chunks);
return chunks;
}
bool write(std::span<T> elems) {
if (ops == nullptr)
return true;
int res = ops->write(ops, elems.data(), sizeof(T), elems.size());
if (res != elems.size()) {
return true;
}
return false;
}
bool close() {
if (ops == nullptr)
return false;
int res = ops->close(ops);
ops = nullptr;
return res;
}
bool can_read() { return ops != nullptr; }
bool can_write() { return ops != nullptr; }
bool eof() { return ops != nullptr; }
expected<vfs_buffer, error> read() {
auto size = this->size();
if (size.has_value()) {
return read(0, *size);
} else {
return unexpected(size.error());
}
}
expected<size_t, error> tell() {
return unexpected(error("Not implemented"));
}
};
template <typename T> struct vfmount_sdlfile {
private:
typedef std::unique_ptr<file_node<T>> file_ptr;
public:
const zpath_view location;
vfmount_sdlfile(zpath_view path) : location(path) {}
expected<file_ptr, error> open_with_mode(zpath_view path, const char *mode) {
auto npath = location / path;
spdlog::info("Path str {} cstr {}", npath.str(), npath.c_str());
auto node = std::make_unique<sdl_file_node<T>>(npath.c_str(), mode);
if (node->valid()) {
return node;
}
return unexpected(
error(fmt::format("Failed to open file: {}", node->get_last_error())));
}
expected<file_ptr, error> open_read(zpath_view path) {
return open_with_mode(path, "r");
};
expected<file_ptr, error> open_readwrite(zpath_view path) {
return open_with_mode(path, "w");
};
};
}; // namespace vfs

24
test/vfs/testfile

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
Linux Ported to Homer Simpson's Brain
SPRINGFIELD -- Slashdot recently reported on Homer Simpson's brain "upgrade"
to an Intel CPU. Intel hails the CPU transplant as the "World's Greatest
Technological Achievement". Intel originally planned to install Microsoft
Windows CE (Cerebrum Enhanced) on Homer's new PentiumBrain II processor.
However, due to delays in releasing Windows CE, Intel decided to install
DebianBrain Linux, the new Linux port for brains.
Computer industry pundits applaud the last minute switch from Windows to
Linux. One said, "I was a bit concerned for Homer. With Windows CE, I could
easily imagine Homer slipping into an infinite loop: "General Protection
Fault. D'oh! D'oh! D'oh! D'oh..." Or, at the worst, the Blue Screen of
Death could have become much more than a joke."
Some pundits are more concerned about the quality of the Intel CPU. "Linux
is certainly an improvement over Windows. But since it's running on a
PentiumBrain chip, all bets are off. What if the chip miscalculates the core
temperature of the power plant where Homer works? I can just imagine the
story on the evening news: 'Springfield was obliterated into countless
subatomic particles yesterday because Homer J. Simpson, power plant
button-pusher, accidentally set the core temperature to 149.992322340948290
instead of 150...' If anything, an Alpha chip running Linux should have been
used for Homer's new brain."
Loading…
Cancel
Save