Compare commits
2 Commits
3cdb7ce958
...
0de837edd2
Author | SHA1 | Date |
---|---|---|
alistair | 0de837edd2 | 11 months ago |
alistair | 673b531d40 | 11 months ago |
8 changed files with 1289 additions and 1943 deletions
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
|
||||
#pragma once |
||||
|
||||
#include <tl/expected.hpp> |
||||
|
||||
using namespace tl; |
@ -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
|
@ -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); |
||||
} |
@ -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
|
@ -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…
Reference in new issue