Squashed 'deps/oxlib/' content from commit 85f17c41

git-subtree-dir: deps/oxlib
git-subtree-split: 85f17c4188e266b82ba8858d21f67289c72e7b9a
This commit is contained in:
2026-05-06 01:38:00 -05:00
commit 69fcd7ad10
457 changed files with 53647 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
set(OX_OS_WINDOWS TRUE)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(OX_OS_FREEBSD TRUE)
else()
set(OX_OS_FREEBSD FALSE)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(OX_OS_LINUX TRUE)
else()
set(OX_OS_LINUX FALSE)
endif()
if(OX_USE_STDLIB)
add_subdirectory(oc)
endif()
add_subdirectory(clargs)
add_subdirectory(claw)
add_subdirectory(event)
add_subdirectory(fs)
add_subdirectory(logconn)
add_subdirectory(mc)
add_subdirectory(model)
add_subdirectory(preloader)
add_subdirectory(std)
+47
View File
@@ -0,0 +1,47 @@
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
# enable warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
endif()
add_library(
OxClArgs
src/clargs.cpp
)
set_property(
TARGET
OxClArgs
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
if(NOT MSVC)
target_compile_options(OxClArgs PRIVATE -Wsign-conversion)
target_compile_options(OxClArgs PRIVATE -Wconversion)
endif()
target_link_libraries(
OxClArgs PUBLIC
OxStd
)
target_include_directories(
OxClArgs PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS
OxClArgs
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/hashmap.hpp>
#include <ox/std/span.hpp>
#include <ox/std/string.hpp>
namespace ox {
class ClArgs {
private:
HashMap<String, bool> m_bools;
HashMap<String, String> m_strings;
HashMap<String, int> m_ints;
public:
ClArgs(int argc, const char **args) noexcept;
ClArgs(ox::SpanView<const char*> args) noexcept;
[[nodiscard]]
bool getBool(ox::StringViewCR arg, bool defaultValue) const noexcept;
[[nodiscard]]
String getString(ox::StringViewCR argName, ox::StringView defaultValue) const noexcept;
[[nodiscard]]
int getInt(ox::StringViewCR arg, int defaultValue) const noexcept;
[[nodiscard]]
Result<bool> getBool(ox::StringViewCR arg) const noexcept;
[[nodiscard]]
Result<String> getString(ox::StringViewCR argName) const noexcept;
Result<int> getInt(ox::StringViewCR arg) const noexcept;
};
}
+72
View File
@@ -0,0 +1,72 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/string.hpp>
#include <ox/clargs/clargs.hpp>
namespace ox {
ClArgs::ClArgs(int argc, const char **args) noexcept: ClArgs({args, static_cast<size_t>(argc)}) {}
ClArgs::ClArgs(ox::SpanView<const char*> args) noexcept {
for (auto i = 0u; i < args.size(); ++i) {
auto arg = StringView{args[i]};
if (arg[0] == '-') {
while (arg[0] == '-' && arg.size()) {
arg = substr(arg, 1);
}
m_bools[arg] = true;
// parse additional arguments
if (i < args.size() && args[i + 1]) {
auto const val = StringView{args[i + 1]};
if (val.size() && val[0] != '-') {
if (val == "false") {
m_bools[arg] = false;
}
m_strings[arg] = val;
if (auto r = ox::strToInt(val); r.error == 0) {
m_ints[arg] = r.value;
}
++i;
}
}
}
}
}
bool ClArgs::getBool(ox::StringViewCR arg, bool defaultValue) const noexcept {
auto const [value, err] = m_ints.at(arg);
return !err ? *value : defaultValue;
}
String ClArgs::getString(ox::StringViewCR arg, ox::StringView defaultValue) const noexcept {
auto const [value, err] = m_strings.at(arg);
return !err ? ox::String(*value) : ox::String(defaultValue);
}
int ClArgs::getInt(ox::StringViewCR arg, int defaultValue) const noexcept {
auto const [value, err] = m_ints.at(arg);
return !err ? *value : defaultValue;
}
Result<bool> ClArgs::getBool(ox::StringViewCR arg) const noexcept {
OX_REQUIRE(out, m_bools.at(arg));
return *out;
}
Result<String> ClArgs::getString(ox::StringViewCR argName) const noexcept {
OX_REQUIRE(out, m_strings.at(argName));
return *out;
}
Result<int> ClArgs::getInt(ox::StringViewCR arg) const noexcept {
OX_REQUIRE(out, m_ints.at(arg));
return *out;
}
}
+50
View File
@@ -0,0 +1,50 @@
add_library(
OxClaw
src/read.cpp
src/write.cpp
)
if(NOT MSVC)
target_compile_options(OxClaw PRIVATE -Wsign-conversion)
target_compile_options(OxClaw PRIVATE -Wconversion)
endif()
target_link_libraries(
OxClaw PUBLIC
OxMetalClaw
$<$<BOOL:${OX_USE_STDLIB}>:OxOrganicClaw>
)
#if(OX_USE_STDLIB)
# add_executable(
# readclaw
# readclaw.cpp
# )
# target_link_libraries(
# readclaw PUBLIC
# OxClaw
# )
#endif()
target_include_directories(
OxClaw PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS OxClaw
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+12
View File
@@ -0,0 +1,12 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "read.hpp"
#include "write.hpp"
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
namespace ox {
enum class ClawFormat {
None,
Metal,
Organic,
};
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/span.hpp>
#include <ox/mc/read.hpp>
#ifdef OX_USE_STDLIB
#include <ox/oc/read.hpp>
#endif
#include <ox/std/buffer.hpp>
#include <ox/std/string.hpp>
#include "format.hpp"
namespace ox {
constexpr auto Error_ClawTypeMismatch = 200;
constexpr auto Error_ClawTypeVersionMismatch = 201;
struct ClawHeader {
String typeName;
int typeVersion = -1;
TypeParamPack typeParams;
ClawFormat fmt = ClawFormat::None;
const char *data = nullptr;
std::size_t dataSize = 0;
};
ox::Result<ox::StringView> readClawTypeId(ox::BufferView buff) noexcept;
Result<ClawHeader> readClawHeader(ox::BufferView buff) noexcept;
Result<BufferView> stripClawHeader(ox::BufferView buff) noexcept;
template<typename T>
Error readClaw(ox::BufferView buff, T &val) {
OX_REQUIRE(header, readClawHeader(buff));
if (header.typeName != getModelTypeName<T>()) {
return ox::Error(Error_ClawTypeMismatch, "Claw Read: Type mismatch");
}
if (header.typeVersion != getModelTypeVersion<T>()) {
return ox::Error(Error_ClawTypeVersionMismatch, "Claw Read: Type Version mismatch");
}
switch (header.fmt) {
case ClawFormat::Metal:
{
ox::BufferReader br({header.data, header.dataSize});
MetalClawReader reader(br);
return model(reader.interface(), &val);
}
case ClawFormat::Organic:
{
#ifdef OX_USE_STDLIB
OrganicClawReader reader(header.data, header.dataSize);
return model(&reader, &val);
#else
break;
#endif
}
case ClawFormat::None:
return ox::Error(1);
}
return ox::Error(1);
}
template<typename T>
Result<T> readClaw(ox::BufferView buff) {
Result<T> val;
OX_RETURN_ERROR(readClaw(buff, val.value));
return val;
}
Result<ModelObject> readClaw(TypeStore &ts, BufferView buff) noexcept;
}
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/mc/write.hpp>
#ifdef OX_USE_STDLIB
#include <ox/oc/write.hpp>
#endif
#include <ox/std/buffer.hpp>
#include <ox/std/string.hpp>
#include <ox/std/stringview.hpp>
#include "format.hpp"
namespace ox {
namespace detail {
struct TypeInfoCatcher {
const char *name = nullptr;
int version = 0;
template<typename T>
constexpr ox::Error setTypeInfo(
const char *pName = T::TypeName,
int pVersion = T::TypeVersion,
const Vector<String>& = {},
std::size_t = 0) noexcept {
this->name = pName;
this->version = pVersion;
return {};
}
constexpr Error field(...) noexcept {
return {};
}
static constexpr auto opType() {
return OpType::Write;
}
};
template<typename T, typename = int>
struct type_version {
static constexpr auto value = -1;
};
template<typename T>
struct type_version<T, decltype((void) T::TypeVersion, -1)> {
static constexpr auto value = T::TypeVersion;
};
template<typename T>
constexpr const char *getTypeName(const T *t) noexcept {
TypeInfoCatcher tnc;
std::ignore = model(&tnc, t);
return tnc.name;
}
template<typename T>
constexpr int getTypeVersion(const T *t) noexcept {
TypeInfoCatcher tnc;
std::ignore = model(&tnc, t);
return tnc.version;
}
template<typename T>
ox::Error writeClawHeader(Writer_c auto &writer, const T *t, ClawFormat fmt) noexcept {
switch (fmt) {
case ClawFormat::Metal:
OX_RETURN_ERROR(write(writer, "M2;"));
break;
case ClawFormat::Organic:
OX_RETURN_ERROR(write(writer, "O1;"));
break;
default:
return ox::Error(1);
}
OX_RETURN_ERROR(write(writer, detail::getTypeName(t)));
OX_RETURN_ERROR(writer.put(';'));
const auto tn = detail::getTypeVersion(t);
if (tn > -1) {
OX_RETURN_ERROR(ox::writeItoa(tn, writer));
}
OX_RETURN_ERROR(writer.put(';'));
return {};
}
}
Result<Buffer> writeClaw(
const auto &t,
ClawFormat fmt = ClawFormat::Metal,
std::size_t buffReserveSz = 2 * units::KB) noexcept {
Buffer out(buffReserveSz);
BufferWriter bw(&out, 0);
OX_RETURN_ERROR(detail::writeClawHeader(bw, &t, fmt));
#ifdef OX_USE_STDLIB
if (fmt == ClawFormat::Metal) {
OX_RETURN_ERROR(writeMC(bw, t));
} else if (fmt == ClawFormat::Organic) {
OX_REQUIRE(data, writeOC(t));
OX_RETURN_ERROR(bw.write(data.data(), data.size()));
}
#else
if (fmt != ClawFormat::Metal) {
return ox::Error(1, "OC is not supported in this build");
}
OX_RETURN_ERROR(writeMC(bw, t));
#endif
out.resize(bw.tellp());
return out;
}
}
+129
View File
@@ -0,0 +1,129 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/buffer.hpp>
#include <ox/claw/read.hpp>
namespace ox {
Result<StringView> readClawTypeId(BufferView const buff) noexcept {
auto buffRaw = buff.data();
auto buffLen = buff.size();
size_t outSz{};
const auto s1End = ox::strchr(buffRaw, ';', buffLen);
if (!s1End) {
return ox::Error(1, "Could not read Claw header");
}
auto const fmtSz = static_cast<std::size_t>(s1End - buffRaw) + 1;
buffRaw += fmtSz;
buffLen -= fmtSz;
outSz += fmtSz;
auto const s2End = ox::strchr(buffRaw, ';', buffLen);
if (!s2End) {
return ox::Error(2, "Could not read Claw header");
}
auto const s2Size = static_cast<std::size_t>(s2End - buffRaw) + 1;
buffRaw += s2Size;
buffLen -= s2Size;
outSz += s2Size;
auto const s3End = ox::strchr(buffRaw, ';', buffLen) + 1;
if (!s3End) {
return ox::Error(3, "Could not read Claw header");
}
auto const s3Size = static_cast<std::size_t>(s3End - buffRaw);
buffRaw += s3Size;
buffLen -= s3Size;
outSz += s3Size;
return {{buff.data() + fmtSz, outSz - fmtSz - 1}};
}
Result<ClawHeader> readClawHeader(BufferView const buff) noexcept {
auto buffRaw = buff.data();
auto buffLen = buff.size();
const auto s1End = ox::strchr(buffRaw, ';', buffLen);
if (!s1End) {
return ox::Error(1, "Could not read Claw header");
}
auto const s1Size = static_cast<std::size_t>(s1End - buffRaw);
StringView const fmt(buffRaw, s1Size);
buffRaw += s1Size + 1;
buffLen -= s1Size + 1;
auto const s2End = ox::strchr(buffRaw, ';', buffLen);
if (!s2End) {
return ox::Error(2, "Could not read Claw header");
}
auto const s2Size = static_cast<std::size_t>(s2End - buffRaw);
StringView const typeName(buffRaw, s2Size);
buffRaw += s2Size + 1;
buffLen -= s2Size + 1;
auto const s3End = ox::strchr(buffRaw, ';', buffLen);
if (!s3End) {
return ox::Error(3, "Could not read Claw header");
}
auto const s3Size = static_cast<std::size_t>(s3End - buffRaw);
StringView const versionStr(buffRaw, s3Size);
buffRaw += s3Size + 1;
buffLen -= s3Size + 1;
ClawHeader hdr;
if (fmt == "M2") {
hdr.fmt = ClawFormat::Metal;
} else if (fmt == "O1") {
hdr.fmt = ClawFormat::Organic;
} else {
return ox::Error(4, "Claw format does not match any supported format/version combo");
}
hdr.typeName = typeName;
std::ignore = ox::strToInt(versionStr).copyTo(hdr.typeVersion);
hdr.data = buffRaw;
hdr.dataSize = buffLen;
return hdr;
}
Result<BufferView> stripClawHeader(BufferView const buff) noexcept {
OX_REQUIRE(header, readClawHeader(buff));
return {{header.data, header.dataSize}};
}
Result<ModelObject> readClaw(TypeStore &ts, BufferView buff) noexcept {
OX_REQUIRE(header, readClawHeader(buff));
auto const [t, tdErr] = ts.getLoad(
header.typeName, header.typeVersion, header.typeParams);
if (tdErr) {
return ox::Error(3, "Could not load type descriptor");
}
ModelObject obj;
OX_RETURN_ERROR(obj.setType(t));
switch (header.fmt) {
case ClawFormat::Metal:
{
ox::BufferReader br({header.data, header.dataSize});
MetalClawReader reader(br);
OX_RETURN_ERROR(model(reader.interface(), &obj));
return obj;
}
case ClawFormat::Organic:
{
#ifdef OX_USE_STDLIB
OrganicClawReader reader({header.data, header.dataSize});
ModelHandlerInterface handler(reader);
OX_RETURN_ERROR(model(&handler, &obj));
return obj;
#else
break;
#endif
}
case ClawFormat::None:
return ox::Error(1);
}
return ox::Error(1);
}
}
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <cstdio>
#include <ox/std/reader.hpp>
#include <ox/std/trace.hpp>
#include "claw.hpp"
int main(int argc, char **args) {
if (argc > 2) {
oxErr("Too many arguments");
return -1;
}
auto const file = argc == 1 ? stdin : fopen(args[1], "r");
if (fseek(file, 0, SEEK_END)) {
oxErr("Could not get file size\n");
return -2;
}
auto const size = static_cast<std::size_t>(ftell(file));
oxDebugf("{}", size);
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/claw/write.hpp>
namespace ox::detail {
struct versioned_type {
static constexpr int TypeVersion = 4;
};
struct unversioned_type {
};
static_assert(type_version<versioned_type>::value == 4);
static_assert(type_version<unversioned_type>::value == -1);
}
+15
View File
@@ -0,0 +1,15 @@
add_executable(
ClawTest
tests.cpp
)
target_link_libraries(
ClawTest
OxClaw
)
add_test("[ox/claw] ClawHeaderReader" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ClawTest ClawHeaderReader)
add_test("[ox/claw] ClawHeaderReader2" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ClawTest ClawHeaderReader2)
add_test("[ox/claw] ClawHeaderReadTypeId" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ClawTest ClawHeaderReadTypeId)
add_test("[ox/claw] ClawWriter" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ClawTest ClawWriter)
add_test("[ox/claw] ClawReader" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ClawTest ClawReader)
+211
View File
@@ -0,0 +1,211 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#undef NDEBUG
#include <map>
#include <ox/claw/format.hpp>
#include <ox/claw/read.hpp>
#include <ox/claw/write.hpp>
#include <ox/mc/mc.hpp>
#include <ox/model/model.hpp>
#include <ox/std/std.hpp>
union TestUnion {
static constexpr auto TypeName = "TestUnion";
static constexpr auto TypeVersion = 1;
bool Bool;
uint32_t Int = 5;
char *String;
};
struct TestStructNest {
static constexpr auto TypeName = "TestStructNest";
static constexpr auto TypeVersion = 1;
bool Bool = false;
uint32_t Int = 0;
ox::IString<32> String = "";
};
struct TestStruct {
static constexpr auto TypeName = "TestStruct";
static constexpr auto TypeVersion = 1;
bool Bool = false;
int32_t Int = 0;
int32_t Int1 = 0;
int32_t Int2 = 0;
int32_t Int3 = 0;
int32_t Int4 = 0;
int32_t Int5 = 0;
int32_t Int6 = 0;
int32_t Int7 = 0;
int32_t Int8 = 0;
int unionIdx = 1;
TestUnion Union;
ox::IString<32> String = "";
uint32_t List[4] = {0, 0, 0, 0};
TestStructNest EmptyStruct;
TestStructNest Struct;
~TestStruct() {
if (unionIdx == 2) {
ox::safeDelete(Union.String);
}
}
};
template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestUnion> auto *obj) {
OX_RETURN_ERROR(io->template setTypeInfo<TestUnion>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->fieldCString("String", &obj->String));
return ox::Error(0);
}
template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestStructNest> auto *obj) {
OX_RETURN_ERROR(io->template setTypeInfo<TestStructNest>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->field("String", &obj->String));
return ox::Error(0);
}
template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestStruct> auto *obj) {
OX_RETURN_ERROR(io->template setTypeInfo<TestStruct>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->field("Int1", &obj->Int1));
OX_RETURN_ERROR(io->field("Int2", &obj->Int2));
OX_RETURN_ERROR(io->field("Int3", &obj->Int3));
OX_RETURN_ERROR(io->field("Int4", &obj->Int4));
OX_RETURN_ERROR(io->field("Int5", &obj->Int5));
OX_RETURN_ERROR(io->field("Int6", &obj->Int6));
OX_RETURN_ERROR(io->field("Int7", &obj->Int7));
OX_RETURN_ERROR(io->field("Int8", &obj->Int8));
int unionIdx = 0;
if constexpr(T::opType() != ox::OpType::Reflect) {
unionIdx = obj->unionIdx;
}
OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, unionIdx}));
OX_RETURN_ERROR(io->field("String", &obj->String));
OX_RETURN_ERROR(io->field("List", obj->List, 4));
OX_RETURN_ERROR(io->field("EmptyStruct", &obj->EmptyStruct));
OX_RETURN_ERROR(io->field("Struct", &obj->Struct));
return ox::Error(0);
}
static std::map<ox::StringView, ox::Error(*)()> tests = {
{
{
"ClawHeaderReader",
[] {
constexpr auto hdr = ox::StringLiteral("O1;com.drinkingtea.ox.claw.test.Header;2;");
auto [ch, err] = ox::readClawHeader({hdr.c_str(), hdr.size() + 1});
oxAssert(err, "Error parsing header");
oxAssert(ch.fmt == ox::ClawFormat::Organic, "Format wrong");
oxAssert(ch.typeName == "com.drinkingtea.ox.claw.test.Header", "Type name wrong");
oxAssert(ch.typeVersion == 2, "Type version wrong");
return ox::Error(0);
}
},
{
"ClawHeaderReader2",
[] {
constexpr auto hdr = ox::StringLiteral("M2;com.drinkingtea.ox.claw.test.Header2;3;");
auto [ch, err] = ox::readClawHeader({hdr.c_str(), hdr.size() + 1});
oxAssert(err, "Error parsing header");
oxAssert(ch.fmt == ox::ClawFormat::Metal, "Format wrong");
oxAssert(ch.typeName == "com.drinkingtea.ox.claw.test.Header2", "Type name wrong");
oxAssert(ch.typeVersion == 3, "Type version wrong");
return ox::Error(0);
}
},
{
"ClawHeaderReadTypeId",
[] {
constexpr auto hdr = ox::StringLiteral("M2;com.drinkingtea.ox.claw.test.Header2;3;awefawf");
constexpr auto expected = ox::StringLiteral("com.drinkingtea.ox.claw.test.Header2;3");
OX_REQUIRE(actual, ox::readClawTypeId({hdr.data(), hdr.size() + 1}));
ox::expect(actual, expected);
return ox::Error{};
}
},
{
"ClawWriter",
[] {
// This test doesn't confirm much, but it does show that the writer
// doesn't segfault
TestStruct ts;
OX_RETURN_ERROR(ox::writeClaw(ts, ox::ClawFormat::Metal));
return ox::Error(0);
}
},
{
"ClawReader",
[] {
TestStruct testIn, testOut;
testIn.Bool = true;
testIn.Int = 42;
testIn.Union.Int = 42;
testIn.String = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = false;
testIn.Struct.Int = 300;
testIn.Struct.String = "Test String 2";
const auto [buff, err] = ox::writeMC(testIn);
oxAssert(err, "writeClaw failed");
oxAssert(ox::readMC(buff, testOut), "readClaw failed");
//std::cout << testIn.Union.Int << "|" << testOut.Union.Int << "|\n";
oxAssert(testIn.Bool == testOut.Bool, "Bool value mismatch");
oxAssert(testIn.Int == testOut.Int, "Int value mismatch");
oxAssert(testIn.Int1 == testOut.Int1, "Int1 value mismatch");
oxAssert(testIn.Int2 == testOut.Int2, "Int2 value mismatch");
oxAssert(testIn.Int3 == testOut.Int3, "Int3 value mismatch");
oxAssert(testIn.Int4 == testOut.Int4, "Int4 value mismatch");
oxAssert(testIn.Int5 == testOut.Int5, "Int5 value mismatch");
oxAssert(testIn.Int6 == testOut.Int6, "Int6 value mismatch");
oxAssert(testIn.Int7 == testOut.Int7, "Int7 value mismatch");
oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch");
oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch");
oxAssert(testIn.String == testOut.String, "String value mismatch");
oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch");
oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch");
oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch");
oxAssert(testIn.List[3] == testOut.List[3], "List[3] value mismatch");
oxAssert(testIn.EmptyStruct.Bool == testOut.EmptyStruct.Bool, "EmptyStruct.Bool value mismatch");
oxAssert(testIn.EmptyStruct.Int == testOut.EmptyStruct.Int, "EmptyStruct.Int value mismatch");
oxAssert(testIn.EmptyStruct.String == testOut.EmptyStruct.String, "EmptyStruct.String value mismatch");
oxAssert(testIn.Struct.Int == testOut.Struct.Int, "Struct.Int value mismatch");
oxAssert(testIn.Struct.String == testOut.Struct.String, "Struct.String value mismatch");
oxAssert(testIn.Struct.Bool == testOut.Struct.Bool, "Struct.Bool value mismatch");
return ox::Error(0);
}
},
}
};
int main(int argc, const char **args) {
if (argc < 2) {
oxError("Must specify test to run");
}
auto const testName = args[1];
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(), "Test returned Error");
return 0;
}
return -1;
}
+51
View File
@@ -0,0 +1,51 @@
add_library(
OxEvent
src/signal.cpp
)
if(NOT MSVC)
target_compile_options(OxEvent PRIVATE -Wsign-conversion)
target_compile_options(OxEvent PRIVATE -Wconversion)
endif()
if(NOT OX_BARE_METAL)
set_property(
TARGET
OxEvent
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
endif()
target_compile_definitions(
OxEvent PUBLIC
$<$<BOOL:${OX_USE_STDLIB}>:OX_USE_STDLIB>
$<$<BOOL:${OX_NODEBUG}>:OX_NODEBUG>
)
target_link_libraries(
OxEvent PUBLIC
OxStd
)
target_include_directories(
OxEvent PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS OxEvent
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+11
View File
@@ -0,0 +1,11 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "signal.hpp"
+416
View File
@@ -0,0 +1,416 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/assert.hpp>
#include <ox/std/def.hpp>
#include <ox/std/defines.hpp>
#include <ox/std/error.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/vector.hpp>
namespace ox {
class SignalHandler;
#ifndef OX_OS_BareMetal
namespace detail {
template<typename T>
struct isError {
static constexpr bool value = false;
};
template<>
struct isError<Error> {
static constexpr bool value = true;
};
}
template<class... Args>
class Signal {
protected:
struct BaseSlot {
virtual ~BaseSlot() = default;
virtual void call(Args...) = 0;
virtual void cleanup(Signal*) noexcept {}
[[nodiscard]]
virtual const void *receiver() const noexcept { return nullptr; }
};
template<typename F>
struct FunctionSlot: public BaseSlot {
F f;
explicit FunctionSlot(F f) {
this->f = f;
}
void call(Args... args) final {
if constexpr(detail::isError<decltype(f(args...))>::value) {
OX_THROW_ERROR(f(args...));
} else {
f(args...);
}
}
};
template<typename T, typename Method>
struct MethodSlot: public BaseSlot {
T m_receiver = nullptr;
Method m_methodPtr;
MethodSlot(T receiver, Method methodPtr) {
m_receiver = receiver;
m_methodPtr = methodPtr;
}
void call(Args... args) final {
if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
} else {
f(args...);
}
}
void cleanup(Signal *signal) noexcept final {
std::ignore = m_receiver->destruction.disconnectSignal(signal);
//if (err) {
// oxErrorf("Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free. ({})", toStr(err));
//}
}
[[nodiscard]]
const void *receiver() const noexcept final {
return m_receiver;
}
};
template<typename SignalT, typename Method>
struct SignalMethodSlot: public BaseSlot {
const SignalT *m_receiver = nullptr;
Method m_methodPtr;
SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
m_receiver = receiver;
m_methodPtr = methodPtr;
}
void call(Args... args) final {
if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
} else {
(m_receiver->*(m_methodPtr))(args...);
}
}
void cleanup(Signal*) noexcept final {
}
[[nodiscard]]
const void *receiver() const noexcept final {
return m_receiver;
}
};
mutable Vector<UPtr<BaseSlot>> m_slots;
public:
~Signal() noexcept;
void connect(Error(*f)(Args...)) const noexcept;
template<typename T, typename Method>
void connect(const T *receiver, Method methodPtr) const noexcept;
template<typename T, typename Method>
void connect(T *receiver, Method methodPtr) const noexcept;
template<class... SubArgs, typename Method>
void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;
template<class... SubArgs>
Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const;
Error emitCheckError(Args... args) const noexcept;
};
extern template class Signal<const SignalHandler*>;
template<class... Args>
Signal<Args...>::~Signal() noexcept {
for (auto &slot : m_slots) {
slot->cleanup(this);
}
}
template<class... Args>
void Signal<Args...>::connect(Error(*f)(Args...)) const noexcept {
m_slots.emplace_back(new FunctionSlot<decltype(f)>(f));
}
template<class... Args>
template<typename T, typename Method>
void Signal<Args...>::connect(const T *receiver, Method methodPtr) const noexcept {
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
}
template<class... Args>
template<typename T, typename Method>
void Signal<Args...>::connect(T *receiver, Method methodPtr) const noexcept {
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
}
template<class... Args>
template<class... SubArgs, typename Method>
void Signal<Args...>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
}
template<class... Args>
template<class... SubArgs>
Error Signal<Args...>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
return disconnectObject(receiver);
}
template<class... Args>
Error Signal<Args...>::disconnectObject(const void *receiver) const noexcept {
for (auto i = 0u; i < m_slots.size(); ++i) {
const auto &slot = m_slots[i];
if (slot->receiver() == receiver) {
OX_RETURN_ERROR(m_slots.erase(i));
--i;
}
}
return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
}
template<class... Args>
void Signal<Args...>::emit(Args... args) const {
for (auto &f : m_slots) {
f->call(args...);
}
}
template<class... Args>
Error Signal<Args...>::emitCheckError(Args... args) const noexcept {
try {
for (auto &f : m_slots) {
f->call(args...);
}
return {};
} catch (const ox::Exception &ex) {
return ox::Error(ex.errCode, ex.msg, ex.src);
}
}
#else
template<typename T>
class Signal;
#endif
template<class... Args>
class Signal<Error(Args...)> {
protected:
struct BaseSlot {
virtual ~BaseSlot() = default;
virtual Error call(Args...) noexcept = 0;
virtual void cleanup(Signal*) noexcept {}
[[nodiscard]]
virtual const void *receiver() const noexcept { return nullptr; }
};
struct FunctionSlot: public BaseSlot {
Error (*f)(Args...);
explicit FunctionSlot(Error (*f)(Args...)) noexcept {
this->f = f;
}
Error call(Args... args) noexcept final {
return f(ox::forward<Args>(args)...);
}
};
template<typename T, typename Method>
struct MethodSlot: public BaseSlot {
T m_receiver = nullptr;
Method m_methodPtr;
MethodSlot(T receiver, Method methodPtr) {
m_receiver = receiver;
m_methodPtr = methodPtr;
}
Error call(Args... args) noexcept final {
return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
}
void cleanup(Signal *signal) noexcept final {
std::ignore = m_receiver->destruction.disconnectSignal(signal);
//oxErrorf("{}", toStr(err));
//oxAssert(err, "Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free.");
}
[[nodiscard]]
const void *receiver() const noexcept final {
return m_receiver;
}
};
template<typename SignalT, typename Method>
struct SignalMethodSlot: public BaseSlot {
const SignalT *m_receiver = nullptr;
Method m_methodPtr;
SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
m_receiver = receiver;
m_methodPtr = methodPtr;
}
Error call(Args... args) noexcept final {
return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
}
void cleanup(Signal*) noexcept final {
}
[[nodiscard]]
const void *receiver() const noexcept final {
return m_receiver;
}
};
mutable Vector<UPtr<BaseSlot>> m_slots;
public:
~Signal() noexcept;
void connect(Error(*f)(Args...)) const noexcept;
template<typename T, typename Method>
void connect(const T *receiver, Method methodPtr) const noexcept;
template<typename T, typename Method>
void connect(T *receiver, Method methodPtr) const noexcept;
template<class... SubArgs, typename Method>
void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;
template<class... SubArgs>
Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const noexcept;
Error emitCheckError(Args... args) const noexcept;
};
extern template class Signal<Error(const SignalHandler*)>;
class SignalHandler {
public:
Signal<Error(const SignalHandler*)> destruction;
constexpr SignalHandler() noexcept = default;
SignalHandler(const SignalHandler&) = delete;
SignalHandler(SignalHandler&) = delete;
SignalHandler(SignalHandler&&) = delete;
virtual ~SignalHandler() noexcept;
};
template<class... Args>
Signal<Error(Args...)>::~Signal() noexcept {
for (auto &slot : m_slots) {
slot->cleanup(this);
}
}
template<class... Args>
void Signal<Error(Args...)>::connect(Error(*f)(Args...)) const noexcept {
m_slots.emplace_back(new FunctionSlot(f));
}
template<class... Args>
template<typename T, typename Method>
void Signal<Error(Args...)>::connect(const T *receiver, Method methodPtr) const noexcept {
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
}
template<class... Args>
template<typename T, typename Method>
void Signal<Error(Args...)>::connect(T *receiver, Method methodPtr) const noexcept {
receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
}
template<class... Args>
template<class... SubArgs, typename Method>
void Signal<Error(Args...)>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
}
template<class... Args>
template<class... SubArgs>
Error Signal<Error(Args...)>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
return disconnectObject(receiver);
}
template<class... Args>
Error Signal<Error(Args...)>::disconnectObject(const void *receiver) const noexcept {
for (auto i = 0u; i < m_slots.size(); ++i) {
const auto &slot = m_slots[i];
if (slot->receiver() == receiver) {
OX_RETURN_ERROR(m_slots.erase(i));
--i;
}
}
return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
}
template<class... Args>
void Signal<Error(Args...)>::emit(Args... args) const noexcept {
for (auto &f : m_slots) {
std::ignore = f->call(ox::forward<Args>(args)...);
}
}
template<class... Args>
Error Signal<Error(Args...)>::emitCheckError(Args... args) const noexcept {
for (auto &f : m_slots) {
OX_RETURN_ERROR(f->call(ox::forward<Args>(args)...));
}
return {};
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/event/signal.hpp>
namespace ox {
#ifndef OX_OS_BareMetal
template class Signal<const SignalHandler*>;
#endif
template class Signal<Error(const SignalHandler*)>;
SignalHandler::~SignalHandler() noexcept {
destruction.emit(this);
}
}
/*
* 1. ProjectExplorer::fileOpened cannot notify StudioUI that it is being destroyed.
* 2. StudioUI tries to unsubscribe from ProjectExplorer::fileOpened upon its destruction.
* 3. Segfault
*/
+10
View File
@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.10)
add_executable(
EventTest
tests.cpp
)
target_link_libraries(EventTest OxEvent)
add_test("[ox/event] Test 1" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/EventTest "test1")
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#undef NDEBUG
#include <map>
#include <functional>
#include <ox/event/signal.hpp>
#include <ox/std/std.hpp>
struct TestStruct: public ox::SignalHandler {
int value = 0;
ox::Error method(int i) noexcept {
value = i;
return ox::Error(0);
}
};
std::map<ox::StringView, std::function<ox::Error()>> tests = {
{
"test1",
[] {
ox::Signal<ox::Error(int)> signal;
signal.connect([](int i) -> ox::Error {
return ox::Error(i != 5);
});
TestStruct ts;
signal.connect(&ts, &TestStruct::method);
OX_RETURN_ERROR(signal.emitCheckError(5));
OX_RETURN_ERROR(ox::Error(ts.value != 5));
return ox::Error(0);
}
},
};
int main(int argc, const char **args) {
if (argc < 2) {
oxError("Must specify test to run");
}
auto const testName = args[1];
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(), "Test returned Error");
return 0;
}
return -1;
}
+74
View File
@@ -0,0 +1,74 @@
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
# enable warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
endif()
add_library(
OxFS
src/filestore/filestoretemplate.cpp
src/filesystem/filelocation.cpp
src/filesystem/pathiterator.cpp
src/filesystem/directory.cpp
src/filesystem/filesystem.cpp
src/filesystem/passthroughfs.cpp
)
if(NOT MSVC)
target_compile_options(OxFS PRIVATE -Wsign-conversion)
endif()
if(NOT OX_BARE_METAL)
set_property(
TARGET
OxFS
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
endif()
target_link_libraries(
OxFS PUBLIC
OxMetalClaw
)
target_include_directories(
OxFS PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
if(NOT OX_BARE_METAL)
add_executable(
oxfs-tool
src/tool.cpp
)
target_link_libraries(
oxfs-tool
OxFS
)
install(
TARGETS
oxfs-tool
DESTINATION
bin
)
endif()
install(
TARGETS
OxFS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
@@ -0,0 +1,777 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/fs/ptrarith/nodebuffer.hpp>
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
using InodeId_t = uint64_t;
using FsSize_t = std::size_t;
struct StatInfo {
InodeId_t inode = 0;
InodeId_t links = 0;
FsSize_t size = 0;
uint8_t fileType = 0;
};
template<typename size_t>
struct OX_PACKED FileStoreItem: public ptrarith::Item<size_t> {
LittleEndian<size_t> id = 0;
LittleEndian<uint8_t> fileType = 0;
LittleEndian<size_t> links = 0;
LittleEndian<size_t> left = 0;
LittleEndian<size_t> right = 0;
FileStoreItem() = default;
explicit FileStoreItem(size_t size) {
this->setSize(size);
}
/**
* @return the size of the data + the size of the Item type
*/
[[nodiscard]]
size_t fullSize() const {
return sizeof(*this) + this->size();
}
ptrarith::Ptr<uint8_t, std::size_t> data() {
return ptrarith::Ptr<uint8_t, std::size_t>(this, this->fullSize(), sizeof(*this), this->size());
}
};
template<typename size_t>
class FileStoreTemplate {
public:
using InodeId_t = size_t;
private:
using Item = FileStoreItem<size_t>;
using ItemPtr = typename ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>::ItemPtr;
using Buffer = ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>;
static constexpr InodeId_t ReservedInodeEnd = 100;
struct OX_PACKED FileStoreData {
LittleEndian<size_t> rootNode = 0;
Random random;
};
size_t m_buffSize = 0;
mutable Buffer *m_buffer = nullptr;
public:
FileStoreTemplate() = default;
FileStoreTemplate(void *buff, std::size_t buffSize);
static Error format(void *buffer, std::size_t bufferSize);
Error setSize(std::size_t buffSize);
Error incLinks(uint64_t id);
Error decLinks(uint64_t id);
Error write(uint64_t id64, const void *data, FsSize_t dataSize, uint8_t fileType = 0);
Error remove(uint64_t id);
Error read(uint64_t id, void *out, FsSize_t outSize, FsSize_t *size = nullptr) const;
Error read(uint64_t id, FsSize_t readStart, FsSize_t readSize, void *data, FsSize_t *size = nullptr) const;
ptrarith::Ptr<uint8_t, std::size_t> read(uint64_t id) const;
/**
* Reads the "file" at the given id. You are responsible for freeing
* the data when done with it.
* @param id id of the "file"
* @param readStart where in the data to start reading
* @param readSize how much data to read
* @param data pointer to the pointer where the data is stored
* @param size pointer to a value that will be assigned the size of data
* @return 0 if read is a success
*/
template<typename T>
Error read(uint64_t id, FsSize_t readStart,
FsSize_t readSize, T *data,
FsSize_t *size) const;
Result<StatInfo> stat(uint64_t id) const;
Error resize();
Error resize(std::size_t size, void *newBuff = nullptr);
[[nodiscard]]
InodeId_t spaceNeeded(FsSize_t size) const;
[[nodiscard]]
InodeId_t size() const;
[[nodiscard]]
InodeId_t available() const;
[[nodiscard]]
char *buff();
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t));
Result<InodeId_t> generateInodeId();
bool valid() const;
Error compact();
private:
FileStoreData *fileStoreData() const;
/**
* Places the given Item at the given ID. If it already exists, the
* existing value will be overwritten.
*/
Error placeItem(ItemPtr item);
/**
* Places the given Item at the given ID. If it already exists, the
* existing value will be overwritten.
*/
Error placeItem(ItemPtr root, ItemPtr item, int depth = 0);
/**
* Removes the given Item at the given ID. If it already exists, the
* existing value will be overwritten.
*/
Error unplaceItem(ItemPtr item);
/**
* Removes the given Item at the given ID. If it already exists, the
* existing value will be overwritten.
*/
Error unplaceItem(ItemPtr root, ItemPtr item, int depth = 0);
Error remove(ItemPtr item);
/**
* Finds the parent an inode by its ID.
*/
ItemPtr findParent(ItemPtr item, size_t id, size_t oldAddr) const;
/**
* Finds an inode by its ID.
*/
ItemPtr find(ItemPtr item, InodeId_t id, int depth = 0) const;
/**
* Finds an inode by its ID.
*/
ItemPtr find(InodeId_t id) const;
/**
* Gets the root inode.
*/
ItemPtr rootInode();
bool canWrite(ItemPtr existing, std::size_t size);
};
template<typename size_t>
FileStoreTemplate<size_t>::FileStoreTemplate(void *buff, std::size_t buffSize) {
m_buffSize = static_cast<size_t>(buffSize);
m_buffer = reinterpret_cast<ptrarith::NodeBuffer<size_t, FileStoreItem<size_t>>*>(buff);
if (!m_buffer->valid(m_buffSize)) {
m_buffSize = 0;
m_buffer = nullptr;
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::format(void *buffer, std::size_t bufferSize) {
auto nb = new (buffer) Buffer(static_cast<size_t>(bufferSize));
auto fsData = nb->malloc(sizeof(FileStoreData)).value;
if (!fsData.valid()) {
oxTrace("ox.fs.FileStoreTemplate.format.fail", "Could not read data section of FileStoreData");
return ox::Error(1, "Could not read data section of FileStoreData");
}
auto data = nb->template dataOf<FileStoreData>(fsData);
if (!data.valid()) {
oxTrace("ox.fs.FileStoreTemplate.format.fail", "Could not read data section of FileStoreData");
return ox::Error(1, "Could not read data section of FileStoreData");
}
new (data) FileStoreData;
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::setSize(std::size_t size) {
if (m_buffSize >= size) {
return m_buffer->setSize(static_cast<size_t>(size));
}
return ox::Error(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::incLinks(uint64_t id) {
OX_REQUIRE_M(item, find(static_cast<size_t>(id)).validate());
++item->links;
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::decLinks(uint64_t id) {
OX_REQUIRE_M(item, find(static_cast<size_t>(id)).validate());
--item->links;
if (item->links == 0) {
OX_RETURN_ERROR(remove(item));
}
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::write(uint64_t id64, const void *data, FsSize_t dataSize, uint8_t fileType) {
const auto id = static_cast<size_t>(id64);
oxTracef("ox.fs.FileStoreTemplate.write", "Attempting to write to inode {}", id);
auto existing = find(id);
if (!canWrite(existing, dataSize)) {
OX_RETURN_ERROR(compact());
existing = find(id);
}
if (canWrite(existing, dataSize)) {
// delete the old node if it exists
if (existing.valid()) {
oxTracef("ox.fs.FileStoreTemplate.write", "Freeing old version of inode found at offset: {}", existing.offset());
auto err = m_buffer->free(existing);
if (err) {
oxTrace("ox.fs.FileStoreTemplate.write.fail", "Free of old version of inode failed");
return err;
}
existing = nullptr;
}
// write the given data
auto dest = m_buffer->malloc(dataSize).value;
// if first malloc failed, compact and try again
if (!dest.valid()) {
oxTrace("ox.fs.FileStoreTemplate.write", "Allocation failed, compacting");
OX_RETURN_ERROR(compact());
dest = m_buffer->malloc(dataSize).value;
}
if (dest.valid()) {
oxTrace("ox.fs.FileStoreTemplate.write", "Memory allocated");
dest->id = id;
dest->fileType = fileType;
auto destData = m_buffer->template dataOf<uint8_t>(dest);
if (destData.valid()) {
oxAssert(destData.size() == dataSize, "Allocation size does not match data.");
// write data if any was provided
if (data != nullptr) {
ox::memcpy(destData, data, dest->size());
oxTrace("ox.fs.FileStoreTemplate.write", "Data written");
}
auto fsData = fileStoreData();
if (fsData) {
oxTracef("ox.fs.FileStoreTemplate.write", "Searching for root node at {}", fsData->rootNode.get());
auto root = m_buffer->ptr(fsData->rootNode);
if (root.valid()) {
oxTracef("ox.fs.FileStoreTemplate.write",
"Placing {} on {} at {}", dest->id.get(), root->id.get(), destData.offset());
return placeItem(dest);
} else {
oxTracef("ox.fs.FileStoreTemplate.write",
"Initializing root inode: {} (offset: {}, data size: {})",
dest->id.get(), dest.offset(), destData.size());
fsData->rootNode = dest.offset();
oxTracef("ox.fs.FileStoreTemplate.write", "Root inode: {}", dest->id.get());
return {};
}
} else {
oxTrace("ox.fs.FileStoreTemplate.write.fail", "Could not place item due to absence of FileStore header.");
}
}
}
OX_RETURN_ERROR(m_buffer->free(dest));
}
return ox::Error(1);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::remove(uint64_t id) {
return remove(find(static_cast<size_t>(id)));
}
template<typename size_t>
Error FileStoreTemplate<size_t>::read(uint64_t id, void *out, FsSize_t outSize, FsSize_t *size) const {
oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read inode {}", id);
auto src = find(static_cast<size_t>(id));
// error check
if (!src.valid()) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id);
return ox::Error(1, "Could not find requested item");
}
auto srcData = m_buffer->template dataOf<uint8_t>(src);
oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}",
id, src.offset(), srcData.offset());
oxTracef("ox.fs.FileStoreTemplate.read.outSize", "{} {} {}", srcData.offset(), srcData.size(), outSize);
// error check
if (!(srcData.valid() && srcData.size() <= outSize)) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id);
oxTracef("ox.fs.FileStoreTemplate.read.fail",
"Item data section size: {}, Expected size: {}", srcData.size(), outSize);
return ox::Error(1, "Invalid inode");
}
ox::memcpy(out, srcData, srcData.size());
if (size) {
*size = src.size();
}
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::read(uint64_t id, FsSize_t readStart, FsSize_t readSize, void *out, FsSize_t *size) const {
oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read from inode {}", id);
auto src = find(static_cast<size_t>(id));
// error check
if (!src.valid()) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id);
return ox::Error(1);
}
auto srcData = m_buffer->template dataOf<uint8_t>(src);
oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}",
id, src.offset(), srcData.offset());
oxTracef("ox.fs.FileStoreTemplate.read.readSize", "{} {} {}", srcData.offset(), srcData.size(), readSize);
// error check
if (!(srcData.valid() && srcData.size() - readStart <= readSize)) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id);
oxTracef("ox.fs.FileStoreTemplate.read.fail",
"Item data section size: {}, Expected size: {}", srcData.size(), readSize);
return ox::Error(1);
}
ox::memcpy(out, srcData.get() + readStart, readSize);
if (size) {
*size = src.size();
}
return {};
}
template<typename size_t>
template<typename T>
Error FileStoreTemplate<size_t>::read(uint64_t id, FsSize_t readStart,
FsSize_t readSize, T *out, FsSize_t *size) const {
oxTracef("ox.fs.FileStoreTemplate.read", "Attempting to read from inode {}", id);
auto src = find(static_cast<size_t>(id));
// error check
if (!src.valid()) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not find requested item: {}", id);
return ox::Error(1);
}
auto srcData = m_buffer->template dataOf<uint8_t>(src);
oxTracef("ox.fs.FileStoreTemplate.read.found", "{} found at {} with data section at {}",
id, src.offset(), srcData.offset());
oxTracef("ox.fs.FileStoreTemplate.read.readSize", "{} {} {}", srcData.offset(), srcData.size(), readSize);
// error check
if (!(srcData.valid() && srcData.size() - readStart <= readSize)) {
oxTracef("ox.fs.FileStoreTemplate.read.fail", "Could not read data section of item: {}", id);
oxTracef("ox.fs.FileStoreTemplate.read.fail",
"Item data section size: {}, Expected size: {}", srcData.size(), readSize);
return ox::Error(1);
}
ox::memcpy(out, srcData.get() + readStart, readSize);
if (size) {
*size = src.size();
}
return {};
}
template<typename size_t>
ptrarith::Ptr<uint8_t, std::size_t> FileStoreTemplate<size_t>::read(uint64_t id) const {
auto item = find(static_cast<size_t>(id));
if (item.valid()) {
return item->data();
} else {
return nullptr;
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::resize() {
OX_RETURN_ERROR(compact());
const auto newSize = static_cast<std::size_t>(size() - available());
oxTracef("ox.fs.FileStoreTemplate.resize", "resize to: {}", newSize);
OX_RETURN_ERROR(m_buffer->setSize(newSize));
oxTracef("ox.fs.FileStoreTemplate.resize", "resized to: {}", m_buffer->size());
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::resize(std::size_t size, void *newBuff) {
if (m_buffer->size() > size) {
return ox::Error{1, "new buffer is too small for existing data"};
}
m_buffSize = static_cast<size_t>(size);
if (newBuff) {
m_buffer = static_cast<Buffer*>(newBuff);
OX_RETURN_ERROR(m_buffer->setSize(static_cast<size_t>(size)));
}
return {};
}
template<typename size_t>
Result<StatInfo> FileStoreTemplate<size_t>::stat(uint64_t id) const {
OX_REQUIRE(inode, find(static_cast<size_t>(id)).validate());
return StatInfo {
id,
inode->links,
inode->size(),
inode->fileType,
};
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::spaceNeeded(FsSize_t size) const {
return m_buffer->spaceNeeded(size);
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::size() const {
return m_buffer->size();
}
template<typename size_t>
typename FileStoreTemplate<size_t>::InodeId_t FileStoreTemplate<size_t>::available() const {
return m_buffer->available();
}
template<typename size_t>
char *FileStoreTemplate<size_t>::buff() {
return reinterpret_cast<char*>(m_buffer);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) {
for (auto i = m_buffer->iterator(); i.valid(); i.next()) {
OX_RETURN_ERROR(cb(i->fileType, i.ptr().offset(), i.ptr().end()));
}
return {};
}
template<typename size_t>
Result<typename FileStoreTemplate<size_t>::InodeId_t> FileStoreTemplate<size_t>::generateInodeId() {
auto fsData = fileStoreData();
if (!fsData) {
return ox::Error(1);
}
for (auto i = 0; i < 100; i++) {
auto inode = static_cast<typename FileStoreTemplate<size_t>::InodeId_t>(fsData->random.gen() % MaxValue<InodeId_t>);
if (inode > ReservedInodeEnd && !find(static_cast<size_t>(inode)).valid()) {
return inode;
}
}
return ox::Error(2);
}
template<typename size_t>
Error FileStoreTemplate<size_t>::compact() {
auto isFirstItem = true;
return m_buffer->compact([this, &isFirstItem](uint64_t oldAddr, ItemPtr item) -> Error {
if (isFirstItem) {
isFirstItem = false;
return {};
}
if (!item.valid()) {
return ox::Error(1);
}
oxTracef("ox.fs.FileStoreTemplate.compact.moveItem", "Moving Item: {} from {} to {}", item->id.get(), oldAddr, item.offset());
// update rootInode if this is it
auto fsData = fileStoreData();
if (fsData && oldAddr == fsData->rootNode) {
fsData->rootNode = item.offset();
}
auto parent = findParent(rootInode(), item->id, static_cast<size_t>(oldAddr));
oxAssert(parent.valid() || rootInode() == item.offset(),
"Parent inode not found for item that should have parent.");
if (parent.valid()) {
if (parent->left == oldAddr) {
parent->left = item;
} else if (parent->right == oldAddr) {
parent->right = item;
}
}
return {};
});
}
template<typename size_t>
typename FileStoreTemplate<size_t>::FileStoreData *FileStoreTemplate<size_t>::fileStoreData() const {
auto first = m_buffer->firstItem();
if (first.valid()) {
auto data = first->data();
if (data.valid()) {
return reinterpret_cast<FileStoreData*>(data.get());
}
}
return nullptr;
}
template<typename size_t>
Error FileStoreTemplate<size_t>::placeItem(ItemPtr item) {
auto fsData = fileStoreData();
if (!fsData) {
return ox::Error(1);
}
OX_REQUIRE_M(root, m_buffer->ptr(fsData->rootNode).validate());
if (root->id == item->id) {
fsData->rootNode = item;
item->left = root->left;
item->right = root->right;
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Overwrote Root Item: {}", item->id.get());
return {};
} else {
return placeItem(root, item);
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::placeItem(ItemPtr root, ItemPtr item, int depth) {
if (depth > 5000) {
oxTrace("ox.fs.FileStoreTemplate.placeItem.fail", "Excessive recursion depth, stopping before stack overflow.");
return ox::Error(2);
}
if (item->id > root->id) {
auto right = m_buffer->ptr(root->right);
if (!right.valid() || right->id == item->id) {
root->right = item.offset();
if (right.valid()) {
item->left = right->left;
item->right = right->right;
}
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get());
return {};
} else {
return placeItem(right, item, depth + 1);
}
} else if (item->id < root->id) {
auto left = m_buffer->ptr(root->left);
if (!left.valid() || left->id == item->id) {
root->left = item.offset();
if (left.valid()) {
item->left = left->left;
item->right = left->right;
}
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get());
return {};
} else {
return placeItem(left, item, depth + 1);
}
} else {
oxTrace("ox.fs.FileStoreTemplate.placeItem.fail", "Cannot insert an item on itself.");
return ox::Error(1, "Cannot insert an item on itself.");
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr item) {
auto fsData = fileStoreData();
if (!fsData) {
return ox::Error(1);
}
OX_REQUIRE_M(root, m_buffer->ptr(fsData->rootNode).validate());
if (root->id == item->id) {
item->left = root->left;
item->right = root->right;
auto left = m_buffer->ptr(item->left);
auto right = m_buffer->ptr(item->right);
if (right.valid()) {
auto oldRoot = fsData->rootNode;
fsData->rootNode = item->right;
if (left.valid()) {
auto err = placeItem(left);
// undo if unable to place the left side of the tree
if (err) {
fsData->rootNode = oldRoot;
return err;
}
}
} else if (left.valid()) {
fsData->rootNode = item->left;
} else {
fsData->rootNode = 0;
}
return {};
} else {
return unplaceItem(root, item);
}
}
template<typename size_t>
Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr root, ItemPtr item, int depth) {
if (depth >= 5000) {
oxTrace("ox.fs.FileStoreTemplate.unplaceItem.fail", "Excessive recursion depth, stopping before stack overflow.");
return ox::Error(1, "Excessive recursion depth, stopping before stack overflow.");
}
if (item->id > root->id) {
auto right = m_buffer->ptr(root->right);
if (right->id == item->id) {
root->right = 0;
oxTracef("ox.fs.FileStoreTemplate.unplaceItem", "Unplaced Item: {}", item->id.get());
} else {
return unplaceItem(right, item, depth + 1);
}
} else if (item->id < root->id) {
auto left = m_buffer->ptr(root->left);
if (left->id == item->id) {
root->left = 0;
oxTracef("ox.fs.FileStoreTemplate.unplaceItem", "Unplaced Item: {}", item->id.get());
} else {
return unplaceItem(left, item, depth + 1);
}
} else {
return ox::Error(1);
}
if (item->right) {
OX_RETURN_ERROR(placeItem(m_buffer->ptr(item->right)));
}
if (item->left) {
OX_RETURN_ERROR(placeItem(m_buffer->ptr(item->left)));
}
return {};
}
template<typename size_t>
Error FileStoreTemplate<size_t>::remove(ItemPtr item) {
if (item.valid()) {
OX_RETURN_ERROR(unplaceItem(item));
OX_RETURN_ERROR(m_buffer->free(item));
return {};
}
return ox::Error(1);
}
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::findParent(ItemPtr item, size_t id, size_t oldAddr) const {
// This is a little bit confusing. findParent uses the inode ID to find
// where the target ID should be, but the actual address of that item is
// currently invalid, so we check it against what is known to be the old
// address of the item to confirm that we have the right item.
if (item.valid()) {
if (id > item->id) {
if (item->right == oldAddr) {
return item;
} else {
auto right = m_buffer->ptr(item->right);
return findParent(right, id, oldAddr);
}
} else if (id < item->id) {
if (item->left == oldAddr) {
return item;
} else {
auto left = m_buffer->ptr(item->left);
return findParent(left, id, oldAddr);
}
}
}
return nullptr;
}
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::find(ItemPtr item, InodeId_t id, int depth) const {
if (depth > 5000) {
oxTracef("ox.fs.FileStoreTemplate.find.fail", "Excessive recursion depth, stopping before stack overflow. Search for: {}", id);
return nullptr;
}
if (!item.valid()) {
oxTrace("ox.fs.FileStoreTemplate.find.fail", "item invalid");
return nullptr;
}
// do search
if (id > item->id) {
oxTracef("ox.fs.FileStoreTemplate.find", "Not a match, searching on {}", item->right.get());
return find(m_buffer->ptr(item->right), id, depth + 1);
} else if (id < item->id) {
oxTracef("ox.fs.FileStoreTemplate.find", "Not a match, searching on {}", item->left.get());
return find(m_buffer->ptr(item->left), id, depth + 1);
} else if (id == item->id) {
oxTracef("ox.fs.FileStoreTemplate.find", "Found {} at {}", id, item.offset());
return item;
}
return nullptr;
}
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::find(InodeId_t id) const {
oxTracef("ox.fs.FileStoreTemplate.find", "Searching for inode: {}", id);
auto fsData = fileStoreData();
if (fsData) {
auto root = m_buffer->ptr(fsData->rootNode);
if (root.valid()) {
auto item = find(root, id);
return item;
} else {
oxTrace("ox.fs.FileStoreTemplate.find.fail", "No root node");
}
} else {
oxTrace("ox.fs.FileStoreTemplate.find.fail", "No FileStore Data");
}
return nullptr;
}
/**
* Gets the root inode.
*/
template<typename size_t>
typename FileStoreTemplate<size_t>::ItemPtr FileStoreTemplate<size_t>::rootInode() {
auto fsData = fileStoreData();
if (fsData) {
return m_buffer->ptr(fsData->rootNode);
} else {
return nullptr;
}
}
template<typename size_t>
bool FileStoreTemplate<size_t>::canWrite(ItemPtr existing, std::size_t size) {
const auto sz = static_cast<size_t>(size);
return existing.size() >= sz || m_buffer->spaceNeeded(sz) <= m_buffer->available();
}
template<typename size_t>
bool FileStoreTemplate<size_t>::valid() const {
return m_buffer;
}
extern template class FileStoreTemplate<uint16_t>;
extern template class FileStoreTemplate<uint32_t>;
using FileStore16 = FileStoreTemplate<uint16_t>;
using FileStore32 = FileStoreTemplate<uint32_t>;
}
OX_CLANG_NOWARN_END
@@ -0,0 +1,339 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/fs/filesystem/pathiterator.hpp>
#include <ox/fs/filestore/filestoretemplate.hpp>
#include <ox/fs/ptrarith/nodebuffer.hpp>
#include <ox/std/byteswap.hpp>
#include "types.hpp"
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
template<typename InodeId_t>
struct OX_PACKED DirectoryEntry {
public:
struct OX_PACKED DirectoryEntryData {
// DirectoryEntry fields
LittleEndian<InodeId_t> inode = 0;
char name[MaxFileNameLength];
static constexpr std::size_t spaceNeeded(std::size_t chars) {
return offsetof(DirectoryEntryData, name) + chars;
}
};
// NodeBuffer fields
LittleEndian<InodeId_t> prev = 0;
LittleEndian<InodeId_t> next = 0;
private:
LittleEndian<InodeId_t> m_bufferSize = sizeof(DirectoryEntry);
public:
constexpr DirectoryEntry() noexcept = default;
Error init(InodeId_t inode, ox::StringViewCR name, size_t bufferSize) noexcept {
oxTracef("ox.fs.DirectoryEntry.init", "inode: {}, name: {}, bufferSize: {}", inode, name, bufferSize);
m_bufferSize = static_cast<InodeId_t>(bufferSize);
auto d = data();
if (d.valid()) {
d->inode = inode;
auto const maxStrSz = bufferSize - 1 - sizeof(*this);
ox::strncpy(d->name, name.data(), ox::min(maxStrSz, name.size()));
return {};
}
return ox::Error(1);
}
ptrarith::Ptr<DirectoryEntryData, InodeId_t> data() noexcept {
oxTracef("ox.fs.DirectoryEntry.data", "{} {} {}", this->fullSize(), sizeof(*this), this->size());
return ptrarith::Ptr<DirectoryEntryData, InodeId_t>(this, this->fullSize(), sizeof(*this), this->size(), this->size());
}
/**
* @return the size of the data + the size of the Item type
*/
[[nodiscard]]
InodeId_t fullSize() const {
return m_bufferSize;
}
[[nodiscard]]
InodeId_t size() const {
return fullSize() - static_cast<InodeId_t>(sizeof(*this));
}
void setSize(std::size_t) {
// ignore set value
}
static constexpr std::size_t spaceNeeded(std::size_t chars) {
return sizeof(DirectoryEntry) + offsetof(DirectoryEntryData, name) + chars;
}
};
template<typename FileStore, typename InodeId_t>
class Directory {
private:
using Buffer = ptrarith::NodeBuffer<InodeId_t, DirectoryEntry<InodeId_t>>;
InodeId_t m_inodeId = 0;
std::size_t m_size = 0;
FileStore m_fs;
public:
Directory() noexcept = default;
Directory(FileStore fs, uint64_t inode) noexcept;
/**
* Initializes Directory.
*/
Error init() noexcept;
Error mkdir(PathIterator path, bool parents);
/**
* @param parents indicates the operation should create non-existent directories in the path, like mkdir -p
*/
Error write(PathIterator path, uint64_t inode64) noexcept;
Error remove(PathIterator path) noexcept;
template<typename F>
Error ls(F cb) noexcept;
[[nodiscard]]
Result<typename FileStore::InodeId_t> findEntry(const StringView &name) const noexcept;
[[nodiscard]]
Result<typename FileStore::InodeId_t> find(PathIterator path) const noexcept;
};
template<typename FileStore, typename InodeId_t>
Directory<FileStore, InodeId_t>::Directory(FileStore fs, uint64_t inodeId) noexcept {
m_fs = fs;
m_inodeId = static_cast<InodeId_t>(inodeId);
auto buff = m_fs.read(static_cast<InodeId_t>(inodeId)).template to<Buffer>();
if (buff.valid()) {
m_size = buff.size();
}
}
template<typename FileStore, typename InodeId_t>
Error Directory<FileStore, InodeId_t>::init() noexcept {
constexpr auto Size = sizeof(Buffer);
oxTracef("ox.fs.Directory.init", "Initializing Directory with Inode ID: {}", m_inodeId);
OX_RETURN_ERROR(m_fs.write(m_inodeId, nullptr, Size, static_cast<uint8_t>(FileType::Directory)));
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
if (!buff.valid()) {
m_size = 0;
return ox::Error(1);
}
new (buff) Buffer(Size);
m_size = Size;
return {};
}
template<typename FileStore, typename InodeId_t>
Error Directory<FileStore, InodeId_t>::mkdir(PathIterator path, bool parents) {
if (path.valid()) {
oxTrace("ox.fs.Directory.mkdir", path.fullPath());
// determine if already exists
ox::StringView name;
OX_RETURN_ERROR(path.get(name));
auto childInode = find(PathIterator(name));
if (!childInode.ok()) {
// if this is not the last item in the path and parents is disabled,
// return an error
if (!parents && path.hasNext()) {
return ox::Error(1);
}
childInode = m_fs.generateInodeId();
oxTracef("ox.fs.Directory.mkdir", "Generated Inode ID: {}", childInode.value);
oxLogError(childInode.error);
OX_RETURN_ERROR(childInode.error);
// initialize the directory
Directory<FileStore, InodeId_t> child(m_fs, childInode.value);
OX_RETURN_ERROR(child.init());
auto err = write(PathIterator(name), childInode.value);
if (err) {
oxLogError(err);
// could not index the directory, delete it
oxLogError(m_fs.remove(childInode.value));
return err;
}
}
Directory<FileStore, InodeId_t> child(m_fs, childInode.value);
if (path.hasNext()) {
OX_RETURN_ERROR(child.mkdir(path.next(), parents));
}
}
return {};
}
template<typename FileStore, typename InodeId_t>
Error Directory<FileStore, InodeId_t>::write(PathIterator path, uint64_t inode64) noexcept {
const auto inode = static_cast<InodeId_t>(inode64);
ox::StringView name;
if (path.next().hasNext()) { // not yet at target directory, recurse to next one
oxTracef("ox.fs.Directory.write", "Attempting to write to next sub-Directory: {} of {}",
name, path.fullPath());
OX_RETURN_ERROR(path.get(name));
OX_REQUIRE(nextChild, findEntry(name));
oxTracef("ox.fs.Directory.write", "{}: {}", name, nextChild);
if (nextChild) {
return Directory(m_fs, nextChild).write(path.next(), inode);
} else {
oxTracef("ox.fs.Directory.write", "{} not found and not allowed to create it.", name);
return ox::Error(1, "File not found and not allowed to create it.");
}
} else {
oxTrace("ox.fs.Directory.write", path.fullPath());
OX_RETURN_ERROR(path.next(name));
// insert the new entry on this directory
// get the name
// find existing version of directory
oxTracef("ox.fs.Directory.write", "Searching for directory inode {}", m_inodeId);
OX_REQUIRE(oldStat, m_fs.stat(m_inodeId));
oxTracef("ox.fs.Directory.write", "Found existing directory of size {}", oldStat.size);
auto old = m_fs.read(m_inodeId).template to<Buffer>();
if (!old.valid()) {
oxTrace("ox.fs.Directory.write.fail", "Could not read existing version of Directory");
return ox::Error(1, "Could not read existing version of Directory");
}
const auto pathSize = name.size() + 1;
const auto entryDataSize = DirectoryEntry<InodeId_t>::DirectoryEntryData::spaceNeeded(pathSize);
const auto newSize = oldStat.size + Buffer::spaceNeeded(entryDataSize);
auto cpy = ox_malloca(newSize, Buffer, *old, oldStat.size);
if (cpy == nullptr) {
oxTrace("ox.fs.Directory.write.fail", "Could not allocate memory for copy of Directory");
return ox::Error(1, "Could not allocate memory for copy of Directory");
}
OX_RETURN_ERROR(cpy->setSize(newSize));
auto val = cpy->malloc(entryDataSize).value;
if (!val.valid()) {
oxTrace("ox.fs.Directory.write.fail", "Could not allocate memory for new directory entry");
return ox::Error(1, "Could not allocate memory for new directory entry");
}
oxTracef("ox.fs.Directory.write", "Attempting to write Directory entry: {}", name);
OX_RETURN_ERROR(val->init(inode, name, val.size()));
return m_fs.write(m_inodeId, cpy.get(), cpy->size(), static_cast<uint8_t>(FileType::Directory));
}
}
template<typename FileStore, typename InodeId_t>
Error Directory<FileStore, InodeId_t>::remove(PathIterator path) noexcept {
ox::StringView name;
OX_RETURN_ERROR(path.get(name));
oxTrace("ox.fs.Directory.remove", name);
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
if (buff.valid()) {
oxTrace("ox.fs.Directory.remove", "Found directory buffer.");
for (auto i = buff->iterator(); i.valid(); i.next()) {
auto data = i->data();
if (data.valid()) {
if (name == data->name) {
OX_RETURN_ERROR(buff->free(i));
}
} else {
oxTrace("ox.fs.Directory.remove", "INVALID DIRECTORY ENTRY");
}
}
} else {
oxTrace("ox.fs.Directory.remove.fail", "Could not find directory buffer");
return ox::Error(1, "Could not find directory buffer");
}
return {};
}
template<typename FileStore, typename InodeId_t>
template<typename F>
Error Directory<FileStore, InodeId_t>::ls(F cb) noexcept {
oxTrace("ox.fs.Directory.ls");
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
if (!buff.valid()) {
oxTrace("ox.fs.Directory.ls.fail", "Could not directory buffer");
return ox::Error(1, "Could not directory buffer");
}
oxTrace("ox.fs.Directory.ls", "Found directory buffer.");
for (auto i = buff->iterator(); i.valid(); i.next()) {
auto data = i->data();
if (data.valid()) {
OX_RETURN_ERROR(cb(data->name, data->inode));
} else {
oxTrace("ox.fs.Directory.ls", "INVALID DIRECTORY ENTRY");
}
}
return {};
}
template<typename FileStore, typename InodeId_t>
Result<typename FileStore::InodeId_t> Directory<FileStore, InodeId_t>::findEntry(StringViewCR name) const noexcept {
oxTrace("ox.fs.Directory.findEntry", name);
auto buff = m_fs.read(m_inodeId).template to<Buffer>();
if (!buff.valid()) {
oxTrace("ox.fs.Directory.findEntry.fail", "Could not findEntry directory buffer");
return ox::Error(2, "Could not findEntry directory buffer");
}
oxTracef("ox.fs.Directory.findEntry", "Found directory buffer, size: {}", buff.size());
for (auto i = buff->iterator(); i.valid(); i.next()) {
auto data = i->data();
if (data.valid()) {
oxTracef("ox.fs.Directory.findEntry", "Comparing \"{}\" to \"{}\"", name, data->name);
if (name == data->name) {
oxTracef("ox.fs.Directory.findEntry", "\"{}\" match found.", name);
return static_cast<InodeId_t>(data->inode);
}
} else {
oxTrace("ox.fs.Directory.findEntry", "INVALID DIRECTORY ENTRY");
}
}
oxTrace("ox.fs.Directory.findEntry.fail", "Entry not present");
return ox::Error(1, "Entry not present");
}
template<typename FileStore, typename InodeId_t>
Result<typename FileStore::InodeId_t> Directory<FileStore, InodeId_t>::find(PathIterator path) const noexcept {
// determine if already exists
ox::StringView name;
OX_RETURN_ERROR(path.get(name));
OX_REQUIRE(v, findEntry(name));
// recurse if not at end of path
if (auto p = path.next(); p.valid()) {
Directory dir(m_fs, v);
return dir.find(p);
}
return v;
}
extern template class Directory<FileStore16, uint16_t>;
extern template class Directory<FileStore32, uint32_t>;
extern template struct DirectoryEntry<uint16_t>;
extern template struct DirectoryEntry<uint32_t>;
using Directory16 = Directory<FileStore16, uint16_t>;
using Directory32 = Directory<FileStore32, uint32_t>;
}
OX_CLANG_NOWARN_END
@@ -0,0 +1,183 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/std.hpp>
#include <ox/model/def.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/model/types.hpp>
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
enum class FileAddressType: int8_t {
None = -1,
Path,
ConstPath,
Inode,
};
class FileAddress {
template<typename T>
friend constexpr Error model(T *h, CommonPtrWith<FileAddress> auto *fa) noexcept;
public:
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress";
static constexpr auto TypeVersion = 1;
union Data {
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress.Data";
static constexpr auto TypeVersion = 1;
char *path = nullptr;
const char *constPath;
uint64_t inode;
};
protected:
FileAddressType m_type = FileAddressType::None;
Data m_data{};
public:
constexpr FileAddress() noexcept = default;
FileAddress(const FileAddress &other) noexcept;
FileAddress(FileAddress &&other) noexcept;
FileAddress(std::nullptr_t) noexcept;
FileAddress(uint64_t inode) noexcept;
explicit FileAddress(StringViewCR path) noexcept;
constexpr FileAddress(ox::StringLiteral path) noexcept;
constexpr ~FileAddress() noexcept;
FileAddress &operator=(const FileAddress &other) noexcept;
FileAddress &operator=(FileAddress &&other) noexcept;
bool operator==(const FileAddress &other) const noexcept;
bool operator==(StringViewCR path) const noexcept;
[[nodiscard]]
constexpr FileAddressType type() const noexcept {
switch (m_type) {
case FileAddressType::Path:
case FileAddressType::ConstPath:
return FileAddressType::Path;
default:
return m_type;
}
}
constexpr Result<uint64_t> getInode() const noexcept {
switch (m_type) {
case FileAddressType::Inode:
return m_data.inode;
default:
return ox::Error(1);
}
}
constexpr Result<ox::CStringView> getPath() const noexcept {
switch (m_type) {
case FileAddressType::Path:
return ox::CStringView(m_data.path);
case FileAddressType::ConstPath:
return ox::CStringView(m_data.constPath);
default:
return ox::Error(1);
}
}
explicit constexpr operator bool() const noexcept {
return m_type != FileAddressType::None;
}
private:
/**
* Cleanup memory allocations.
*/
constexpr void cleanup() noexcept;
/**
* Clears fields, but does not delete allocations.
*/
constexpr void clear() noexcept;
};
constexpr FileAddress::FileAddress(ox::StringLiteral path) noexcept {
m_data.constPath = path.c_str();
m_type = FileAddressType::ConstPath;
}
constexpr FileAddress::~FileAddress() noexcept {
cleanup();
}
constexpr void FileAddress::cleanup() noexcept {
if (m_type == FileAddressType::Path) {
safeDeleteArray(m_data.path);
clear();
}
}
constexpr void FileAddress::clear() noexcept {
m_data.path = nullptr;
m_type = FileAddressType::None;
}
template<>
constexpr const char *getModelTypeName<FileAddress::Data>() noexcept {
return FileAddress::Data::TypeName;
}
template<>
constexpr const char *getModelTypeName<FileAddress>() noexcept {
return FileAddress::TypeName;
}
template<typename T>
constexpr Error model(T *h, CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
OX_RETURN_ERROR(h->template setTypeInfo<FileAddress::Data>());
OX_RETURN_ERROR(h->fieldCString("path", &obj->path));
OX_RETURN_ERROR(h->fieldCString("constPath", &obj->path));
OX_RETURN_ERROR(h->field("inode", &obj->inode));
return {};
}
template<typename T>
constexpr Error model(T *h, CommonPtrWith<FileAddress> auto *fa) noexcept {
OX_RETURN_ERROR(h->template setTypeInfo<FileAddress>());
if constexpr(T::opType() == OpType::Reflect) {
int8_t type = -1;
OX_RETURN_ERROR(h->field("type", &type));
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, type)));
} else if constexpr(T::opType() == OpType::Read) {
auto type = static_cast<int8_t>(fa->m_type);
OX_RETURN_ERROR(h->field("type", &type));
fa->m_type = static_cast<FileAddressType>(type);
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
} else if constexpr(T::opType() == OpType::Write) {
auto const type = static_cast<int8_t>(fa->m_type);
OX_RETURN_ERROR(h->field("type", &type));
OX_RETURN_ERROR(h->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
}
return {};
}
}
OX_CLANG_NOWARN_END
@@ -0,0 +1,553 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/buffer.hpp>
#include <ox/std/span.hpp>
#include <ox/fs/filestore/filestoretemplate.hpp>
#include <ox/fs/filesystem/filelocation.hpp>
#include <ox/fs/filesystem/types.hpp>
#include "directory.hpp"
namespace ox {
namespace detail {
inline void fsBuffFree(char *buff) noexcept {
safeDelete(buff);
}
}
class FileSystem {
public:
virtual ~FileSystem() noexcept = default;
virtual Error mkdir(StringViewCR path, bool recursive) noexcept = 0;
/**
* Moves an entry from one directory to another.
* @param src the path to the file
* @param dest the path of the destination directory
*/
virtual Error move(StringViewCR src, StringViewCR dest) noexcept = 0;
Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept;
Result<Buffer> read(FileAddress const &addr, size_t size) noexcept;
Result<Buffer> read(StringViewCR path, size_t size) noexcept;
Result<Buffer> read(const FileAddress &addr) noexcept;
Result<Buffer> read(StringViewCR path) noexcept;
Error read(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
return readFilePath(path, buffer, buffSize);
}
Error read(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
return readFileInode(inode, buffer, buffSize);
}
Error read(
FileAddress const &addr,
size_t readStart,
size_t readSize,
void *buffer,
size_t *size) noexcept;
/**
*
* @param path
* @param readStart
* @param readSize
* @param buff
* @return error or number of bytes read
*/
Result<size_t> read(
StringViewCR path, size_t readStart, size_t readSize, ox::Span<char> buff) noexcept;
virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0;
Error remove(StringViewCR path, bool recursive = false) noexcept {
return removePath(path, recursive);
}
virtual Error resize(uint64_t size, void *buffer) noexcept = 0;
Error write(StringViewCR path, const void *buffer, uint64_t size) noexcept {
return writeFilePath(path, buffer, size, FileType::NormalFile);
}
Error write(StringViewCR path, ox::SpanView<char> const&buff) noexcept {
return write(path, buff.data(), buff.size(), FileType::NormalFile);
}
Error write(uint64_t inode, const void *buffer, uint64_t size) noexcept {
return write(inode, buffer, size, FileType::NormalFile);
}
Error write(uint64_t inode, ox::SpanView<char> const&buff) noexcept {
return write(inode, buff.data(), buff.size(), FileType::NormalFile);
}
Error write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType = FileType::NormalFile) noexcept;
Error write(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept {
return writeFilePath(path, buffer, size, fileType);
}
Error write(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
return writeFileInode(inode, buffer, size, fileType);
}
Result<FileStat> stat(uint64_t inode) const noexcept {
return statInode(inode);
}
Result<FileStat> stat(StringViewCR path) const noexcept {
return statPath(path);
}
Result<FileStat> stat(const FileAddress &addr) const noexcept;
[[nodiscard]]
bool exists(uint64_t inode) const noexcept {
return statInode(inode).ok();
}
[[nodiscard]]
bool exists(ox::StringView path) const noexcept {
return statPath(path).ok();
}
[[nodiscard]]
bool exists(FileAddress const&addr) const noexcept {
return stat(addr).ok();
}
[[nodiscard]]
virtual uint64_t spaceNeeded(uint64_t size) const noexcept = 0;
[[nodiscard]]
virtual Result<uint64_t> available() const noexcept = 0;
[[nodiscard]]
virtual Result<uint64_t> size() const noexcept = 0;
[[nodiscard]]
virtual char *buff() noexcept = 0;
virtual Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept = 0;
[[nodiscard]]
virtual bool valid() const noexcept = 0;
protected:
virtual Result<FileStat> statInode(uint64_t inode) const noexcept = 0;
virtual Result<FileStat> statPath(StringViewCR path) const noexcept = 0;
virtual Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept = 0;
virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0;
virtual Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept = 0;
virtual Error readFileInodeRange(uint64_t inode, size_t readStart, size_t readSize, void *buffer, size_t *size) noexcept = 0;
virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0;
virtual Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;
virtual Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;
};
class MemFS: public FileSystem {
public:
Result<const char*> directAccess(const FileAddress &addr) const noexcept;
Result<const char*> directAccess(StringViewCR path) const noexcept {
return directAccessPath(path);
}
Result<const char*> directAccess(uint64_t inode) const noexcept {
return directAccessInode(inode);
}
protected:
virtual Result<const char*> directAccessPath(StringViewCR path) const noexcept = 0;
virtual Result<const char*> directAccessInode(uint64_t inode) const noexcept = 0;
};
/**
* FileSystemTemplate used to create file system that wraps around a FileStore,
* taking an inode size and a directory type as parameters.
*
* Note: Directory parameter must have a default constructor.
*/
template<typename FileStore, typename Directory>
class FileSystemTemplate: public MemFS {
private:
static constexpr auto InodeFsData = 2;
struct OX_PACKED FileSystemData {
LittleEndian<typename FileStore::InodeId_t> rootDirInode;
};
FileStore m_fs;
void(*m_freeBuffer)(char*) = nullptr;
public:
FileSystemTemplate() noexcept = default;
FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*) = detail::fsBuffFree) noexcept;
explicit FileSystemTemplate(ox::Buffer &buffer) noexcept;
explicit FileSystemTemplate(FileStore fs) noexcept;
~FileSystemTemplate() noexcept override;
static Error format(void *buff, uint64_t buffSize) noexcept;
Error mkdir(StringViewCR path, bool recursive) noexcept override;
Error move(StringViewCR src, StringViewCR dest) noexcept override;
Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept override;
Result<const char*> directAccessPath(StringViewCR) const noexcept override;
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override;
Result<const char*> directAccessInode(uint64_t) const noexcept override;
Result<Vector<String>> ls(StringViewCR dir) const noexcept override;
template<typename F>
Error ls(StringViewCR path, F cb) const;
/**
* Resizes FileSystem to minimum possible size.
*/
Error resize() noexcept;
Error resize(uint64_t size, void *buffer) noexcept override;
Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override;
Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override;
Result<FileStat> statInode(uint64_t inode) const noexcept override;
Result<FileStat> statPath(StringViewCR path) const noexcept override;
[[nodiscard]]
uint64_t spaceNeeded(uint64_t size) const noexcept override;
Result<uint64_t> available() const noexcept override;
Result<uint64_t> size() const noexcept override;
char *buff() noexcept override;
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept override;
[[nodiscard]]
bool valid() const noexcept override;
private:
Result<FileSystemData> fileSystemData() const noexcept;
/**
* Finds the inode ID at the given path.
*/
Result<uint64_t> find(StringViewCR path) const noexcept;
Result<Directory> rootDir() const noexcept;
};
template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(FileStore fs) noexcept {
m_fs = fs;
}
template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(ox::Buffer &buffer) noexcept:
m_fs(buffer.data(), static_cast<std::size_t>(buffer.size())) {
}
template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*)) noexcept:
m_fs(buffer, static_cast<std::size_t>(bufferSize)),
m_freeBuffer(freeBuffer) {
}
template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::~FileSystemTemplate() noexcept {
if (m_freeBuffer && m_fs.buff()) {
m_freeBuffer(m_fs.buff());
}
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::format(void *buff, uint64_t buffSize) noexcept {
OX_RETURN_ERROR(FileStore::format(buff, static_cast<size_t>(buffSize)));
FileStore fs(buff, static_cast<size_t>(buffSize));
constexpr auto rootDirInode = MaxValue<typename FileStore::InodeId_t> / 2;
Directory rootDir(fs, rootDirInode);
OX_RETURN_ERROR(rootDir.init());
FileSystemData fd;
fd.rootDirInode = rootDirInode;
oxTracef("ox.fs.FileSystemTemplate.format", "rootDirInode: {}", fd.rootDirInode.get());
OX_RETURN_ERROR(fs.write(InodeFsData, &fd, sizeof(fd)));
if (!fs.read(fd.rootDirInode).valid()) {
oxTrace("ox.fs.FileSystemTemplate.format.error", "FileSystemTemplate::format did not correctly create root directory");
return ox::Error(1);
}
return {};
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::mkdir(StringViewCR path, bool recursive) noexcept {
oxTracef("ox.fs.FileSystemTemplate.mkdir", "path: {}, recursive: {}", path, recursive);
OX_REQUIRE_M(rootDir, this->rootDir());
return rootDir.mkdir(path, recursive);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::move(StringViewCR src, StringViewCR dest) noexcept {
OX_REQUIRE(fd, fileSystemData());
Directory rootDir(m_fs, fd.rootDirInode);
OX_REQUIRE_M(inode, rootDir.find(src));
OX_RETURN_ERROR(rootDir.write(dest, inode));
OX_RETURN_ERROR(rootDir.remove(src));
return {};
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
oxTrace("ox.fs.FileSystemTemplate.readFilePath", path);
OX_REQUIRE(fd, fileSystemData());
Directory rootDir(m_fs, fd.rootDirInode);
OX_REQUIRE(s, stat(path));
if (s.size > buffSize) {
return ox::Error(1, "Buffer to small to load file");
}
return readFileInodeRange(s.inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
}
template<typename FileStore, typename Directory>
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessPath(StringViewCR path) const noexcept {
OX_REQUIRE(fd, fileSystemData());
Directory rootDir(m_fs, fd.rootDirInode);
OX_REQUIRE(inode, rootDir.find(path));
return directAccessInode(inode);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFileInode(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
oxTracef("ox.fs.FileSystemTemplate.readFileInode", "{}", inode);
OX_REQUIRE(s, stat(inode));
if (s.size > buffSize) {
return ox::Error(1, "Buffer to small to load file");
}
return readFileInodeRange(inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept {
return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
OX_REQUIRE(s, stat(path));
return readFileInodeRange(s.inode, readStart, readSize, buffer, buffSize);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept {
OX_REQUIRE(fd, fileSystemData());
Directory rootDir(m_fs, fd.rootDirInode);
OX_REQUIRE(inode, rootDir.find(path));
OX_REQUIRE(st, statInode(inode));
if (st.fileType == FileType::NormalFile || recursive) {
if (auto err = rootDir.remove(path)) {
// removal failed, try putting the index back
oxLogError(rootDir.write(path, inode));
return err;
}
} else {
oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting.");
return ox::Error(1);
}
return {};
}
template<typename FileStore, typename Directory>
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessInode(uint64_t inode) const noexcept {
auto data = m_fs.read(inode);
if (!data.valid()) {
return ox::Error(1, "Data not valid");
}
return reinterpret_cast<char*>(data.get());
}
template<typename FileStore, typename Directory>
Result<Vector<String>> FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path) const noexcept {
Vector<String> out;
OX_RETURN_ERROR(ls(path, [&out](StringViewCR name, typename FileStore::InodeId_t) {
out.emplace_back(name);
return ox::Error{};
}));
return out;
}
template<typename FileStore, typename Directory>
template<typename F>
Error FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path, F cb) const {
oxTracef("ox.fs.FileSystemTemplate.ls", "path: {}", path);
OX_REQUIRE(s, stat(path));
Directory dir(m_fs, s.inode);
return dir.ls(cb);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::resize() noexcept {
return m_fs.resize();
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::resize(uint64_t size, void *buffer) noexcept {
OX_RETURN_ERROR(m_fs.resize(static_cast<size_t>(size), buffer));
return {};
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::writeFilePath(
StringViewCR path,
const void *buffer,
uint64_t size,
FileType fileType) noexcept {
oxTrace("ox.fs.FileSystemTemplate.writeFilePath", path);
auto [inode, err] = find(path);
if (err) {
OX_RETURN_ERROR(m_fs.generateInodeId().moveTo(inode));
OX_REQUIRE_M(rootDir, this->rootDir());
OX_RETURN_ERROR(rootDir.write(path, inode));
}
OX_RETURN_ERROR(writeFileInode(inode, buffer, size, fileType));
return {};
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
oxTrace("ox.fs.FileSystemTemplate.writeFileInode", ox::intToStr(inode));
return m_fs.write(inode, buffer, static_cast<size_t>(size), static_cast<uint8_t>(fileType));
}
template<typename FileStore, typename Directory>
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statInode(uint64_t inode) const noexcept {
OX_REQUIRE(s, m_fs.stat(inode));
FileStat out;
out.inode = s.inode;
out.links = s.links;
out.size = s.size;
out.fileType = static_cast<FileType>(s.fileType);
return out;
}
template<typename FileStore, typename Directory>
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statPath(StringViewCR path) const noexcept {
OX_REQUIRE(inode, find(path));
return stat(inode);
}
template<typename FileStore, typename Directory>
uint64_t FileSystemTemplate<FileStore, Directory>::spaceNeeded(uint64_t size) const noexcept {
return m_fs.spaceNeeded(static_cast<size_t>(size));
}
template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::available() const noexcept {
return m_fs.available();
}
template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::size() const noexcept {
return m_fs.size();
}
template<typename FileStore, typename Directory>
char *FileSystemTemplate<FileStore, Directory>::buff() noexcept {
return m_fs.buff();
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept {
return m_fs.walk(cb);
}
template<typename FileStore, typename Directory>
bool FileSystemTemplate<FileStore, Directory>::valid() const noexcept {
return m_fs.valid();
}
template<typename FileStore, typename Directory>
Result<typename FileSystemTemplate<FileStore, Directory>::FileSystemData> FileSystemTemplate<FileStore, Directory>::fileSystemData() const noexcept {
FileSystemData fd;
OX_RETURN_ERROR(m_fs.read(InodeFsData, &fd, sizeof(fd)));
return fd;
}
template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::find(StringViewCR path) const noexcept {
OX_REQUIRE(fd, fileSystemData());
// return root as a special case
if (path == "/") {
return static_cast<uint64_t>(fd.rootDirInode);
}
Directory rootDir(m_fs, fd.rootDirInode);
OX_REQUIRE(out, rootDir.find(path));
return static_cast<uint64_t>(out);
}
template<typename FileStore, typename Directory>
Result<Directory> FileSystemTemplate<FileStore, Directory>::rootDir() const noexcept {
OX_REQUIRE(fd, fileSystemData());
return Directory(m_fs, fd.rootDirInode);
}
extern template class FileSystemTemplate<FileStore16, Directory16>;
extern template class FileSystemTemplate<FileStore32, Directory32>;
using FileSystem16 = FileSystemTemplate<FileStore16, Directory16>;
using FileSystem32 = FileSystemTemplate<FileStore32, Directory32>;
}
@@ -0,0 +1,115 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#if __has_include(<filesystem>) && defined(OX_USE_STDLIB)
#include <filesystem>
#include <ox/std/bit.hpp>
#include "filesystem.hpp"
#define OX_HAS_PASSTHROUGHFS
namespace ox {
constexpr auto HasPassThroughFS = true;
/**
*
*/
class PassThroughFS: public FileSystem {
private:
std::filesystem::path m_path;
public:
explicit PassThroughFS(StringViewCR dirPath);
~PassThroughFS() noexcept override;
[[nodiscard]]
String basePath() const noexcept;
Error mkdir(StringViewCR path, bool recursive) noexcept override;
Error move(StringViewCR src, StringViewCR dest) noexcept override;
Result<Vector<String>> ls(StringViewCR dir) const noexcept override;
template<typename F>
Error ls(StringViewCR dir, F cb) const noexcept;
Error resize(uint64_t size, void *buffer) noexcept override;
Result<FileStat> statInode(uint64_t inode) const noexcept override;
Result<FileStat> statPath(StringViewCR path) const noexcept override;
[[nodiscard]]
uint64_t spaceNeeded(uint64_t size) const noexcept override;
Result<uint64_t> available() const noexcept override;
Result<uint64_t> size() const noexcept override;
[[nodiscard]]
char *buff() noexcept override;
Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept override;
[[nodiscard]]
bool valid() const noexcept override;
protected:
Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept override;
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override;
Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override;
Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override;
private:
/**
* Strips the leading slashes from a string.
*/
[[nodiscard]]
static std::string_view stripSlash(StringView path) noexcept;
};
template<typename F>
Error PassThroughFS::ls(StringViewCR dir, F cb) const noexcept {
std::error_code ec;
const auto di = std::filesystem::directory_iterator(m_path / stripSlash(dir), ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: ls failed"));
for (auto &p : di) {
OX_RETURN_ERROR(cb(p.path().filename().c_str(), 0));
}
return {};
}
}
#else
namespace ox {
constexpr auto HasPassThroughFS = false;
}
#endif
@@ -0,0 +1,52 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/std.hpp>
namespace ox {
constexpr std::size_t MaxFileNameLength = 255;
class PathIterator {
private:
const char *m_path = nullptr;
std::size_t m_iterator = 0;
std::size_t m_maxSize = 0;
public:
PathIterator(const char *path, std::size_t maxSize, std::size_t iterator = 0);
PathIterator(const char *path);
PathIterator(StringViewCR path);
Error dirPath(char *pathOut, std::size_t pathOutSize);
Error next(StringView &fileName);
Error get(StringView &fileName);
Result<std::size_t> nextSize() const;
[[nodiscard]]
bool hasNext() const;
[[nodiscard]]
bool valid() const;
[[nodiscard]]
PathIterator next() const;
[[nodiscard]]
const char *fullPath() const;
};
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/types.hpp>
namespace ox {
enum class FileType: uint8_t {
None = 0,
NormalFile = 1,
Directory = 2
};
constexpr const char *toString(FileType t) noexcept {
switch (t) {
case FileType::NormalFile:
return "Normal File";
case FileType::Directory:
return "Directory";
default:
return "";
}
}
struct FileStat {
uint64_t inode = 0;
uint64_t links = 0;
uint64_t size = 0;
FileType fileType = FileType::None;
};
}
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "filestore/filestoretemplate.hpp"
#include "filesystem/filelocation.hpp"
#include "filesystem/filesystem.hpp"
#include "filesystem/passthroughfs.hpp"
#include "filesystem/directory.hpp"
@@ -0,0 +1,455 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/stddef.hpp>
#include <ox/std/trace.hpp>
#include "ptr.hpp"
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox::ptrarith {
template<typename size_t, typename Item>
class OX_PACKED NodeBuffer {
public:
struct OX_PACKED Header {
LittleEndian<size_t> size = sizeof(Header); // capacity
LittleEndian<size_t> bytesUsed = sizeof(Header);
LittleEndian<size_t> firstItem = 0;
};
using ItemPtr = Ptr<Item, size_t, sizeof(Header)>;
class Iterator {
private:
NodeBuffer *m_buffer = nullptr;
ItemPtr m_current;
size_t m_it = 0;
public:
Iterator(NodeBuffer *buffer, ItemPtr current) noexcept {
m_buffer = buffer;
m_current = current;
oxTrace("ox.ptrarith.Iterator.start") << current.offset();
}
operator const Item*() const noexcept {
return m_current;
}
ItemPtr ptr() noexcept {
return m_current;
}
[[nodiscard]]
Item *get() noexcept {
return m_current;
}
operator ItemPtr() noexcept {
return m_current;
}
operator Item*() noexcept {
return m_current;
}
const Item *operator->() const noexcept {
return m_current;
}
Item *operator->() noexcept {
return m_current;
}
[[nodiscard]]
bool valid() const noexcept {
return m_current.valid();
}
[[nodiscard]]
bool hasNext() noexcept {
if (m_current.valid()) {
oxTrace("ox.ptrarith.NodeBuffer.Iterator.hasNext.current") << m_current.offset();
auto next = m_buffer->next(m_current);
return next.valid() && m_buffer->firstItem() != next;
}
return false;
}
void next() noexcept {
oxTrace("ox.ptrarith.NodeBuffer.Iterator.next") << m_it++;
if (hasNext()) {
m_current = m_buffer->next(m_current);
} else {
m_current = nullptr;
}
}
};
Header m_header;
public:
NodeBuffer(const NodeBuffer &other, std::size_t size) noexcept;
explicit NodeBuffer(std::size_t size) noexcept;
[[nodiscard]]
const Iterator iterator() const noexcept;
[[nodiscard]]
Iterator iterator() noexcept;
ItemPtr firstItem() noexcept;
ItemPtr lastItem() noexcept;
/**
* @return the data section of the given item
*/
template<typename T>
Ptr<T, size_t, sizeof(Item)> dataOf(ItemPtr) noexcept;
ItemPtr prev(Item *item) noexcept;
ItemPtr next(Item *item) noexcept;
/**
* Like pointer but omits checks that assume the memory at the offset has
* already been initialed as an Item.
*/
ItemPtr uninitializedPtr(size_t offset) noexcept;
ItemPtr ptr(size_t offset) noexcept;
Result<ItemPtr> malloc(std::size_t size) noexcept;
Error free(ItemPtr item) noexcept;
[[nodiscard]]
bool valid(size_t maxSize) const noexcept;
/**
* Set size, capacity.
*/
Error setSize(std::size_t size) noexcept;
/**
* Get size, capacity.
* @return capacity
*/
[[nodiscard]]
constexpr size_t size() const noexcept;
/**
* @return the bytes still available in this NodeBuffer
*/
[[nodiscard]]
size_t available() const noexcept;
/**
* @return the actual number a bytes need to store the given number of
* bytes
*/
[[nodiscard]]
static size_t spaceNeeded(std::size_t size) noexcept;
template<typename F>
Error compact(F cb = [](uint64_t, ItemPtr) {}) noexcept;
private:
[[nodiscard]]
uint8_t *data() noexcept;
};
template<typename size_t, typename Item>
NodeBuffer<size_t, Item>::NodeBuffer(std::size_t size) noexcept {
m_header.size = static_cast<size_t>(size);
ox::memset(this + 1, 0, size - sizeof(*this));
oxTracef("ox.ptrarith.NodeBuffer.constructor", "{}", m_header.firstItem.get());
}
template<typename size_t, typename Item>
NodeBuffer<size_t, Item>::NodeBuffer(const NodeBuffer &other, std::size_t size) noexcept {
oxTracef("ox.ptrarith.NodeBuffer.copy", "other.m_header.firstItem: {}", other.m_header.firstItem.get());
ox::memset(this + 1, 0, size - sizeof(*this));
ox::memcpy(this, &other, size);
}
template<typename size_t, typename Item>
const typename NodeBuffer<size_t, Item>::Iterator NodeBuffer<size_t, Item>::iterator() const noexcept {
return Iterator(this, firstItem());
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::Iterator NodeBuffer<size_t, Item>::iterator() noexcept {
oxTracef("ox.ptrarith.NodeBuffer.iterator.size", "{}", m_header.size.get());
return Iterator(this, firstItem());
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::firstItem() noexcept {
//oxTrace("ox::ptrarith::NodeBuffer::firstItem") << m_header.firstItem;
return ptr(m_header.firstItem);
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::lastItem() noexcept {
auto first = ptr(m_header.firstItem);
if (first.valid()) {
return prev(first);
}
return nullptr;
}
template<typename size_t, typename Item>
template<typename T>
Ptr<T, size_t, sizeof(Item)> NodeBuffer<size_t, Item>::dataOf(ItemPtr ip) noexcept {
auto out = ip.template subPtr<T>(sizeof(Item));
oxAssert(out.size() == ip.size() - sizeof(Item), "Sub Ptr has invalid size.");
return out;
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::prev(Item *item) noexcept {
return ptr(item->prev);
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::next(Item *item) noexcept {
return ptr(item->next);
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::uninitializedPtr(size_t itemOffset) noexcept {
// make sure this can be read as an Item, and then use Item::size for the size
std::size_t itemSpace = m_header.size - itemOffset;
if (itemOffset >= sizeof(Header) &&
itemOffset + itemSpace <= size() &&
itemSpace >= sizeof(Item)) {
return ItemPtr(this, m_header.size, itemOffset, itemSpace);
} else {
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset:" << itemOffset;
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset >= sizeof(Header):" << (itemOffset >= sizeof(Header));
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= sizeof(Item):" << (itemSpace >= sizeof(Item));
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= item->fullSize():" << (itemSpace >= item->fullSize());
return ItemPtr(this, m_header.size, 0, 0);
}
}
template<typename size_t, typename Item>
typename NodeBuffer<size_t, Item>::ItemPtr NodeBuffer<size_t, Item>::ptr(size_t itemOffset) noexcept {
// make sure this can be read as an Item, and then use Item::size for the size
std::size_t itemSpace = m_header.size - itemOffset;
auto item = reinterpret_cast<Item*>(reinterpret_cast<uint8_t*>(this) + itemOffset);
if (itemOffset >= sizeof(Header) &&
itemOffset + itemSpace <= size() &&
itemSpace >= sizeof(Item) &&
itemSpace >= item->fullSize()) {
return ItemPtr(this, m_header.size, itemOffset, item->fullSize());
} else {
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset:" << itemOffset;
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemOffset >= sizeof(Header):" << (itemOffset >= sizeof(Header));
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= sizeof(Item):" << (itemSpace >= sizeof(Item));
//oxTrace("ox::ptrarith::NodeBuffer::ptr::null") << "itemSpace >= item->fullSize():" << (itemSpace >= item->fullSize());
return ItemPtr(this, m_header.size, 0, 0);
}
}
template<typename size_t, typename Item>
Result<typename NodeBuffer<size_t, Item>::ItemPtr> NodeBuffer<size_t, Item>::malloc(std::size_t size) noexcept {
const auto sz = static_cast<std::size_t>(size);
oxTracef("ox.ptrarith.NodeBuffer.malloc", "Size: {}", sz);
size_t fullSize = static_cast<size_t>(sz + sizeof(Item));
if (m_header.size - m_header.bytesUsed >= fullSize) {
auto last = lastItem();
size_t addr;
if (last.valid()) {
addr = last.offset() + last.size();
} else {
// there is no first item, so this must be the first item
if (!m_header.firstItem) {
oxTrace("ox.ptrarith.NodeBuffer.malloc", "No first item, initializing.");
m_header.firstItem = static_cast<size_t>(sizeof(m_header));
addr = m_header.firstItem;
} else {
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer is in invalid state.");
return ox::Error(1, "NodeBuffer is in invalid state.");
}
}
oxTracef("ox.ptrarith.NodeBuffer.malloc", "buffer size: {}; addr: {}; fullSize: {}", m_header.size.get(), addr, fullSize);
auto out = ItemPtr(this, m_header.size, addr, fullSize);
if (!out.valid()) {
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "Unknown");
return ox::Error(1, "NodeBuffer::malloc: unknown failure");
}
ox::memset(out, 0, fullSize);
new (out) Item;
out->setSize(sz);
auto first = firstItem();
auto oldLast = last;
out->next = first.offset();
if (first.valid()) {
first->prev = out.offset();
} else {
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer malloc failed due to invalid first element pointer.");
return ox::Error(1, "NodeBuffer malloc failed due to invalid first element pointer.");
}
if (oldLast.valid()) {
out->prev = oldLast.offset();
oldLast->next = out.offset();
} else { // check to see if this is the first allocation
if (out.offset() != first.offset()) {
// if this is not the first allocation, there should be an oldLast
oxTrace("ox.ptrarith.NodeBuffer.malloc.fail", "NodeBuffer malloc failed due to invalid last element pointer.");
return ox::Error(1, "NodeBuffer malloc failed due to invalid last element pointer.");
}
out->prev = out.offset();
}
m_header.bytesUsed += out.size();
oxTracef("ox.ptrarith.NodeBuffer.malloc", "Offset: {}", out.offset());
return out;
}
oxTracef("ox.ptrarith.NodeBuffer.malloc.fail", "Insufficient space: {} needed, {} available", fullSize, available());
return ox::Error(1);
}
template<typename size_t, typename Item>
Error NodeBuffer<size_t, Item>::free(ItemPtr item) noexcept {
oxTracef("ox.ptrarith.NodeBuffer.free", "offset: {}", item.offset());
auto prev = this->prev(item);
auto next = this->next(item);
if (prev.valid() && next.valid()) {
if (next != item) {
prev->next = next;
next->prev = prev;
if (item.offset() == m_header.firstItem) {
m_header.firstItem = next;
}
} else {
// only one item, null out first
oxTrace("ox.ptrarith.NodeBuffer.free", "Nulling out firstItem.");
m_header.firstItem = 0;
}
} else {
if (!prev.valid()) {
oxTracef("ox.ptrarith.NodeBuffer.free.fail", "NodeBuffer free failed due to invalid prev element pointer: {}", prev.offset());
return ox::Error(1);
}
if (!next.valid()) {
oxTracef("ox.ptrarith.NodeBuffer.free.fail", "NodeBuffer free failed due to invalid next element pointer: {}", next.offset());
return ox::Error(1);
}
}
m_header.bytesUsed -= item.size();
return {};
}
template<typename size_t, typename Item>
Error NodeBuffer<size_t, Item>::setSize(std::size_t size) noexcept {
oxTracef("ox.ptrarith.NodeBuffer.setSize", "{} to {}", m_header.size.get(), size);
auto last = lastItem();
auto end = last.valid() ? last.end() : sizeof(m_header);
oxTracef("ox.ptrarith.NodeBuffer.setSize", "end: {}", end);
if (end > size) {
// resizing to less than buffer size
return ox::Error(1);
} else {
m_header.size = static_cast<size_t>(size);
auto data = reinterpret_cast<uint8_t*>(this) + end;
ox::memset(data, 0, size - end);
return {};
}
}
template<typename size_t, typename Item>
constexpr size_t NodeBuffer<size_t, Item>::size() const noexcept {
return m_header.size;
}
template<typename size_t, typename Item>
bool NodeBuffer<size_t, Item>::valid(size_t maxSize) const noexcept {
return m_header.size <= maxSize;
}
template<typename size_t, typename Item>
size_t NodeBuffer<size_t, Item>::available() const noexcept {
return m_header.size - m_header.bytesUsed;
}
template<typename size_t, typename Item>
size_t NodeBuffer<size_t, Item>::spaceNeeded(std::size_t size) noexcept {
return static_cast<size_t>(sizeof(Item) + size);
}
template<typename size_t, typename Item>
template<typename F>
Error NodeBuffer<size_t, Item>::compact(F cb) noexcept {
auto src = firstItem();
auto dest = ptr(sizeof(*this));
while (dest.offset() <= src.offset()) {
if (!src.valid()) {
return ox::Error(1);
}
if (!dest.valid()) {
return ox::Error(2);
}
// move node
ox::memcpy(dest, src, src->fullSize());
OX_RETURN_ERROR(cb(src, dest));
// update surrounding nodes
auto prev = ptr(dest->prev);
if (prev.valid()) {
prev->next = dest;
}
auto next = ptr(dest->next);
if (next.valid()) {
next->prev = dest;
}
// update iterators
src = ptr(dest->next);
dest = uninitializedPtr(dest.offset() + dest->fullSize());
}
return {};
}
template<typename size_t, typename Item>
uint8_t *NodeBuffer<size_t, Item>::data() noexcept {
return reinterpret_cast<uint8_t*>(ptr(sizeof(*this)).get());
}
template<typename size_t>
struct OX_PACKED Item {
public:
LittleEndian<size_t> prev = 0;
LittleEndian<size_t> next = 0;
private:
LittleEndian<size_t> m_size = sizeof(Item);
public:
size_t size() const {
return m_size;
}
void setSize(std::size_t size) {
m_size = static_cast<size_t>(size);
}
};
}
OX_CLANG_NOWARN_END
+262
View File
@@ -0,0 +1,262 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/std.hpp>
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox::ptrarith {
template<typename T, typename size_t, size_t minOffset = 1>
class [[nodiscard]] Ptr {
private:
uint8_t *m_dataStart = nullptr;
size_t m_dataSize = 0;
size_t m_itemOffset = 0;
size_t m_itemSize = 0;
// this should be removed later on, but the excessive validation is
// desirable during during heavy development
mutable uint8_t m_validated = false;
public:
constexpr Ptr() noexcept = default;
constexpr Ptr(std::nullptr_t) noexcept;
constexpr Ptr(
void *pDataStart,
std::size_t pDataSize,
std::size_t pItemStart,
std::size_t pItemSize = sizeof(T),
std::size_t pItemTypeSize = sizeof(T),
bool pPrevalidated = false) noexcept;
[[nodiscard]]
constexpr bool valid() const noexcept;
constexpr size_t size() const noexcept;
constexpr size_t offset() const noexcept;
constexpr size_t end() noexcept;
constexpr const T *get() const noexcept;
constexpr T *get() noexcept;
constexpr const T *operator->() const noexcept;
constexpr T *operator->() noexcept;
constexpr operator const T*() const noexcept;
constexpr operator T*() noexcept;
constexpr const T &operator*() const noexcept;
constexpr T &operator*() noexcept;
constexpr operator size_t() const noexcept;
constexpr bool operator==(const Ptr<T, size_t, minOffset> &other) const noexcept;
constexpr bool operator!=(const Ptr<T, size_t, minOffset> &other) const noexcept;
template<typename SubT>
constexpr const Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset, size_t size) const noexcept;
template<typename SubT>
constexpr const Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset) const noexcept;
template<typename SubT>
constexpr Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset, size_t size) noexcept;
template<typename SubT>
constexpr Ptr<SubT, size_t, sizeof(T)> subPtr(size_t offset) noexcept;
template<typename SubT>
constexpr const Ptr<SubT, size_t, minOffset> to() const noexcept;
constexpr Result<Ptr<T, size_t, minOffset>> validate() const noexcept;
};
template<typename T, typename size_t, size_t minOffset>
constexpr Ptr<T, size_t, minOffset>::Ptr(std::nullptr_t) noexcept {
}
template<typename T, typename size_t, size_t minOffset>
constexpr Ptr<T, size_t, minOffset>::Ptr(
void *pDataStart,
std::size_t pDataSize,
std::size_t pItemStart,
std::size_t pItemSize,
std::size_t pItemTypeSize,
bool pPrevalidated) noexcept {
const auto dataSize = static_cast<size_t>(pDataSize);
const auto itemStart = static_cast<size_t>(pItemStart);
const auto itemSize = static_cast<size_t>(pItemSize);
const auto itemTypeSize = static_cast<size_t>(pItemTypeSize);
// do some sanity checks before assuming this is valid
if (itemSize >= itemTypeSize &&
pDataStart &&
itemStart >= minOffset &&
itemStart + itemSize <= dataSize) {
m_dataStart = reinterpret_cast<uint8_t*>(pDataStart);
m_dataSize = dataSize;
m_itemOffset = itemStart;
m_itemSize = itemSize;
m_validated = pPrevalidated;
}
}
template<typename T, typename size_t, size_t minOffset>
constexpr bool Ptr<T, size_t, minOffset>::valid() const noexcept {
m_validated = m_dataStart != nullptr;
return m_validated;
}
template<typename T, typename size_t, size_t minOffset>
constexpr size_t Ptr<T, size_t, minOffset>::size() const noexcept {
return m_itemSize;
}
template<typename T, typename size_t, size_t minOffset>
constexpr size_t Ptr<T, size_t, minOffset>::offset() const noexcept {
return m_itemOffset;
}
template<typename T, typename size_t, size_t minOffset>
constexpr size_t Ptr<T, size_t, minOffset>::end() noexcept {
return m_itemOffset + m_itemSize;
}
template<typename T, typename size_t, size_t minOffset>
constexpr const T *Ptr<T, size_t, minOffset>::get() const noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::get())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::get())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr T *Ptr<T, size_t, minOffset>::get() noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::get())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::get())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr const T *Ptr<T, size_t, minOffset>::operator->() const noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator->())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator->())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr T *Ptr<T, size_t, minOffset>::operator->() noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator->())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator->())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr Ptr<T, size_t, minOffset>::operator const T*() const noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator const T*())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator const T*())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr Ptr<T, size_t, minOffset>::operator T*() noexcept {
oxAssert(m_validated, "Unvalidated pointer access. (ox::fs::Ptr::operator T*())");
oxAssert(valid(), "Invalid pointer access. (ox::fs::Ptr::operator T*())");
return reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr const T &Ptr<T, size_t, minOffset>::operator*() const noexcept {
oxAssert(m_validated, "Unvalidated pointer dereference. (ox::fs::Ptr::operator*())");
oxAssert(valid(), "Invalid pointer dereference. (ox::fs::Ptr::operator*())");
return *reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr T &Ptr<T, size_t, minOffset>::operator*() noexcept {
oxAssert(m_validated, "Unvalidated pointer dereference. (ox::fs::Ptr::operator*())");
oxAssert(valid(), "Invalid pointer dereference. (ox::fs::Ptr::operator*())");
return *reinterpret_cast<T*>(m_dataStart + m_itemOffset);
}
template<typename T, typename size_t, size_t minOffset>
constexpr Ptr<T, size_t, minOffset>::operator size_t() const noexcept {
if (m_dataStart && m_itemOffset) {
return m_itemOffset;
}
return 0;
}
template<typename T, typename size_t, size_t minOffset>
constexpr bool Ptr<T, size_t, minOffset>::operator==(const Ptr<T, size_t, minOffset> &other) const noexcept {
return m_dataStart == other.m_dataStart &&
m_itemOffset == other.m_itemOffset &&
m_itemSize == other.m_itemSize;
}
template<typename T, typename size_t, size_t minOffset>
constexpr bool Ptr<T, size_t, minOffset>::operator!=(const Ptr<T, size_t, minOffset> &other) const noexcept {
return m_dataStart != other.m_dataStart ||
m_itemOffset != other.m_itemOffset ||
m_itemSize != other.m_itemSize;
}
template<typename T, typename size_t, size_t minOffset>
template<typename SubT>
constexpr const Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset, size_t size) const noexcept {
return Ptr<SubT, size_t, sizeof(T)>(get(), this->size(), offset, size);
}
template<typename T, typename size_t, size_t minOffset>
template<typename SubT>
constexpr const Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset) const noexcept {
oxTracef("ox.ptrarith.Ptr.subPtr", "{} {} {} {} {}", m_itemOffset, this->size(), offset, m_itemSize, (m_itemSize - offset));
return subPtr<SubT>(offset, m_itemSize - offset);
}
template<typename T, typename size_t, size_t minOffset>
template<typename SubT>
constexpr Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset, size_t size) noexcept {
return Ptr<SubT, size_t, sizeof(T)>(get(), this->size(), offset, size);
}
template<typename T, typename size_t, size_t minOffset>
template<typename SubT>
constexpr Ptr<SubT, size_t, sizeof(T)> Ptr<T, size_t, minOffset>::subPtr(size_t offset) noexcept {
oxTracef("ox.ptrarith.Ptr.subPtr", "{} {} {} {} {}", m_itemOffset, this->size(), offset, m_itemSize, (m_itemSize - offset));
return subPtr<SubT>(offset, m_itemSize - offset);
}
template<typename T, typename size_t, size_t minOffset>
template<typename SubT>
constexpr const Ptr<SubT, size_t, minOffset> Ptr<T, size_t, minOffset>::to() const noexcept {
return Ptr<SubT, size_t, minOffset>(m_dataStart, m_dataSize, m_itemOffset, m_itemSize);
}
template<typename T, typename size_t, size_t minOffset>
constexpr Result<Ptr<T, size_t, minOffset>> Ptr<T, size_t, minOffset>::validate() const noexcept {
if (valid()) {
return *this;
}
return ox::Error(1);
}
}
OX_CLANG_NOWARN_END
@@ -0,0 +1,16 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/fs/filestore/filestoretemplate.hpp>
namespace ox {
template class FileStoreTemplate<uint16_t>;
template class FileStoreTemplate<uint32_t>;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/fs/filesystem/directory.hpp>
namespace ox {
template class Directory<FileStore16, uint16_t>;
template class Directory<FileStore32, uint32_t>;
template struct DirectoryEntry<uint16_t>;
template struct DirectoryEntry<uint32_t>;
}
+129
View File
@@ -0,0 +1,129 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/model/modelops.hpp>
#include <ox/fs/filesystem/filelocation.hpp>
namespace ox {
FileAddress::FileAddress(const FileAddress &other) noexcept {
operator=(other);
}
FileAddress::FileAddress(FileAddress &&other) noexcept {
operator=(std::move(other));
}
FileAddress::FileAddress(std::nullptr_t) noexcept {
}
FileAddress::FileAddress(uint64_t inode) noexcept {
m_data.inode = inode;
m_type = FileAddressType::Inode;
}
FileAddress::FileAddress(ox::StringViewCR path) noexcept {
auto pathSize = path.bytes();
m_data.path = new char[pathSize + 1];
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
memcpy(m_data.path, path.data(), pathSize);
m_data.path[pathSize] = 0;
OX_ALLOW_UNSAFE_BUFFERS_END
m_type = FileAddressType::Path;
}
FileAddress &FileAddress::operator=(const FileAddress &other) noexcept {
if (this == &other) {
return *this;
}
cleanup();
m_type = other.m_type;
switch (m_type) {
case FileAddressType::Path:
{
if (other.m_data.path) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
auto strSize = ox::strlen(other.m_data.path) + 1;
m_data.path = new char[strSize];
ox::memcpy(m_data.path, other.m_data.path, strSize);
OX_ALLOW_UNSAFE_BUFFERS_END
} else {
m_data.constPath = "";
m_type = FileAddressType::ConstPath;
}
break;
}
case FileAddressType::ConstPath:
case FileAddressType::Inode:
m_data = other.m_data;
break;
case FileAddressType::None:
break;
}
return *this;
}
FileAddress &FileAddress::operator=(FileAddress &&other) noexcept {
if (this == &other) {
return *this;
}
cleanup();
m_type = other.m_type;
switch (m_type) {
case FileAddressType::Path:
{
m_data.path = other.m_data.path;
break;
}
case FileAddressType::ConstPath:
case FileAddressType::Inode:
m_data = other.m_data;
break;
case FileAddressType::None:
break;
}
other.clear();
return *this;
}
bool FileAddress::operator==(FileAddress const&other) const noexcept {
if (m_type != other.m_type) {
auto const aIsPath =
m_type == FileAddressType::Path || m_type == FileAddressType::ConstPath;
auto const bIsPath =
other.m_type == FileAddressType::Path || other.m_type == FileAddressType::ConstPath;
if (!(aIsPath && bIsPath)) {
return false;
}
}
switch (m_type) {
case FileAddressType::ConstPath:
case FileAddressType::Path: {
auto const a = getPath();
auto const b = other.getPath();
return (other.m_type == FileAddressType::ConstPath || other.m_type == FileAddressType::Path)
&& (a.value == b.value);
}
case FileAddressType::Inode:
return m_data.inode == other.m_data.inode;
case FileAddressType::None:
return true;
}
return true;
}
bool FileAddress::operator==(StringViewCR path) const noexcept {
auto [p, err] = getPath();
if (err) {
return false;
}
return path == p;
}
}
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/error.hpp>
#include <ox/std/utility.hpp>
#include <ox/fs/filesystem/filesystem.hpp>
namespace ox {
Result<const char*> MemFS::directAccess(const FileAddress &addr) const noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return directAccess(addr.getInode().value);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return directAccess(StringView(addr.getPath().value));
default:
return ox::Error(1);
}
}
Error FileSystem::read(const FileAddress &addr, void *buffer, std::size_t size) noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return readFileInode(addr.getInode().value, buffer, size);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return readFilePath(StringView(addr.getPath().value), buffer, size);
default:
return ox::Error(1);
}
}
Result<Buffer> FileSystem::read(FileAddress const &addr, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
switch (addr.type()) {
case FileAddressType::Inode:
OX_RETURN_ERROR(readFileInode(addr.getInode().value, out.value.data(), size));
break;
case FileAddressType::ConstPath:
case FileAddressType::Path:
OX_RETURN_ERROR(readFilePath(StringView{addr.getPath().value}, out.value.data(), size));
break;
default:
return ox::Error{1};
}
return out;
}
Result<Buffer> FileSystem::read(StringViewCR path, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
OX_RETURN_ERROR(readFilePath(path, out.value.data(), size));
return out;
}
Result<Buffer> FileSystem::read(const FileAddress &addr) noexcept {
OX_REQUIRE(s, stat(addr));
Buffer buff(static_cast<std::size_t>(s.size));
OX_RETURN_ERROR(read(addr, buff.data(), buff.size()));
return buff;
}
Result<Buffer> FileSystem::read(StringViewCR path) noexcept {
OX_REQUIRE(s, statPath(path));
Buffer buff(static_cast<std::size_t>(s.size));
OX_RETURN_ERROR(readFilePath(path, buff.data(), buff.size()));
return buff;
}
Error FileSystem::read(
FileAddress const &addr,
std::size_t const readStart,
std::size_t const readSize,
void *buffer,
std::size_t *size) noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return readFileInodeRange(addr.getInode().value, readStart, readSize, buffer, size);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return readFilePathRange(addr.getPath().value, readStart, readSize, buffer, size);
default:
return ox::Error(1);
}
}
Result<size_t> FileSystem::read(
StringViewCR path,
std::size_t const readStart,
std::size_t const readSize,
Span<char> buff) noexcept {
size_t szOut{buff.size()};
OX_RETURN_ERROR(readFilePathRange(path, readStart, readSize, buff.data(), &szOut));
return szOut;
}
Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return writeFileInode(addr.getInode().value, buffer, size, fileType);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return writeFilePath(StringView(addr.getPath().value), buffer, size, fileType);
default:
return ox::Error(1);
}
}
Result<FileStat> FileSystem::stat(const FileAddress &addr) const noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return statInode(addr.getInode().value);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return statPath(StringView(addr.getPath().value));
default:
return ox::Error(1);
}
}
template class FileSystemTemplate<FileStore16, Directory16>;
template class FileSystemTemplate<FileStore32, Directory32>;
}
+217
View File
@@ -0,0 +1,217 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/error.hpp>
#include <ox/fs/filesystem/passthroughfs.hpp>
#if defined(OX_HAS_PASSTHROUGHFS)
#include <fstream>
#include <string_view>
namespace ox {
PassThroughFS::PassThroughFS(StringViewCR dirPath) {
m_path = std::string_view(dirPath.data(), dirPath.bytes());
}
PassThroughFS::~PassThroughFS() noexcept = default;
String PassThroughFS::basePath() const noexcept {
return ox::String(m_path.string().c_str());
}
Error PassThroughFS::mkdir(StringViewCR path, bool recursive) noexcept {
bool success = false;
const auto p = m_path / stripSlash(path);
const auto u8p = p.u8string();
oxTrace("ox.fs.PassThroughFS.mkdir", std::bit_cast<const char*>(u8p.c_str()));
if (recursive) {
std::error_code ec;
const auto isDir = std::filesystem::is_directory(p, ec);
if (isDir && !ec.value()) {
success = true;
} else {
success = std::filesystem::create_directories(p, ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
}
} else {
std::error_code ec;
const auto isDir = std::filesystem::is_directory(p, ec);
if (isDir) {
success = true;
} else {
success = std::filesystem::create_directory(p, ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
}
}
return ox::Error(success ? 0 : 1);
}
Error PassThroughFS::move(StringViewCR src, StringViewCR dest) noexcept {
std::error_code ec;
std::filesystem::rename(m_path / stripSlash(src), m_path / stripSlash(dest), ec);
if (ec.value()) {
return ox::Error(1);
}
return {};
}
Result<Vector<String>> PassThroughFS::ls(StringViewCR dir) const noexcept {
Vector<String> out;
std::error_code ec;
const auto di = std::filesystem::directory_iterator(m_path / stripSlash(dir), ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: ls failed"));
for (const auto &p : di) {
const auto u8p = p.path().filename().u8string();
out.emplace_back(reinterpret_cast<const char*>(u8p.c_str()));
}
return out;
}
Error PassThroughFS::resize(uint64_t, void*) noexcept {
// unsupported
return ox::Error(1, "resize is not supported by PassThroughFS");
}
Result<FileStat> PassThroughFS::statInode(uint64_t) const noexcept {
// unsupported
return ox::Error(1, "statInode(uint64_t) is not supported by PassThroughFS");
}
Result<FileStat> PassThroughFS::statPath(StringViewCR path) const noexcept {
std::error_code ec;
const auto p = m_path / stripSlash(path);
const FileType type = std::filesystem::is_directory(p, ec) ?
FileType::Directory : FileType::NormalFile;
oxTracef("ox.fs.PassThroughFS.statInode", "{} {}", ec.message(), path);
const uint64_t size = type == FileType::Directory ? 0 : std::filesystem::file_size(p, ec);
oxTracef("ox.fs.PassThroughFS.statInode.size", "{} {}", path, size);
if (auto err = ec.value()) {
return ox::Error{static_cast<ox::ErrorCode>(err), "PassThroughFS: stat failed"};
}
return FileStat{0, 0, size, type};
}
uint64_t PassThroughFS::spaceNeeded(uint64_t size) const noexcept {
return size;
}
Result<uint64_t> PassThroughFS::available() const noexcept {
std::error_code ec;
const auto s = std::filesystem::space(m_path, ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
return s.available;
}
Result<uint64_t> PassThroughFS::size() const noexcept {
std::error_code ec;
const auto s = std::filesystem::space(m_path, ec);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
return s.capacity;
}
char *PassThroughFS::buff() noexcept {
return nullptr;
}
Error PassThroughFS::walk(Error(*)(uint8_t, uint64_t, uint64_t)) noexcept {
return ox::Error(1, "walk(Error(*)(uint8_t, uint64_t, uint64_t)) is not supported by PassThroughFS");
}
bool PassThroughFS::valid() const noexcept {
std::error_code ec;
const auto out = std::filesystem::is_directory(m_path, ec);
if (!ec.value()) {
return out;
}
return false;
}
Error PassThroughFS::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
try {
std::ifstream file((m_path / stripSlash(path)), std::ios::binary | std::ios::ate);
const std::size_t size = static_cast<std::size_t>(file.tellg());
file.seekg(0, std::ios::beg);
if (size > buffSize) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
return ox::Error(1);
}
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(buffSize));
} catch (const std::fstream::failure &f) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
return ox::Error(2);
}
return {};
}
Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
// unsupported
return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS");
}
Error PassThroughFS::readFilePathRange(
StringViewCR path, size_t const readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
try {
std::ifstream file(m_path / stripSlash(path), std::ios::binary | std::ios::ate);
auto const size = static_cast<size_t>(file.tellg());
readSize = ox::min(readSize, size);
file.seekg(static_cast<off_t>(readStart), std::ios::beg);
if (readSize > *buffSize) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
return ox::Error{1};
}
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(readSize));
return {};
} catch (std::fstream::failure const &f) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
return ox::Error{2};
}
}
Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept {
// unsupported
return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS");
}
Error PassThroughFS::removePath(StringViewCR path, bool const recursive) noexcept {
if (recursive) {
return ox::Error{std::filesystem::remove_all(m_path / stripSlash(path)) == 0};
} else {
return ox::Error{!std::filesystem::remove(m_path / stripSlash(path))};
}
}
Error PassThroughFS::writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType) noexcept {
const auto p = (m_path / stripSlash(path));
try {
std::ofstream f(p, std::ios::binary);
f.write(static_cast<const char*>(buffer), static_cast<std::streamsize>(size));
} catch (const std::fstream::failure &f) {
oxTracef("ox.fs.PassThroughFS.read.error", "Write of {} failed: {}", path, f.what());
return ox::Error(1);
}
return {};
}
Error PassThroughFS::writeFileInode(uint64_t, const void*, uint64_t, FileType) noexcept {
// unsupported
return ox::Error(1, "writeFileInode(uint64_t, void*, uint64_t, uint8_t) is not supported by PassThroughFS");
}
std::string_view PassThroughFS::stripSlash(StringView path) noexcept {
for (auto i = 0u; i < path.size() && path[0] == '/'; i++) {
path = substr(path, 1);
}
return {path.data(), path.bytes()};
}
}
#endif
+189
View File
@@ -0,0 +1,189 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/memops.hpp>
#include <ox/std/strops.hpp>
#include <ox/std/trace.hpp>
#include <ox/fs/filesystem/pathiterator.hpp>
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
PathIterator::PathIterator(const char *path, std::size_t maxSize, std::size_t iterator) {
m_path = path;
m_maxSize = maxSize;
m_iterator = iterator;
}
PathIterator::PathIterator(const char *path): PathIterator(path, ox::strlen(path)) {
}
PathIterator::PathIterator(StringViewCR path): PathIterator(path.data(), path.bytes()) {
}
/**
* @return 0 if no error
*/
Error PathIterator::dirPath(char *out, std::size_t outSize) {
const auto idx = ox::lastIndexOf(m_path, '/', m_maxSize);
const auto size = static_cast<std::size_t>(idx) + 1;
if (idx >= 0 && size < outSize) {
ox::memcpy(out, m_path, size);
out[size] = 0;
return {};
} else {
return ox::Error(1);
}
}
// Gets the get item in the path
Error PathIterator::get(StringView &fileName) {
std::size_t size = 0;
if (m_iterator >= m_maxSize) {
oxTracef("ox.fs.PathIterator.get", "m_iterator ({}) >= m_maxSize ({})", m_iterator, m_maxSize);
return ox::Error(1);
}
if (!ox::strlen(&m_path[m_iterator])) {
oxTrace("ox.fs.PathIterator.get", "!ox::strlen(&m_path[m_iterator])");
return ox::Error(1);
}
auto start = m_iterator;
if (m_path[start] == '/') {
start++;
}
// end is at the next /
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
// correct end if it is invalid, which happens if there is no next /
if (!substr) {
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
}
const auto end = static_cast<size_t>(substr - m_path);
size = end - start;
// cannot fit the output in the output parameter
if (size >= MaxFileNameLength || size == 0) {
return ox::Error(1);
}
fileName = ox::substr(m_path, start, start + size);
// truncate trailing /
if (size && fileName[size - 1] == '/') {
fileName = ox::substr(m_path, start, start + size - 1);
}
oxAssert(fileName[fileName.size()-1] != '/', "name ends in /");
return {};
}
/**
* @return 0 if no error
*/
Error PathIterator::next(StringView &fileName) {
std::size_t size = 0;
auto retval = ox::Error(1);
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
retval = {};
if (m_path[m_iterator] == '/') {
m_iterator++;
}
const auto start = m_iterator;
// end is at the next /
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
// correct end if it is invalid, which happens if there is no next /
if (!substr) {
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
}
const auto end = static_cast<size_t>(substr - m_path);
size = end - start;
// cannot fit the output in the output parameter
if (size >= MaxFileNameLength) {
return ox::Error(1);
}
fileName = ox::substr(m_path, start, start + size);
// truncate trailing /
while (fileName.size() && fileName[fileName.size() - 1] == '/') {
fileName = ox::substr(m_path, start, start + size);
}
m_iterator += size;
oxAssert(fileName.size() == 0 || fileName[fileName.size()-1] != '/', "name ends in /");
}
return retval;
}
Result<std::size_t> PathIterator::nextSize() const {
std::size_t size = 0;
auto retval = ox::Error(1);
auto it = m_iterator;
if (it < m_maxSize && ox::strlen(&m_path[it])) {
retval = {};
if (m_path[it] == '/') {
it++;
}
const auto start = it;
// end is at the next /
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
// correct end if it is invalid, which happens if there is no next /
if (!substr) {
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
}
const auto end = static_cast<std::size_t>(substr - m_path);
size = end - start;
}
it += size;
return {size, retval};
}
bool PathIterator::hasNext() const {
std::size_t size = 0;
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
std::size_t start = m_iterator;
if (m_path[start] == '/') {
start++;
}
// end is at the next /
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
// correct end if it is invalid, which happens if there is no next /
if (!substr) {
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
}
const auto end = static_cast<std::size_t>(substr - m_path);
size = end - start;
}
return size > 0;
}
bool PathIterator::valid() const {
return m_iterator < m_maxSize && m_path[m_iterator] != 0;
}
PathIterator PathIterator::next() const {
std::size_t size = 0;
auto iterator = m_iterator;
if (iterator < m_maxSize && ox::strlen(&m_path[iterator])) {
if (m_path[iterator] == '/') {
iterator++;
}
const auto start = iterator;
// end is at the next /
const char *substr = ox::strchr(&m_path[start], '/', m_maxSize - start);
// correct end if it is invalid, which happens if there is no next /
if (!substr) {
substr = ox::strchr(&m_path[start], 0, m_maxSize - start);
}
const auto end = static_cast<std::size_t>(substr - m_path);
size = end - start;
}
iterator += size;
return {m_path, m_maxSize, iterator + 1};
}
const char *PathIterator::fullPath() const {
return m_path;
}
}
OX_CLANG_NOWARN_END
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <cstdio>
#include <fstream>
#include <ox/fs/fs.hpp>
struct Buff {
char *data = nullptr;
std::size_t size = 0;
};
static ox::Result<Buff> loadFsBuff(const char *path) noexcept {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.good()) {
oxErrorf("Could not find OxFS file: {}", path);
return ox::Error(1, "Could not find OxFS file");
}
try {
const auto size = static_cast<std::size_t>(file.tellg());
file.seekg(0, std::ios::beg);
const auto buff = new char[size];
file.read(buff, static_cast<std::streamsize>(size));
return Buff{buff, size};
} catch (const std::ios_base::failure &e) {
oxErrorf("Could not read OxFS file: {}", e.what());
return ox::Error(2, "Could not read OxFS file");
}
}
static ox::Result<ox::UPtr<ox::FileSystem>> loadFs(const char *path) noexcept {
OX_REQUIRE(buff, loadFsBuff(path));
return {ox::make_unique<ox::FileSystem32>(buff.data, buff.size)};
}
static ox::Error runLs(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
if (args.size() < 2) {
oxErr("Must provide a directory to ls\n");
return ox::Error(1);
}
OX_REQUIRE(files, fs->ls(args[1]));
for (const auto &file : files) {
oxOutf("{}\n", file);
}
return {};
}
static ox::Error runRead(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
if (args.size() < 2) {
oxErr("Must provide a path to a file to read\n");
return ox::Error(1);
}
OX_REQUIRE(buff, fs->read(ox::StringView(args[1])));
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
std::ignore = fwrite(buff.data(), sizeof(decltype(buff)::value_type), buff.size(), stdout);
OX_ALLOW_UNSAFE_BUFFERS_END
return {};
}
static ox::Error run(int argc, const char **argv) noexcept {
if (argc < 3) {
oxErr("OxFS file and subcommand arguments are required\n");
return ox::Error(1);
}
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
auto const fsPath = args[1];
ox::String subCmd(args[2]);
OX_REQUIRE(fs, loadFs(fsPath));
if (subCmd == "ls") {
return runLs(fs.get(), args + 2);
} else if (subCmd == "read") {
return runRead(fs.get(), args + 2);
}
return ox::Error(1);
}
int main(int argc, const char **argv) {
auto err = run(argc, argv);
oxAssert(err, "unhandled error");
return static_cast<int>(err);
}
+27
View File
@@ -0,0 +1,27 @@
add_executable(
FSTests
tests.cpp
)
target_link_libraries(
FSTests
OxFS
OxMetalClaw
)
add_test("[ox/fs] PtrArith::setSize" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PtrArith::setSize)
add_test("[ox/fs] PathIterator::next1" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next1)
add_test("[ox/fs] PathIterator::next2" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next2)
add_test("[ox/fs] PathIterator::next3" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next3)
add_test("[ox/fs] PathIterator::next4" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next4)
add_test("[ox/fs] PathIterator::next5" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::next5)
add_test("[ox/fs] PathIterator::hasNext" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::hasNext)
add_test("[ox/fs] PathIterator::dirPath" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests PathIterator::dirPath)
add_test("[ox/fs] NodeBuffer::insert" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "NodeBuffer::insert")
add_test("[ox/fs] FileStore::readWrite" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "FileStore::readWrite")
add_test("[ox/fs] Directory" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "Directory")
add_test("[ox/fs] FileSystem" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/FSTests "FileSystem")
+249
View File
@@ -0,0 +1,249 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// make sure asserts are enabled for the test file
#undef NDEBUG
#include <functional>
#include <map>
#include <string_view>
#include <ox/std/std.hpp>
#include <ox/fs/ptrarith/nodebuffer.hpp>
#include <ox/fs/filestore/filestoretemplate.hpp>
#include <ox/fs/filesystem/filesystem.hpp>
template<typename T>
struct OX_PACKED NodeType: public ox::ptrarith::Item<T> {
public:
[[nodiscard]]
size_t fullSize() const {
return this->size() + sizeof(*this);
}
};
const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests = {
{
{
"PtrArith::setSize",
[](ox::StringView) {
using BuffPtr_t = uint32_t;
ox::Vector<char> buff(5 * ox::units::MB);
auto buffer = new (buff.data()) ox::ptrarith::NodeBuffer<BuffPtr_t, NodeType<BuffPtr_t>>(buff.size());
using String = ox::IString<6>;
auto a1 = buffer->malloc(sizeof(String)).value;
auto a2 = buffer->malloc(sizeof(String)).value;
oxAssert(a1.valid(), "Allocation 1 failed.");
oxAssert(a2.valid(), "Allocation 2 failed.");
auto s1Buff = buffer->dataOf<String>(a1);
auto s2Buff = buffer->dataOf<String>(a2);
oxAssert(s1Buff.valid(), "s1 allocation 1 failed.");
oxAssert(s2Buff.valid(), "s2 allocation 2 failed.");
auto &s1 = *new (s1Buff) String("asdf");
auto &s2 = *new (s2Buff) String("aoeu");
oxTrace("test") << "s1: " << s1.c_str();
oxTrace("test") << "s2: " << s2.c_str();
oxAssert(s1 == "asdf", "Allocation 1 not as expected.");
oxAssert(s2 == "aoeu", "Allocation 2 not as expected.");
oxAssert(buffer->free(a1), "Free failed.");
oxAssert(buffer->free(a2), "Free failed.");
oxAssert(buffer->setSize(buffer->size() - buffer->available()), "Resize failed.");
return ox::Error(0);
}
},
{
"PathIterator::next1",
[](ox::StringView) {
auto constexpr path = ox::StringLiteral("/usr/share/charset.gbag");
ox::PathIterator it(path.c_str(), path.size());
ox::StringView buff;
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
oxAssert(it.next(buff) == 0 && buff == "charset.gbag", "PathIterator shows wrong next");
return ox::Error(0);
}
},
{
"PathIterator::next2",
[](ox::StringView) {
auto constexpr path = ox::StringView("/usr/share/");
ox::PathIterator it(path);
ox::StringView buff;
oxAssert(it.next(buff), "PathIterator::next returned error");
ox::expect(buff, "usr");
oxAssert(it.next(buff), "PathIterator::next returned error");
ox::expect(buff, "share");
return ox::Error(0);
}
},
{
"PathIterator::next3",
[](ox::StringView) {
auto const path = ox::String("/");
ox::PathIterator it(path.c_str(), path.size());
ox::StringView buff;
oxAssert(it.next(buff) == 0 && buff == "\0", "PathIterator shows wrong next");
return ox::Error(0);
}
},
{
"PathIterator::next4",
[](ox::StringView) {
auto constexpr path = ox::StringLiteral("usr/share/charset.gbag");
ox::PathIterator it(path);
ox::StringView buff;
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
oxAssert(it.next(buff) == 0 && buff == "charset.gbag", "PathIterator shows wrong next");
return ox::Error(0);
}
},
{
"PathIterator::next5",
[](ox::StringView) {
auto const path = ox::String("usr/share/");
ox::PathIterator it(path.c_str(), path.size());
ox::StringView buff;
oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
return ox::Error(0);
}
},
{
"PathIterator::dirPath",
[] (ox::StringView) {
auto constexpr path = ox::StringLiteral("/usr/share/charset.gbag");
ox::PathIterator it(path.c_str(), path.size());
auto buff = static_cast<char*>(ox_alloca(path.size() + 1));
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
oxAssert(it.dirPath(buff, path.size()) == 0 && ox::strcmp(buff, "/usr/share/") == 0, "PathIterator shows incorrect dir path");
OX_ALLOW_UNSAFE_BUFFERS_END
return ox::Error(0);
}
},
{
"PathIterator::hasNext",
[](ox::StringView) {
const auto path = "/file1";
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::PathIterator it(path, ox::strlen(path));
OX_ALLOW_UNSAFE_BUFFERS_END
oxAssert(it.hasNext(), "PathIterator shows incorrect hasNext");
oxAssert(!it.next().hasNext(), "PathIterator shows incorrect hasNext");
return ox::Error(0);
}
},
{
"Ptr::subPtr",
[](ox::StringView) {
constexpr auto buffLen = 5000;
ox::ptrarith::Ptr<uint8_t, uint32_t> p(ox_alloca(buffLen), buffLen, 500, 500);
oxAssert(p.valid(), "Ptr::subPtr: Ptr p is invalid.");
auto subPtr = p.subPtr<uint64_t>(50);
oxAssert(subPtr.valid(), "Ptr::subPtr: Ptr subPtr is invalid.");
return ox::Error(0);
}
},
{
"NodeBuffer::insert",
[](ox::StringView) {
constexpr auto buffLen = 5000;
auto list = new (ox_alloca(buffLen)) ox::ptrarith::NodeBuffer<uint32_t, ox::FileStoreItem<uint32_t>>(buffLen);
oxAssert(list->malloc(50).value.valid(), "NodeBuffer::insert: malloc 1 failed");
oxAssert(list->malloc(50).value.valid(), "NodeBuffer::insert: malloc 2 failed");
auto first = list->firstItem();
oxAssert(first.valid(), "NodeBuffer::insert: Could not access first item");
oxAssert(first->size() == 50, "NodeBuffer::insert: First item size invalid");
return ox::Error(0);
}
},
{
"FileStore::readWrite",
[](ox::StringView) {
constexpr auto buffLen = 5000;
constexpr auto str1 = "Hello, World!";
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
constexpr auto str1Len = ox::strlen(str1) + 1;
constexpr auto str2 = "Hello, Moon!";
constexpr auto str2Len = ox::strlen(str2) + 1;
OX_ALLOW_UNSAFE_BUFFERS_END
auto list = new (ox_alloca(buffLen)) ox::ptrarith::NodeBuffer<uint32_t, ox::FileStoreItem<uint32_t>>(buffLen);
oxAssert(ox::FileStore32::format(list, buffLen), "FileStore::format failed.");
ox::FileStore32 fileStore(list, buffLen);
oxAssert(fileStore.write(4, str1, str1Len, 1), "FileStore::write 1 failed.");
oxAssert(fileStore.write(5, str2, str2Len, 1), "FileStore::write 2 failed.");
char str1Read[str1Len];
size_t str1ReadSize = 0;
oxAssert(fileStore.read(4, reinterpret_cast<void*>(str1Read), str1Len, &str1ReadSize), "FileStore::read 1 failed.");
return ox::Error(0);
}
},
{
"Directory",
[](ox::StringView) {
ox::Vector<uint8_t> fsBuff(5000);
oxAssert(ox::FileStore32::format(fsBuff.data(), fsBuff.size()), "FS format failed");
ox::FileStore32 fileStore(fsBuff.data(), fsBuff.size());
ox::Directory32 dir(fileStore, 105);
oxTrace("ox.fs.test.Directory") << "Init";
oxAssert(dir.init(), "Init failed");
oxTrace("ox.fs.test.Directory") << "write 1";
oxAssert(dir.write("/file1", 1), "Directory write of file1 failed");
oxTrace("ox.fs.test.Directory") << "find";
oxAssert(dir.find("file1").error, "Could not find file1");
oxAssert(dir.find("file1").value == 1, "Could not find file1");
oxTrace("ox.fs.test.Directory") << "write 2";
oxAssert(dir.write("/file3", 3), "Directory write of file3 failed");
oxTrace("ox.fs.test.Directory") << "write 3";
oxAssert(dir.write("/file2", 2), "Directory write of file2 failed");
return ox::Error(0);
}
},
{
"FileSystem",
[](ox::StringView) {
ox::Vector<uint8_t> fsBuff(5000);
oxTrace("ox.fs.test.FileSystem") << "format";
oxAssert(ox::FileSystem32::format(fsBuff.data(), fsBuff.size()), "FileSystem format failed");
ox::FileSystem32 fs(ox::FileStore32(fsBuff.data(), fsBuff.size()));
oxTrace("ox.fs.test.FileSystem") << "mkdir";
oxAssert(fs.mkdir("/dir", true), "mkdir failed");
oxAssert(fs.stat("/dir").error, "mkdir failed");
oxAssert(fs.mkdir("/l1d1/l2d1/l3d1", true), "mkdir failed");
oxAssert(fs.stat("/l1d1/l2d1/l3d1").error, "mkdir failed");
oxAssert(fs.mkdir("/l1d1/l2d2", true), "mkdir failed");
oxAssert(fs.stat("/l1d1/l2d2").error, "mkdir failed");
return ox::Error(0);
}
},
},
};
int main(int argc, const char **argv) {
if (argc < 2) {
oxError("Must specify test to run");
return -1;
}
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
ox::StringView const testName = args[1];
ox::StringView const testArg = argc >= 3 ? args[2] : nullptr;
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(testArg), "Test returned Error");
return 0;
}
return -1;
}
+44
View File
@@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.10)
add_library(
OxLogConn
src/logconn.cpp
)
set_property(
TARGET
OxLogConn
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
if(NOT MSVC)
target_compile_options(OxLogConn PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
OxLogConn PUBLIC
OxStd
OxMetalClaw
$<$<BOOL:${OX_OS_FREEBSD}>:pthread>
$<$<BOOL:${OX_OS_WINDOWS}>:ws2_32>
)
target_include_directories(
OxLogConn PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS
OxLogConn
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
@@ -0,0 +1,103 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/assert.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/units.hpp>
namespace ox::detail {
class CirculerBuffer {
private:
std::size_t m_readPt = 0;
std::size_t m_writePt = 0;
ox::Buffer m_buff = ox::Buffer(ox::units::MB);
private:
[[nodiscard]]
constexpr auto avail() const noexcept {
if (m_writePt >= m_readPt) {
return m_buff.size() - (m_writePt - m_readPt);
} else {
return (m_buff.size() - m_writePt) - (m_buff.size() - m_readPt);
}
}
public:
constexpr ox::Error put(char v) noexcept {
return write(&v, 1);
if (1 > avail()) {
return ox::Error(1, "Insufficient space in buffer");
}
m_buff[m_writePt] = v;
++m_writePt;
return {};
}
constexpr ox::Error write(const char *buff, std::size_t sz) noexcept {
if (sz > avail()) {
return ox::Error(1, "Insufficient space in buffer");
}
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
// write seg 1
const auto seg1Sz = ox::min(sz, m_buff.size() - m_writePt);
ox::listcpy(&m_buff[m_writePt], &buff[0], seg1Sz);
m_writePt += sz;
if (seg1Sz != sz) {
m_writePt -= m_buff.size();
// write seg 2
const auto seg2Sz = sz - seg1Sz;
ox::listcpy(&m_buff[0], &buff[seg1Sz], seg2Sz);
oxAssert(m_buff[0] == buff[seg1Sz], "break");
}
OX_ALLOW_UNSAFE_BUFFERS_END
return {};
}
constexpr ox::Error seekp(std::size_t bytesFwd) noexcept {
if (bytesFwd > avail()) {
return ox::Error(1, "Insufficient space in buffer to seek that far ahead");
}
m_writePt += bytesFwd;
if (m_writePt > m_buff.size()) {
m_writePt -= m_buff.size();
}
return {};
}
constexpr ox::Error seekp(int, ios_base::seekdir) noexcept {
return ox::Error(1, "Unimplemented");
}
[[nodiscard]]
constexpr std::size_t tellp() const noexcept {
return m_buff.size() - avail();
}
[[nodiscard]]
constexpr std::size_t read(char *out, std::size_t outSize) noexcept {
const auto bytesRead = ox::min(outSize, m_buff.size() - avail());
// read seg 1
const auto seg1Sz = ox::min(bytesRead, m_buff.size() - m_readPt);
ox::listcpy(&out[0], &m_buff[m_readPt], seg1Sz);
m_readPt += bytesRead;
if (seg1Sz != bytesRead) {
m_readPt -= m_buff.size();
// read seg 2
const auto seg2Sz = bytesRead - seg1Sz;
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::listcpy(&out[seg1Sz], &m_buff[0], seg2Sz);
OX_ALLOW_UNSAFE_BUFFERS_END
}
return bytesRead;
}
};
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#if defined(DEBUG) && !defined(OX_BARE_METAL)
#define OX_INIT_DEBUG_LOGGER(loggerName, appName) \
ox::LoggerConn loggerName; \
{ \
const auto loggerErr = (loggerName).initConn(appName); \
if (loggerErr) { \
oxErrf("Could not connect to logger: {} ({}:{})\n", toStr(loggerErr), loggerErr.src.file_name(), loggerErr.src.line()); \
} else { \
ox::trace::setLogger(&(loggerName)); \
} \
ox::trace::init(); \
}
#else
#define OX_INIT_DEBUG_LOGGER(loggerName, appName)
#endif
@@ -0,0 +1,78 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#ifdef OX_USE_STDLIB
#include <condition_variable>
#include <mutex>
#include <thread>
#endif
#include <ox/mc/write.hpp>
#include <ox/std/trace.hpp>
#include "circularbuff.hpp"
namespace ox {
#ifdef OX_USE_STDLIB
class LoggerConn: public trace::Logger {
private:
int m_socket = 0;
detail::CirculerBuffer m_buff;
std::thread m_netThread;
std::condition_variable m_waitCond;
std::mutex m_waitMut;
std::mutex m_buffMut;
bool m_running = true;
public:
LoggerConn() noexcept;
LoggerConn(const LoggerConn&) noexcept = delete;
~LoggerConn() noexcept override;
LoggerConn &operator=(const LoggerConn&) noexcept = delete;
ox::Error send(const trace::TraceMsg&) noexcept final;
ox::Error sendInit(const trace::InitTraceMsg&) noexcept final;
ox::Error initConn(ox::StringViewCR appName) noexcept;
ox::Error send(const char *buff, std::size_t len) const noexcept;
private:
void msgSend() noexcept;
ox::Error send(trace::MsgId msgId, const auto &msg) noexcept {
ox::Array<char, 10 * ox::units::KB> buff;
std::size_t sz = 0;
OX_RETURN_ERROR(ox::writeMC(&buff[0], buff.size(), msg, &sz));
//std::printf("sz: %lu\n", sz);
OX_REQUIRE(szBuff, serialize(static_cast<uint32_t>(sz)));
std::unique_lock buffLk(m_buffMut);
OX_RETURN_ERROR(m_buff.put(static_cast<char>(msgId)));
OX_RETURN_ERROR(m_buff.write(szBuff.data(), szBuff.size()));
OX_RETURN_ERROR(m_buff.write(buff.data(), sz));
buffLk.unlock();
m_waitCond.notify_one();
return {};
}
};
#else
class LoggerConn: public trace::Logger {
private:
public:
constexpr LoggerConn() noexcept = default;
LoggerConn(const LoggerConn&) noexcept = delete;
constexpr ~LoggerConn() noexcept override = default;
LoggerConn &operator=(const LoggerConn&) noexcept = delete;
ox::Error send(const trace::TraceMsg&) noexcept final { return {}; }
static ox::Error initConn() noexcept { return {}; }
static ox::Error send(const char*, std::size_t) noexcept { return {}; }
};
#endif
}
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#ifdef OX_USE_STDLIB
#include <cstdio>
#ifndef _WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#else
#include <winsock.h>
#undef interface
#undef min
#undef max
#endif
#include <ox/logconn/logconn.hpp>
namespace ox {
#ifdef _WIN32
using Socket = SOCKET;
using LenType = int;
#else
using Socket = int;
using LenType = size_t;
#endif
using namespace trace;
static void closeSock(auto s) noexcept {
#ifdef _WIN32
closesocket(static_cast<Socket>(s));
#else
close(s);
#endif
}
LoggerConn::LoggerConn() noexcept: m_netThread([this]{this->msgSend();}) {
}
LoggerConn::~LoggerConn() noexcept {
m_running = false;
m_waitCond.notify_one();
m_netThread.join();
if (m_socket) {
closeSock(m_socket);
}
}
ox::Error LoggerConn::initConn(ox::StringViewCR appName) noexcept {
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(5590);
m_socket = static_cast<int>(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(connect(static_cast<Socket>(m_socket), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)))));
return sendInit({.appName = ox::BasicString<128>(appName)});
}
ox::Error LoggerConn::send(const char *buff, std::size_t len) const noexcept {
std::size_t totalSent = 0;
while (totalSent < len) {
//std::fprintf(stdout, "Sending %lu/%lu bytes on socket %d\n", len, totalSent, m_socket);
const auto sent = ::send(static_cast<Socket>(m_socket), buff, static_cast<LenType>(len), 0);
if (sent < 0) {
std::ignore = std::fprintf(stderr, "Could not send msg\n");
return ox::Error(1, "Could not send msg");
}
totalSent += static_cast<std::size_t>(sent);
}
return {};
}
ox::Error LoggerConn::send(const TraceMsg &msg) noexcept {
return send(MsgId::TraceEvent, msg);
}
ox::Error LoggerConn::sendInit(const InitTraceMsg &msg) noexcept {
return send(MsgId::Init, msg);
}
void LoggerConn::msgSend() noexcept {
try {
while (true) {
std::unique_lock lk(m_waitMut);
m_waitCond.wait(lk);
if (!m_running) {
break;
}
std::lock_guard const buffLk(m_buffMut);
while (true) {
Array<char, units::KB> tmp;
const auto read = m_buff.read(tmp.data(), tmp.size());
if (!read) {
break;
}
oxAssert(read <= tmp.size(), "logger trying to read too much data");
//std::printf("LoggerConn: sending %lu bytes\n", read);
std::ignore = send(tmp.data(), read);
}
}
} catch (std::exception const &e) {
oxErrf("Exception in logger thread: {}\n", e.what());
oxAssert(false, "logger thread exception");
}
}
}
#endif
+47
View File
@@ -0,0 +1,47 @@
add_library(
OxMetalClaw
src/read.cpp
src/write.cpp
)
if(NOT MSVC)
target_compile_options(OxMetalClaw PRIVATE -Wsign-conversion)
target_compile_options(OxMetalClaw PRIVATE -Wconversion)
endif()
target_link_libraries(
OxMetalClaw PUBLIC
OxModel
OxStd
)
if(NOT OX_BARE_METAL)
set_property(
TARGET
OxMetalClaw
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
endif()
target_include_directories(
OxMetalClaw PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS OxMetalClaw
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
namespace ox {
enum {
McPresenceMapOverflow = 1,
McBuffEnded = 2,
McOutputBuffEnded = 4
};
}
+199
View File
@@ -0,0 +1,199 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/assert.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/math.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/reader.hpp>
namespace ox::mc {
template<Integer_c T>
static constexpr auto Bits = sizeof(T) << 3;
/**
* Returns highest bit other than possible signed bit.
* Bit numbering starts at 0.
*/
template<Integer_c I>
[[nodiscard]]
constexpr size_t highestBit(I const val) noexcept {
unsigned shiftStart = sizeof(I) * 8 - 1;
// find the most significant non-sign indicator bit
size_t highestBit = 0;
// start at one bit lower if signed
if constexpr(is_signed_v<I>) {
--shiftStart;
}
for (auto i = shiftStart; i > 0; --i) {
auto const bitValue = (val >> i) & 1;
if (bitValue) {
highestBit = i;
break;
}
}
return highestBit;
}
static_assert(highestBit(static_cast<int8_t>(0b10000000)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-1)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-2)) == 0);
static_assert(highestBit(~static_cast<int8_t>(-3)) == 1);
static_assert(highestBit(1) == 0);
static_assert(highestBit(2) == 1);
static_assert(highestBit(4) == 2);
static_assert(highestBit(8) == 3);
static_assert(highestBit(static_cast<uint64_t>(1) << 31) == 31);
static_assert(highestBit(static_cast<uint64_t>(1) << 63) == 63);
struct McInt {
Array<uint8_t, 9> data{};
// length of integer in bytes
size_t length = 0;
};
template<Integer_c I>
[[nodiscard]]
constexpr McInt encodeInteger(I const pInput) noexcept {
auto const input = ResizedInt_t<I, 64>{pInput};
McInt out;
auto const inputNegative = is_signed_v<I> && input < 0;
// move input to uint64_t to allow consistent bit manipulation and to avoid
// overflow concerns
auto const val = std::bit_cast<uint64_t>(input);
if (val) {
// bits needed to represent number factoring in space possibly
// needed for signed bit
auto const highBit = inputNegative ? highestBit(~val) : highestBit(val);
auto const bits = highBit + 1 + (is_signed_v<I> ? 1 : 0);
// bytes needed to store value
size_t bytes = bits / 8 + (bits % 8 != 0);
auto const bitsAvailable = bytes * 8; // bits available to integer value
auto const bitsNeeded = bits + bytes;
// factor in bits needed for bytesIndicator (does not affect bytesIndicator)
// bits for integer + bits needed to represent bytes > bits available
if (bitsNeeded > bitsAvailable && bytes != 9) {
++bytes;
}
auto const bytesIndicator = onMask<uint8_t>(bytes - 1);
// ensure we are copying from little endian representation
LittleEndian<uint64_t> leVal = val;
if (inputNegative) {
leVal |= static_cast<uint64_t>(1 << (bitsNeeded - 1));
}
if (bytes == 9) {
out.data[0] = bytesIndicator;
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::memcpy(&out.data[1], &leVal, 8);
OX_ALLOW_UNSAFE_BUFFERS_END
if (inputNegative) {
out.data[1] |= 0b1000'0000;
}
} else {
auto const valBits = bytes * 8;
uint64_t const negBit = inputNegative ? 1 : 0;
auto const intermediate =
static_cast<uint64_t>(leVal.raw() | (negBit << (valBits - 1))) << bytes |
static_cast<uint64_t>(bytesIndicator);
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::memcpy(&out.data[0], &intermediate, sizeof(intermediate));
OX_ALLOW_UNSAFE_BUFFERS_END
}
out.length = bytes;
}
return out;
}
/**
* Returns the number of bytes indicated by the bytes indicator of a variable
* length integer.
*/
[[nodiscard]]
constexpr size_t countBytes(unsigned const b) noexcept {
size_t i = 0;
while ((b >> i) & 1) ++i;
return i + 1;
}
static_assert(countBytes(0b0000'0000) == 1);
static_assert(countBytes(0b0000'0001) == 2);
static_assert(countBytes(0b0000'0011) == 3);
static_assert(countBytes(0b0000'0111) == 4);
static_assert(countBytes(0b0000'1111) == 5);
static_assert(countBytes(0b0001'1111) == 6);
static_assert(countBytes(0b0011'1111) == 7);
static_assert(countBytes(0b0111'1111) == 8);
static_assert(countBytes(0b1111'1111) == 9);
template<Integer_c I>
constexpr Result<I> decodeInteger(Reader_c auto &rdr, size_t &bytesRead) noexcept {
uint8_t firstByte = 0;
OX_RETURN_ERROR(rdr.read(&firstByte, 1));
OX_RETURN_ERROR(rdr.seekg(-1, ox::ios_base::cur));
auto const bytes = countBytes(firstByte);
if (bytes == 9) {
bytesRead = bytes;
I out = 0;
OX_RETURN_ERROR(rdr.seekg(1, ox::ios_base::cur));
OX_RETURN_ERROR(rdr.read(&out, sizeof(I)));
return fromLittleEndian<I>(out);
}
bytesRead = bytes;
uint64_t decoded = 0;
OX_RETURN_ERROR(rdr.read(&decoded, bytes));
decoded >>= bytes;
// move sign bit
if constexpr(is_signed_v<I>) {
auto const negBit = bytes * 8 - bytes - 1;
// move sign
auto const negative = (decoded >> negBit) == 1;
if (negative) {
// fill in all bits between encoded sign and real sign with 1s
// split it up because the 32-bit ARM can't shift more than 32 bits
Array<uint32_t, 2> d = {};
//d[0] = decoded & 0xffff'ffff;
//d[1] = decoded >> 32;
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::memcpy(&d[0], &decoded, sizeof(decoded));
OX_ALLOW_UNSAFE_BUFFERS_END
auto bit = negBit;
for (; bit < ox::min<size_t>(Bits<I>, 32); ++bit) {
d[0] |= 1 << bit;
}
bit -= 32;
for (; bit < Bits<I>; ++bit) {
d[1] |= 1 << bit;
}
I out = 0;
if constexpr(ox::defines::BigEndian) {
auto const d0Tmp = d[0];
d[0] = d[1];
d[1] = d0Tmp;
}
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::memcpy(&out, &d[0], sizeof(out));
OX_ALLOW_UNSAFE_BUFFERS_END
return out;
}
}
return static_cast<I>(decoded);
}
template<Integer_c I>
Result<I> decodeInteger(McInt const &m) noexcept {
size_t bytesRead{};
BufferReader br({reinterpret_cast<const char*>(m.data.data()), 9});
return decodeInteger<I>(br, bytesRead);
}
}
+14
View File
@@ -0,0 +1,14 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "intops.hpp"
#include "read.hpp"
#include "types.hpp"
#include "write.hpp"
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/span.hpp>
#include <ox/std/types.hpp>
#include <ox/std/reader.hpp>
#include "err.hpp"
namespace ox {
template<Reader_c Reader>
class FieldBitmapReader {
protected:
mutable size_t m_mapBlockIdx = ~size_t{};
mutable uint64_t m_mapBlock{};
size_t m_mapStart{};
Reader &m_reader;
public:
explicit constexpr FieldBitmapReader(Reader &reader) noexcept:
m_mapStart(reader.tellg()),
m_reader(reader) {
}
constexpr Result<bool> get(size_t idx) const noexcept {
constexpr auto blockBits = sizeof(m_mapBlock);
auto const blockIdx = idx / blockBits;
if (m_mapBlockIdx != blockIdx) [[unlikely]] {
OX_RETURN_ERROR(loadMapBlock(blockIdx));
}
idx %= blockBits;
return (m_mapBlock >> idx) & 1;
}
private:
constexpr Error loadMapBlock(size_t const idx) const noexcept {
OX_REQUIRE(g, m_reader.tellg());
OX_RETURN_ERROR(m_reader.seekg(static_cast<int>(m_mapStart + idx), ox::ios_base::beg));
Array<char, sizeof(m_mapBlock)> mapBlock{};
OX_RETURN_ERROR(m_reader.read(mapBlock.data(), sizeof(m_mapBlock)));
// Warning: narrow-conv
OX_RETURN_ERROR(m_reader.seekg(static_cast<int>(g), ox::ios_base::beg));
m_mapBlock = 0;
for (uint64_t i{}; auto b : mapBlock) {
m_mapBlock |= static_cast<uint64_t>(std::bit_cast<uint8_t>(b)) << i;
i += 8;
}
m_mapBlockIdx = idx;
return {};
}
};
class FieldBitmapWriter {
protected:
Span<char> m_map;
size_t m_mapLen{};
public:
explicit constexpr FieldBitmapWriter(Span<char> const &map) noexcept:
m_map(map),
m_mapLen(m_map.size()) {
}
constexpr auto setBuffer(Span<char> const &map) noexcept {
m_map = map;
m_mapLen = map.size();
}
constexpr Result<bool> get(size_t const i) const noexcept {
if (i / 8 < m_mapLen) {
return (std::bit_cast<uint8_t>(m_map[i / 8]) >> (i % 8)) & 1;
}
return Error{McPresenceMapOverflow};
}
constexpr Error setFields(int const fields) noexcept {
m_mapLen = static_cast<size_t>((fields / 8 + 1) - (fields % 8 == 0));
if (m_mapLen > m_map.size()) [[unlikely]] {
return Error{McPresenceMapOverflow};
}
return {};
}
constexpr void setMaxLen(int const maxLen) noexcept {
m_mapLen = static_cast<size_t>(maxLen);
}
constexpr int64_t getMaxLen() const noexcept {
return static_cast<int64_t>(m_mapLen);
}
constexpr Error set(size_t const i, bool const on) noexcept {
if (i / 8 < m_mapLen) {
char &actual = m_map[i / 8];
uint8_t v = std::bit_cast<uint8_t>(actual);
if (on) {
v |= 1 << (i % 8);
} else {
v &= ~static_cast<uint8_t>(1 << (i % 8));
}
actual = std::bit_cast<char>(v);
return {};
}
return Error{McPresenceMapOverflow};
}
};
}
+558
View File
@@ -0,0 +1,558 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/model/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/model/types.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/optional.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/vector.hpp>
#include "err.hpp"
#include "intops.hpp"
#include "presenceindicator.hpp"
#include "types.hpp"
namespace ox {
template<Reader_c Reader>
class MetalClawReaderTemplate: public ModelHandlerBase<MetalClawReaderTemplate<Reader>, ox::OpType::Read> {
private:
FieldBitmapReader<Reader> m_fieldPresence;
size_t m_fields{};
size_t m_field{};
Optional<int> const m_unionIdx{};
Reader &m_reader;
public:
explicit constexpr MetalClawReaderTemplate(
Reader &reader,
Optional<int> const &unionIdx = {}) noexcept;
constexpr ~MetalClawReaderTemplate() noexcept;
constexpr Error field(CString, int8_t *val) noexcept;
constexpr Error field(CString, int16_t *val) noexcept;
constexpr Error field(CString, int32_t *val) noexcept;
constexpr Error field(CString, int64_t *val) noexcept;
constexpr Error field(CString, uint8_t *val) noexcept;
constexpr Error field(CString, uint16_t *val) noexcept;
constexpr Error field(CString, uint32_t *val) noexcept;
constexpr Error field(CString, uint64_t *val) noexcept;
constexpr Error field(CString, bool *val) noexcept;
// array handler
constexpr Error field(CString, auto *val, size_t valLen) noexcept;
// map handler
template<typename T>
constexpr Error field(CString, HashMap<String, T> *val) noexcept;
// array handler, with callback to allow handling individual elements
template<typename T, typename CB>
constexpr Error field(CString, CB cb) noexcept;
template<typename T>
constexpr Error field(CString, T *val) noexcept;
template<typename U, bool force>
constexpr Error field(CString, UnionView<U, force> val) noexcept;
template<size_t SmallStringSize>
constexpr Error field(CString, BasicString<SmallStringSize> *val) noexcept;
template<size_t L>
constexpr Error field(CString, IString<L> *val) noexcept;
constexpr Error fieldCString(CString, char *val, size_t buffLen) noexcept;
constexpr Error fieldCString(CString, char **val) noexcept;
constexpr Error fieldCString(CString, char **val, size_t buffLen) noexcept;
/**
* Reads an array length from the current location in the buffer.
* @param pass indicates that the parsing should iterate past the array length
*/
constexpr Result<ArrayLength> arrayLength(const char *name, bool pass = true) noexcept;
/**
* Reads an string length from the current location in the buffer.
*/
constexpr Result<StringLength> stringLength(const char *name) noexcept;
template<typename T = std::nullptr_t>
constexpr ox::Error setTypeInfo(
const char *name = T::TypeName,
int version = T::TypeVersion,
const Vector<String>& = {},
size_t fields = ModelFieldCount_v<T>) noexcept;
/**
* Returns a MetalClawReader to parse a child object.
*/
[[nodiscard]]
constexpr MetalClawReaderTemplate<Reader> child(const char *name, Optional<int> unionIdx = {}) noexcept;
/**
* Indicates whether or not the next field to be read is present.
*/
[[nodiscard]]
constexpr bool fieldPresent(const char *name) const noexcept;
/**
* Indicates whether or not the given field is present.
*/
[[nodiscard]]
constexpr bool fieldPresent(int fieldNo) const noexcept;
[[nodiscard]]
constexpr int whichFieldPresent(const char *name, ModelUnion const&) const noexcept;
constexpr void nextField() noexcept;
private:
template<typename I>
constexpr Error readInteger(I &val) noexcept;
};
template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader>::MetalClawReaderTemplate(
Reader &reader,
Optional<int> const &unionIdx) noexcept:
m_fieldPresence(reader),
m_unionIdx(unionIdx),
m_reader(reader) {
}
template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader>::~MetalClawReaderTemplate() noexcept {
if (m_field != m_fields) {
oxTrace("ox.mc.MetalClawReader.error") << "MetalClawReader: incorrect fields number given";
}
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int8_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int16_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int32_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int64_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint8_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint16_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint32_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint64_t *val) noexcept {
return readInteger(*val);
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, bool *val) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
auto const result = m_fieldPresence.get(static_cast<size_t>(m_field));
*val = result.value;
OX_RETURN_ERROR(result);
}
++m_field;
return {};
}
// array handler
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(
const char *name, auto *val, size_t const valLen) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
// read the list
if (valLen >= len) {
auto reader = child({});
auto &handler = *reader.interface();
OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
for (size_t i = 0; i < len; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
OX_RETURN_ERROR(handler.field({}, &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
}
} else {
oxTracef("ox.mc.read.field(T)", "{}, length: {}", name, valLen);
return ox::Error(McOutputBuffEnded);
}
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<typename T>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, HashMap<String, T> *val) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
OX_REQUIRE(g, m_reader.tellg());
size_t bytesRead = 0;
OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
OX_RETURN_ERROR(m_reader.seekg(g));
// read the list
auto reader = child("");
auto &handler = *reader.interface();
OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
// this loop body needs to be in a lambda because of the potential alloca call
constexpr auto loopBody = [](auto &handler, auto &val) {
OX_REQUIRE(keyLen, handler.stringLength(nullptr));
auto wkey = ox_malloca(keyLen + 1, char, 0);
auto wkeyPtr = wkey.get();
OX_RETURN_ERROR(handler.fieldCString("", &wkeyPtr, keyLen + 1));
return handler.field("", &val[wkeyPtr]);
};
for (size_t i = 0; i < len; ++i) {
OX_RETURN_ERROR(loopBody(handler, *val));
}
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<typename T>
constexpr Error MetalClawReaderTemplate<Reader>::field(const char *name, T *val) noexcept {
if constexpr(isVector_v<T>) {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
// set size of val if the field is present, don't worry about it if not
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
OX_REQUIRE(len, arrayLength(name, false));
OX_RETURN_ERROR(ox::resizeVector(*val, len));
return field(name, val->data(), val->size());
}
OX_RETURN_ERROR(ox::resizeVector(*val, 0));
}
++m_field;
return {};
} else if constexpr(isArray_v<T>) {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
// set size of val if the field is present, don't worry about it if not
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
OX_REQUIRE(len, arrayLength(name, false));
if (len > val->size()) {
return ox::Error(1, "Input array is too long");
}
}
return field(name, val->data(), val->size());
}
++m_field;
return {};
} else {
if ((!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) && val) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
auto reader = child("");
OX_RETURN_ERROR(model(reader.interface(), val));
}
}
++m_field;
return {};
}
}
template<Reader_c Reader>
template<typename U, bool force>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, UnionView<U, force> val) noexcept {
if ((!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) && val.get()) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
auto reader = child("", ox::Optional<int>(ox::in_place, val.idx()));
OX_RETURN_ERROR(model(reader.interface(), val.get()));
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<size_t SmallStringSize>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, BasicString<SmallStringSize> *val) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
const auto cap = size;
*val = BasicString<SmallStringSize>(cap);
auto data = val->data();
// read the string
OX_RETURN_ERROR(m_reader.read(data, size));
} else {
*val = "";
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<size_t L>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, IString<L> *val) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
*val = IString<L>();
OX_RETURN_ERROR(val->resize(size));
auto const data = val->data();
// read the string
OX_RETURN_ERROR(m_reader.read(data, size));
} else {
*val = "";
}
}
++m_field;
return {};
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(
CString, char *val, size_t const buffLen) noexcept {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
if (size > buffLen) {
return ox::Error(McOutputBuffEnded);
}
// re-allocate in case too small
auto data = val;
// read the string
OX_RETURN_ERROR(m_reader.read(data, size));
data[size] = 0;
}
++m_field;
return {};
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(CString, char **val) noexcept {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
// re-allocate in case too small
safeDelete(*val);
*val = new char[size + 1];
auto data = ox::Span{*val, size + 1};
// read the string
OX_RETURN_ERROR(m_reader.read(data.data(), size));
data[size] = 0;
}
++m_field;
return {};
}
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(CString, char **val, size_t buffLen) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
// re-allocate if too small
if (buffLen < size + 1) {
safeDelete(*val);
*val = new char[size + 1];
buffLen = size + 1;
}
auto data = ox::Span{*val, size + 1};
// read the string
OX_RETURN_ERROR(m_reader.read(data.data(), size));
data[size] = 0;
} else {
auto data = *val;
if (data) {
data[0] = 0;
}
}
}
++m_field;
return {};
}
template<Reader_c Reader>
constexpr Result<ArrayLength> MetalClawReaderTemplate<Reader>::arrayLength(CString, bool const pass) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(g, m_reader.tellg());
OX_REQUIRE(out, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
if (!pass) {
OX_RETURN_ERROR(m_reader.seekg(g));
}
return out;
}
}
return ox::Error(1);
}
template<Reader_c Reader>
constexpr Result<StringLength> MetalClawReaderTemplate<Reader>::stringLength(CString) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
auto len = mc::decodeInteger<StringLength>(m_reader, bytesRead);
OX_RETURN_ERROR(m_reader.seekg(-static_cast<int64_t>(bytesRead), ox::ios_base::cur));
return len;
}
}
return 0;
}
template<Reader_c Reader>
template<typename I>
constexpr Error MetalClawReaderTemplate<Reader>::readInteger(I &val) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
size_t bytesRead = 0;
auto const result = mc::decodeInteger<I>(m_reader, bytesRead);
OX_RETURN_ERROR(result);
val = result.value;
} else {
val = 0;
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<typename T, typename CB>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, CB cb) noexcept {
if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
// read the length
size_t bytesRead = 0;
OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
// read the list
auto reader = child("");
auto &handler = *reader.interface();
OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
for (size_t i = 0; i < len; ++i) {
T val;
OX_RETURN_ERROR(handler.field("", &val));
OX_RETURN_ERROR(cb(i, &val));
}
}
}
++m_field;
return {};
}
template<Reader_c Reader>
template<typename T>
constexpr ox::Error MetalClawReaderTemplate<Reader>::setTypeInfo(
CString, int, const Vector<String>&, size_t const fields) noexcept {
m_fields = fields;
// Warning: narrow-conv
return m_reader.seekg(
static_cast<int>((fields / 8 + 1) - (fields % 8 == 0)),
ox::ios_base::cur);
}
template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader> MetalClawReaderTemplate<Reader>::child(
CString,
Optional<int> const unionIdx) noexcept {
return MetalClawReaderTemplate<Reader>(m_reader, unionIdx);
}
template<Reader_c Reader>
constexpr bool MetalClawReaderTemplate<Reader>::fieldPresent(CString) const noexcept {
return m_fieldPresence.get(static_cast<size_t>(m_field)).value;
}
template<Reader_c Reader>
constexpr bool MetalClawReaderTemplate<Reader>::fieldPresent(int const fieldNo) const noexcept {
return m_fieldPresence.get(static_cast<size_t>(fieldNo)).value;
}
template<Reader_c Reader>
[[nodiscard]]
constexpr int MetalClawReaderTemplate<Reader>::whichFieldPresent(CString, ModelUnion const &u) const noexcept {
FieldBitmapReader<Reader> p(m_reader);
for (auto i = 0u; i < u.fieldCount(); ++i) {
if (p.get(i)) {
return static_cast<int>(i);
}
}
return -1;
}
template<Reader_c Reader>
constexpr void MetalClawReaderTemplate<Reader>::nextField() noexcept {
++m_field;
}
using MetalClawReader = MetalClawReaderTemplate<ox::BufferReader>;
template<typename T>
Error readMC(ox::BufferView const buff, T &val) noexcept {
BufferReader br(buff);
MetalClawReader reader(br);
return model(reader.interface(), &val);
}
template<typename T>
Result<T> readMC(ox::BufferView buff) noexcept {
Result<T> val;
OX_RETURN_ERROR(readMC(buff, val.value));
return val;
}
extern template class ModelHandlerInterface<MetalClawReaderTemplate<BufferReader>>;
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/string.hpp>
#include <ox/std/strops.hpp>
#include <ox/std/types.hpp>
namespace ox {
using StringLength = std::size_t;
using ArrayLength = std::size_t;
}
+401
View File
@@ -0,0 +1,401 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/model/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/optional.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include <ox/std/units.hpp>
#include "intops.hpp"
#include "err.hpp"
#include "presenceindicator.hpp"
#include "types.hpp"
namespace ox {
template<Writer_c Writer>
class MetalClawWriter: public ModelHandlerBase<MetalClawWriter<Writer>, OpType::Write> {
private:
Vector<char, 16> m_presenceMapBuff{};
FieldBitmapWriter m_fieldPresence{m_presenceMapBuff};
int m_field{};
Optional<int> m_unionIdx;
size_t m_writerBeginP{};
Writer &m_writer;
public:
constexpr explicit MetalClawWriter(Writer &writer, Optional<int> const &unionIdx = {}) noexcept;
constexpr ~MetalClawWriter() noexcept = default;
constexpr Error field(CString, int8_t const *val) noexcept;
constexpr Error field(CString, int16_t const *val) noexcept;
constexpr Error field(CString, int32_t const *val) noexcept;
constexpr Error field(CString, int64_t const *val) noexcept;
constexpr Error field(CString, uint8_t const *val) noexcept;
constexpr Error field(CString, uint16_t const *val) noexcept;
constexpr Error field(CString, uint32_t const *val) noexcept;
constexpr Error field(CString, uint64_t const *val) noexcept;
constexpr Error field(CString, bool const *val) noexcept;
template<typename T>
constexpr Error field(CString, T const *val, size_t len) noexcept;
template<typename T>
constexpr Error field(CString name, HashMap<String, T> const *val) noexcept;
template<size_t SmallStringSize>
constexpr Error field(CString, BasicString<SmallStringSize> const *val) noexcept;
template<size_t L>
constexpr Error field(CString, IString<L> const *val) noexcept;
constexpr Error fieldCString(CString name, CString const*val, size_t buffLen) noexcept;
constexpr Error fieldCString(CString name, CString *val) noexcept;
constexpr Error fieldCString(CString name, CString const*val) noexcept;
constexpr Error fieldCString(CString name, CString val, size_t strLen) noexcept;
template<typename T>
constexpr Error field(CString, T const *val) noexcept;
template<typename U, bool force = false>
constexpr Error field(CString, UnionView<U, force> val) noexcept;
template<typename T = std::nullptr_t>
constexpr Error setTypeInfo(
CString name = T::TypeName,
int version = T::TypeVersion,
Vector<String> const& = {},
size_t fields = ModelFieldCount_v<T>) noexcept;
/**
* stringLength is not implemented in MetalClawWriter
*/
[[nodiscard]]
constexpr auto stringLength(CString) noexcept {
return 0;
}
/**
* stringLength is not implemented in MetalClawWriter
*/
[[nodiscard]]
constexpr auto arrayLength(CString, bool = true) noexcept {
return 0;
}
constexpr Error finalize() noexcept;
private:
constexpr Error appendInteger(Integer_c auto val) noexcept {
bool fieldSet = false;
if (val && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
auto mi = mc::encodeInteger(val);
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(mi.data.data()), mi.length));
fieldSet = true;
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
};
extern template class ModelHandlerInterface<MetalClawWriter<BufferWriter>>;
extern template class ModelHandlerInterface<MetalClawWriter<CharBuffWriter>>;
template<Writer_c Writer>
constexpr MetalClawWriter<Writer>::MetalClawWriter(Writer &writer, Optional<int> const &unionIdx) noexcept:
m_unionIdx(unionIdx),
m_writerBeginP(writer.tellp()),
m_writer(writer) {
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, int8_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, int16_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, int32_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, int64_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, uint8_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, uint16_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, uint32_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, uint64_t const *val) noexcept {
return appendInteger(*val);
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, bool const *val) noexcept {
if (!m_unionIdx.has_value() || *m_unionIdx == m_field) {
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), *val));
}
++m_field;
return {};
}
template<Writer_c Writer>
template<size_t SmallStringSize>
constexpr Error MetalClawWriter<Writer>::field(CString, BasicString<SmallStringSize> const *val) noexcept {
bool fieldSet = false;
if (val->size() && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
// write the length
auto const strLen = mc::encodeInteger(val->size());
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLen.data.data()), strLen.length));
// write the string
OX_RETURN_ERROR(m_writer.write(val->c_str(), static_cast<size_t>(val->size())));
fieldSet = true;
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
template<size_t L>
constexpr Error MetalClawWriter<Writer>::field(CString name, IString<L> const *val) noexcept {
return fieldCString(name, val->data(), val->size());
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString, CString const *val, size_t) noexcept {
bool fieldSet = false;
if (!m_unionIdx.has_value() || *m_unionIdx == m_field) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
// this strlen is tolerated because sometimes 0 gets passed to
// the size param, which is a lie
// this code should be cleaned up at some point...
auto const strLen = *val ? ox::strlen(*val) : 0;
OX_ALLOW_UNSAFE_BUFFERS_END
// write the length
auto const strLenBuff = mc::encodeInteger(strLen);
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLenBuff.data.data()), strLenBuff.length));
// write the string
OX_RETURN_ERROR(m_writer.write(*val, static_cast<size_t>(strLen)));
fieldSet = true;
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString const name, CString *val) noexcept {
return fieldCString(name, val, {});
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString const name, CString const *val) noexcept {
return fieldCString(name, val, {});
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString, CString const val, size_t const strLen) noexcept {
bool fieldSet = false;
if (strLen && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
// write the length
auto const strLenBuff = mc::encodeInteger(strLen);
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLenBuff.data.data()), strLenBuff.length));
// write the string
OX_RETURN_ERROR(m_writer.write(val, static_cast<size_t>(strLen)));
fieldSet = true;
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, T const *val) noexcept {
if constexpr(isVector_v<T> || isArray_v<T>) {
return field(nullptr, val->data(), val->size());
} else {
bool fieldSet = false;
if (val && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
auto const writeIdx = m_writer.tellp();
MetalClawWriter writer(m_writer);
OX_RETURN_ERROR(model(writer.interface(), val));
OX_RETURN_ERROR(writer.finalize());
fieldSet = writeIdx != m_writer.tellp();
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
}
template<Writer_c Writer>
template<typename U, bool force>
constexpr Error MetalClawWriter<Writer>::field(CString, UnionView<U, force> val) noexcept {
bool fieldSet = false;
if (val.get() && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
auto const writeIdx = m_writer.tellp();
MetalClawWriter writer(m_writer, Optional<int>(in_place, val.idx()));
OX_RETURN_ERROR(model(writer.interface(), val.get()));
OX_RETURN_ERROR(writer.finalize());
fieldSet = writeIdx != m_writer.tellp();
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, T const *val, size_t const len) noexcept {
bool fieldSet = false;
if (len && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
// write the length
auto const arrLen = mc::encodeInteger(len);
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(arrLen.data.data()), arrLen.length));
auto const writeIdx = m_writer.tellp();
MetalClawWriter writer(m_writer);
OX_RETURN_ERROR(writer.interface()->template setTypeInfo<T>("List", 0, {}, static_cast<size_t>(len)));
// write the array
for (size_t i{}; i < len; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
OX_RETURN_ERROR(writer.interface()->field("", &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
}
OX_RETURN_ERROR(writer.finalize());
fieldSet = writeIdx != m_writer.tellp();
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, HashMap<String, T> const *val) noexcept {
auto const &keys = val->keys();
auto const len = keys.size();
bool fieldSet = false;
if (len && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
// write the length
auto const arrLen = mc::encodeInteger(len);
OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(arrLen.data.data()), arrLen.length));
// write map
MetalClawWriter writer(m_writer);
// double len for both key and value
OX_RETURN_ERROR(writer.interface()->setTypeInfo("Map", 0, {}, len * 2));
// this loop body needs to be in a lambda because of the potential alloca call
constexpr auto loopBody = [](auto &handler, auto const &key, auto const &val) -> Error {
auto const keyLen = key.size();
auto wkey = ox_malloca(keyLen + 1, char, 0);
memcpy(wkey.get(), key.c_str(), keyLen + 1);
OX_RETURN_ERROR(handler.fieldCString("", wkey.get(), keyLen));
OX_REQUIRE_M(value, val.at(key));
return handler.field("", value);
};
// write the array
for (size_t i{}; i < len; ++i) {
auto const &key = keys[i];
OX_RETURN_ERROR(loopBody(*writer.interface(), key, *val));
}
OX_RETURN_ERROR(writer.finalize());
fieldSet = true;
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
++m_field;
return {};
}
template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::setTypeInfo(
CString,
int,
Vector<String> const&,
size_t const fields) noexcept {
auto const fieldPresenceLen = (fields - 1) / 8 + 1;
OX_RETURN_ERROR(m_writer.write(nullptr, fieldPresenceLen));
m_presenceMapBuff.resize(fieldPresenceLen);
m_fieldPresence.setBuffer(m_presenceMapBuff);
return m_fieldPresence.setFields(static_cast<int>(fields));
}
template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::finalize() noexcept {
auto const end = m_writer.tellp();
OX_RETURN_ERROR(m_writer.seekp(m_writerBeginP));
OX_RETURN_ERROR(m_writer.write(
m_presenceMapBuff.data(),
m_presenceMapBuff.size()));
OX_RETURN_ERROR(m_writer.seekp(end));
return {};
}
Result<Buffer> writeMC(Writer_c auto &writer, auto const &val) noexcept {
MetalClawWriter mcWriter(writer);
OX_RETURN_ERROR(model(mcWriter.interface(), &val));
OX_RETURN_ERROR(mcWriter.finalize());
return {};
}
Result<Buffer> writeMC(auto const &val, size_t const buffReserveSz = 2 * units::KB) noexcept {
Buffer buff(buffReserveSz);
BufferWriter bw(&buff, 0);
OX_RETURN_ERROR(writeMC(bw, val));
buff.resize(bw.tellp());
return buff;
}
Error writeMC(char *buff, size_t const buffLen, auto const &val, size_t *sizeOut = nullptr) noexcept {
CharBuffWriter bw{{buff, buffLen}};
OX_RETURN_ERROR(writeMC(bw, val));
if (sizeOut) {
*sizeOut = bw.tellp();
}
return {};
}
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/reader.hpp>
#include <ox/mc/read.hpp>
namespace ox {
template class ModelHandlerInterface<MetalClawReaderTemplate<BufferReader>>;
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/assert.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/trace.hpp>
#include <ox/mc/write.hpp>
namespace ox {
template class ModelHandlerInterface<MetalClawWriter<BufferWriter>>;
template class ModelHandlerInterface<MetalClawWriter<CharBuffWriter>>;
}
+16
View File
@@ -0,0 +1,16 @@
add_executable(
McTest
tests.cpp
)
target_link_libraries(
McTest
OxMetalClaw
)
add_test("[ox/mc] Writer" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest MetalClawWriter)
add_test("[ox/mc] Reader" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest MetalClawReader)
#add_test("[ox/mc] MetalClawDef" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest MetalClawDef)
add_test("[ox/mc] MetalClawModelValue" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest MetalClawModelValue)
add_test("[ox/mc] encodeInteger" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest encodeInteger)
add_test("[ox/mc] decodeInteger" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/McTest decodeInteger)
+476
View File
@@ -0,0 +1,476 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#undef NDEBUG
#include <iostream>
#include <map>
#include <ox/mc/mc.hpp>
#include <ox/model/model.hpp>
#include <ox/std/std.hpp>
union TestUnion {
static constexpr auto TypeName = "TestUnion";
static constexpr auto TypeVersion = 1;
bool Bool;
uint32_t Int;
char *CString{};
};
struct TestStructNest {
static constexpr auto TypeName = "TestStructNest";
static constexpr auto TypeVersion = 1;
bool Bool = false;
uint32_t Int = 0;
ox::IString<32> IString = "";
};
struct TestStruct {
static constexpr auto TypeName = "TestStruct";
static constexpr auto TypeVersion = 1;
bool Bool = false;
int32_t Int = 0;
int32_t Int1 = 0;
int32_t Int2 = 0;
int32_t Int3 = 0;
int32_t Int4 = 0;
int32_t Int5 = 0;
int32_t Int6 = 0;
int32_t Int7 = 0;
int32_t Int8 = 0;
int unionIdx = 1;
TestUnion Union;
ox::String String;
ox::IString<32> IString = "";
uint32_t List[4] = {0, 0, 0, 0};
ox::Vector<uint32_t> Vector = {1, 2, 3, 4, 5};
ox::Vector<uint32_t> Vector2 = {1, 2, 3, 4, 5};
ox::HashMap<ox::String, int> Map;
TestStructNest EmptyStruct;
TestStructNest Struct;
constexpr ~TestStruct() noexcept {
if (unionIdx == 2) {
ox::safeDelete(Union.CString);
}
}
};
template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestUnion> auto *obj) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<TestUnion>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->fieldCString("CString", &obj->CString));
return ox::Error(0);
}
OX_MODEL_BEGIN(TestStructNest)
OX_MODEL_FIELD(Bool)
OX_MODEL_FIELD(Int)
OX_MODEL_FIELD(IString)
OX_MODEL_END()
template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestStruct> auto *obj) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<TestStruct>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->field("Int1", &obj->Int1));
OX_RETURN_ERROR(io->field("Int2", &obj->Int2));
OX_RETURN_ERROR(io->field("Int3", &obj->Int3));
OX_RETURN_ERROR(io->field("Int4", &obj->Int4));
OX_RETURN_ERROR(io->field("Int5", &obj->Int5));
OX_RETURN_ERROR(io->field("Int6", &obj->Int6));
OX_RETURN_ERROR(io->field("Int7", &obj->Int7));
OX_RETURN_ERROR(io->field("Int8", &obj->Int8));
OX_RETURN_ERROR(io->field("unionIdx", &obj->unionIdx));
if constexpr(T::opType() == ox::OpType::Reflect) {
OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, 0}));
} else {
OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, obj->unionIdx}));
}
OX_RETURN_ERROR(io->field("String", &obj->String));
OX_RETURN_ERROR(io->field("IString", &obj->IString));
OX_RETURN_ERROR(io->field("List", obj->List, 4));
OX_RETURN_ERROR(io->field("Vector", &obj->Vector));
OX_RETURN_ERROR(io->field("Vector2", &obj->Vector2));
OX_RETURN_ERROR(io->field("Map", &obj->Map));
OX_RETURN_ERROR(io->field("Struct", &obj->Struct));
OX_RETURN_ERROR(io->field("EmptyStruct", &obj->EmptyStruct));
return ox::Error(0);
}
std::map<ox::StringView, ox::Error(*)()> tests = {
{
{
"MetalClawWriter",
[] {
// This test doesn't confirm much, but it does show that the writer
// doesn't segfault
ox::Array<char, 1024> buff;
TestStruct ts;
OX_RETURN_ERROR(ox::writeMC(buff.data(), buff.size(), ts));
OX_RETURN_ERROR(ox::writeMC(ts));
return ox::Error(0);
}
},
{
"MetalClawReader",
[] {
// setup for tests
TestStruct testIn, testOut;
testIn.Bool = true;
testIn.Int = 42;
testIn.IString = "Test String 1";
testIn.String = "Test String 2";
testIn.Vector = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };
testIn.Vector2 = {};
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = true;
testIn.Struct.Int = 300;
testIn.Struct.IString = "Test String 3";
testIn.unionIdx = 1;
testIn.Union.Int = 93;
// run tests
const auto [buff, err] = ox::writeMC(testIn);
oxAssert(err, "writeMC failed");
oxAssert(ox::readMC(buff, testOut), "readMC failed");
//std::cout << testIn.Union.Int << "|" << testOut.Union.Int << "|\n";
oxAssert(testIn.Bool == testOut.Bool, "Bool value mismatch");
oxAssert(testIn.Int == testOut.Int, "Int value mismatch");
oxAssert(testIn.Int1 == testOut.Int1, "Int1 value mismatch");
oxAssert(testIn.Int2 == testOut.Int2, "Int2 value mismatch");
oxAssert(testIn.Int3 == testOut.Int3, "Int3 value mismatch");
oxAssert(testIn.Int4 == testOut.Int4, "Int4 value mismatch");
oxAssert(testIn.Int5 == testOut.Int5, "Int5 value mismatch");
oxAssert(testIn.Int6 == testOut.Int6, "Int6 value mismatch");
oxAssert(testIn.Int7 == testOut.Int7, "Int7 value mismatch");
oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch");
oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch");
oxAssert(testIn.String == testOut.String, "String value mismatch");
ox::expect(testIn.IString, testOut.IString);
oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch");
oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch");
oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch");
oxAssert(testIn.List[3] == testOut.List[3], "List[3] value mismatch");
oxAssert(testIn.Vector.size() == testOut.Vector.size(), "Vector size mismatch");
for (auto i = 0u; i < testIn.Vector.size(); ++i) {
oxAssert(testIn.Vector[i] == testOut.Vector[i], ox::sfmt("Vector[{}] value mismatch", i));
}
oxAssert(testIn.Vector2.size() == testOut.Vector2.size(), "Vector2 size mismatch");
oxAssert(testIn.Map["asdf"] == testOut.Map["asdf"], "Map[\"asdf\"] value mismatch");
oxAssert(testIn.Map["aoeu"] == testOut.Map["aoeu"], "Map[\"aoeu\"] value mismatch");
oxAssert(testIn.EmptyStruct.Bool == testOut.EmptyStruct.Bool, "EmptyStruct.Bool value mismatch");
oxAssert(testIn.EmptyStruct.Int == testOut.EmptyStruct.Int, "EmptyStruct.Int value mismatch");
oxAssert(testIn.EmptyStruct.IString == testOut.EmptyStruct.IString, "EmptyStruct.IString value mismatch");
oxAssert(testIn.Struct.Int == testOut.Struct.Int, "Struct.Int value mismatch");
oxAssert(testIn.Struct.IString == testOut.Struct.IString, "Struct.IString value mismatch");
oxAssert(testIn.Struct.Bool == testOut.Struct.Bool, "Struct.Bool value mismatch");
return ox::Error(0);
}
},
{
"encodeInteger",
[] {
using ox::MaxValue;
using ox::mc::McInt;
using ox::mc::encodeInteger;
static constexpr auto check = [](McInt val, const ox::Vector<uint8_t, 9> &expected) {
if (val.length != expected.size()) {
std::cout << "val.length: " << val.length << ", expected: " << expected.size() << '\n';
return ox::Error(1);
}
for (std::size_t i = 0; i < expected.size(); i++) {
if (expected[i] != val.data[i]) {
std::cout << "decoded: " << static_cast<uint32_t>(val.data[i]) << ", expected: " << static_cast<uint32_t>(expected[i]) << '\n';
std::cout << "decoded: " << i << ": " << static_cast<uint32_t>(val.data[i]) << '\n';
return ox::Error(1);
}
}
return ox::Error(0);
};
constexpr auto check64 = [](McInt val, auto expected) {
if (val.length != 9) {
std::cout << "val.length: " << val.length << '\n';
return ox::Error(1);
}
ox::LittleEndian<decltype(expected)> decoded = *reinterpret_cast<decltype(expected)*>(&val.data[1]);
if (expected != decoded) {
std::cout << "decoded: " << decoded << ", expected: " << expected << '\n';
return ox::Error(1);
}
return ox::Error(0);
};
// signed positive
oxAssert(check(encodeInteger(int64_t(1)), {0b000'0001'0}), "Encode 1 fail");
oxAssert(check(encodeInteger(int64_t(2)), {0b000'0010'0}), "Encode 2 fail");
oxAssert(check(encodeInteger(int64_t(3)), {0b000'0011'0}), "Encode 3 fail");
oxAssert(check(encodeInteger(int64_t(4)), {0b000'0100'0}), "Encode 4 fail");
oxAssert(check(encodeInteger(int64_t(64)), {0b00'0000'01, 0b1}), "Encode 64 fail");
oxAssert(check(encodeInteger(int64_t(128)), {0b00'0000'01, 0b10}), "Encode 128 fail");
oxAssert(check(encodeInteger(int64_t(129)), {0b00'0001'01, 0b10}), "Encode 129 fail");
oxAssert(check(encodeInteger(int64_t(130)), {0b00'0010'01, 0b10}), "Encode 130 fail");
oxAssert(check(encodeInteger(int64_t(131)), {0b00'0011'01, 0b10}), "Encode 131 fail");
// signed negative
oxAssert(check(encodeInteger( int64_t(-1)), {0b111'1111'0}), "Encode -1 fail");
oxAssert(check(encodeInteger( int64_t(-2)), {0b111'1110'0}), "Encode -2 fail");
oxAssert(check(encodeInteger( int64_t(-3)), {0b111'1101'0}), "Encode -3 fail");
oxAssert(check(encodeInteger( int64_t(-4)), {0b111'1100'0}), "Encode -4 fail");
oxAssert(check(encodeInteger( int64_t(-64)), {0b100'0000'0}), "Encode -64 fail");
oxAssert(check(encodeInteger(int64_t(-128)), {0b00'0000'01, 0b11'1111'10}), "Encode -128 fail");
oxAssert(check(encodeInteger(int64_t(-129)), {0b11'1111'01, 0b11'1111'01}), "Encode -129 fail");
oxAssert(check(encodeInteger(int64_t(-130)), {0b11'1110'01, 0b11'1111'01}), "Encode -130 fail");
oxAssert(check(encodeInteger(int64_t(-131)), {0b11'1101'01, 0b11'1111'01}), "Encode -131 fail");
// unsigned
oxAssert(check(encodeInteger(uint32_t(0xffffffff)), {0b11101111, 255, 255, 255, 0b00011111}), "Encode 0xffffffff fail");
oxAssert(check(encodeInteger(uint64_t(1)), {0b0010}), "Encode 1 fail");
oxAssert(check(encodeInteger(uint64_t(2)), {0b0100}), "Encode 2 fail");
oxAssert(check(encodeInteger(uint64_t(3)), {0b0110}), "Encode 3 fail");
oxAssert(check(encodeInteger(uint64_t(4)), {0b1000}), "Encode 4 fail");
oxAssert(check(encodeInteger(uint64_t(64)), {0b1000'000'0}), "Encode 4 fail");
oxAssert(check(encodeInteger(uint64_t(128)), {0b0001, 0b10}), "Encode 128 fail");
oxAssert(check(encodeInteger(uint64_t(129)), {0b0101, 0b10}), "Encode 129 fail");
oxAssert(check(encodeInteger(uint64_t(130)), {0b1001, 0b10}), "Encode 130 fail");
oxAssert(check(encodeInteger(uint64_t(131)), {0b1101, 0b10}), "Encode 131 fail");
// Signed check needs lambda templates to run correctly without
// code deduplication
oxAssert(check64(encodeInteger(MaxValue<int64_t>), MaxValue<int64_t>), "Encode MaxValue<int64_t> fail");
oxAssert(check64(encodeInteger(MaxValue<uint64_t>), MaxValue<uint64_t>), "Encode MaxValue<uint64_t> fail");
return ox::Error(0);
}
},
{
"decodeInteger",
[] {
using ox::MaxValue;
using ox::mc::McInt;
using ox::mc::encodeInteger;
using ox::mc::decodeInteger;
static constexpr auto check = [](auto val) {
auto result = decodeInteger<decltype(val)>(encodeInteger(val));
OX_RETURN_ERROR(result.error);
if (result.value != val) {
std::cout << "Bad value: " << result.value << ", expected: " << val << '\n';
return ox::Error(1);
}
return ox::Error(0);
};
oxAssert(check(uint32_t(14)), "Decode of 14 failed.");
oxAssert(check(int8_t(-1)), "Decode of -1 failed.");
oxAssert(check(int16_t(-1)), "Decode of -1 failed.");
oxAssert(check(int32_t(-1)), "Decode of -1 failed.");
oxAssert(check(int64_t(-1)), "Decode of -1 failed.");
oxAssert(check(int64_t(-2)), "Decode of -2 failed.");
oxAssert(check(int64_t(-127)), "Decode of -127 failed.");
oxAssert(check(int64_t(-128)), "Decode of -128 failed.");
oxAssert(check(int64_t(-129)), "Decode of -129 failed.");
oxAssert(check(int64_t(-129000)), "Decode of -129000 failed.");
oxAssert(check(int64_t(1)), "Decode of 1 failed.");
oxAssert(check(int64_t(2)), "Decode of 2 failed.");
oxAssert(check(int64_t(42)), "Decode of 42 failed.");
oxAssert(check(int64_t(130)), "Decode of 130 failed.");
oxAssert(check(int64_t(131)), "Decode of 131 failed.");
oxAssert(check(int64_t(131000)), "Decode of 131000 failed.");
oxAssert(check(uint64_t(1)), "Decode of 1 failed.");
oxAssert(check(uint64_t(2)), "Decode of 2 failed.");
oxAssert(check(uint64_t(42)), "Decode of 42 failed.");
oxAssert(check(uint64_t(130)), "Decode of 130 failed.");
oxAssert(check(uint64_t(131)), "Decode of 131 failed.");
oxAssert(check(0xffffffff), "Decode of 0xffffffff failed.");
oxAssert(check(0xffffffffffff), "Decode of 0xffffffffffff failed.");
oxAssert(check(0xffffffffffffffff), "Decode of U64 max failed.");
return ox::Error(0);
}
},
{
"MetalClawModelValue",
[] {
static constexpr size_t dataBuffLen = ox::units::MB;
ox::Buffer dataBuff(dataBuffLen);
TestStruct testIn;
testIn.Bool = true;
testIn.Int = 42;
testIn.IString = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = true;
testIn.Struct.Int = 300;
testIn.Struct.IString = "Test String 2";
testIn.unionIdx = 1;
testIn.Union.Int = 93;
oxAssert(ox::writeMC(dataBuff.data(), dataBuff.size(), testIn), "Data generation failed");
ox::TypeStore typeStore;
const auto [type, typeErr] = ox::buildTypeDef(typeStore, testIn);
oxAssert(typeErr, "Descriptor write failed");
ox::ModelObject testOut;
OX_RETURN_ERROR(testOut.setType(type));
oxAssert(ox::readMC(dataBuff, testOut), "Data read failed");
oxAssert(testOut.at("Int").unwrap()->get<int>() == testIn.Int, "testOut.Int failed");
oxAssert(testOut.at("Bool").unwrap()->get<bool>() == testIn.Bool, "testOut.Bool failed");
oxAssert(testOut.at("IString").unwrap()->get<ox::String>() == testIn.IString.c_str(), "testOut.String failed");
oxAssert(testOut.at("String").unwrap()->get<ox::String>() == testIn.String, "testOut.String failed");
auto &testOutStruct = testOut.at("Struct").unwrap()->get<ox::ModelObject>();
auto &testOutUnion = testOut.at("Union").unwrap()->get<ox::ModelUnion>();
auto &testOutList = testOut.at("List").unwrap()->get<ox::ModelValueVector>();
auto testOutStructCopy = testOut.at("Struct").unwrap()->get<ox::ModelObject>();
auto testOutUnionCopy = testOut.at("Union").unwrap()->get<ox::ModelUnion>();
auto testOutListCopy = testOut.at("List").unwrap()->get<ox::ModelValueVector>();
oxAssert(testOutStruct.typeName() == TestStructNest::TypeName, "ModelObject TypeName failed");
oxAssert(testOutStruct.typeVersion() == TestStructNest::TypeVersion, "ModelObject TypeVersion failed");
oxAssert(testOutStruct.at("Bool").unwrap()->get<bool>() == testIn.Struct.Bool, "testOut.Struct.Bool failed");
oxAssert(testOutStruct.at("IString").unwrap()->get<ox::String>() == testIn.Struct.IString.c_str(), "testOut.Struct.IString failed");
oxAssert(testOut.at("unionIdx").unwrap()->get<int>() == testIn.unionIdx, "testOut.unionIdx failed");
oxAssert(testOutUnion.unionIdx() == testIn.unionIdx, "testOut.Union idx wrong");
oxAssert(testOutUnion.at("Int").unwrap()->get<uint32_t>() == testIn.Union.Int, "testOut.Union.Int failed");
oxAssert(testOutList[0].get<uint32_t>() == testIn.List[0], "testOut.List[0] failed");
oxAssert(testOutList[1].get<uint32_t>() == testIn.List[1], "testOut.Struct.List[1] failed");
oxAssert(testOutStructCopy.at("Bool").unwrap()->get<bool>() == testIn.Struct.Bool, "testOut.Struct.Bool (copy) failed");
oxAssert(testOutStructCopy.at("IString").unwrap()->get<ox::String>() == testIn.Struct.IString.c_str(), "testOut.Struct.IString (copy) failed");
oxAssert(testOutListCopy[0].get<uint32_t>() == testIn.List[0], "testOut.Struct.List[0] (copy) failed");
oxAssert(testOutListCopy[1].get<uint32_t>() == testIn.List[1], "testOut.Struct.List[1] (copy) failed");
return ox::Error(0);
}
},
{
"MetalClawDef",
[] {
//constexpr size_t descBuffLen = 1024;
//uint8_t descBuff[descBuffLen];
static constexpr size_t dataBuffLen = ox::units::MB;
char dataBuff[dataBuffLen];
TestStruct testIn, testOut;
testIn.Bool = true;
testIn.Int = 42;
testIn.IString = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = false;
testIn.Struct.Int = 300;
testIn.Struct.IString = "Test String 2";
oxAssert(ox::writeMC(dataBuff, dataBuffLen, testIn), "Data generation failed");
ox::TypeStore typeStore;
const auto [type, typeErr] = ox::buildTypeDef(typeStore, testIn);
oxAssert(typeErr, "Descriptor write failed");
ox::BufferReader br({dataBuff, dataBuffLen});
OX_RETURN_ERROR(ox::walkModel<ox::MetalClawReader>(type, br,
[](const ox::Vector<ox::FieldName>&, const ox::Vector<ox::String>&, const ox::DescriptorField &f, ox::MetalClawReader *rdr) -> ox::Error {
//std::cout << f.fieldName.c_str() << '\n';
auto fieldName = f.fieldName.c_str();
switch (f.type->primitiveType) {
case ox::PrimitiveType::UnsignedInteger:
std::cout << fieldName << ":\tuint" << f.type->length * 8 << "_t:\t";
switch (f.type->length) {
case 1: {
uint8_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 2: {
uint16_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 4: {
uint32_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 8: {
uint64_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
}
std::cout << '\n';
break;
case ox::PrimitiveType::SignedInteger:
std::cout << fieldName << ":\tint" << f.type->length * 8 << "_t:\t";
switch (f.type->length) {
case 1: {
int8_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 2: {
int16_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 4: {
int32_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
case 8: {
int64_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << i;
break;
}
}
std::cout << '\n';
break;
case ox::PrimitiveType::Bool: {
bool i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
std::cout << fieldName << ":\t" << "bool:\t\t" << (i ? "true" : "false") << '\n';
break;
}
case ox::PrimitiveType::String: {
ox::String s;
//std::cout << rdr->stringLength() << '\n';
oxAssert(rdr->field(fieldName, &s), "Walking model failed.");
oxOutf("{}:\tstring:\t\t{}\n", fieldName, s);
break;
}
case ox::PrimitiveType::Struct:
break;
case ox::PrimitiveType::Union:
break;
}
return ox::Error(0);
}
));
return ox::Error(0);
}
},
}
};
int main(int argc, const char **args) {
if (argc < 2) {
oxError("Must specify test to run");
}
auto const testName = args[1];
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(), "Test returned Error");
return 0;
}
return -1;
}
+45
View File
@@ -0,0 +1,45 @@
add_library(
OxModel
src/desctypes.cpp
src/descwrite.cpp
src/modelvalue.cpp
)
if(NOT MSVC)
target_compile_options(OxModel PRIVATE -Wconversion)
target_compile_options(OxModel PRIVATE -Wsign-conversion)
endif()
target_link_libraries(
OxModel PUBLIC
OxStd
)
if(NOT OX_BARE_METAL)
set_property(
TARGET
OxModel
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
endif()
target_link_libraries(
OxModel PUBLIC
OxStd
)
target_include_directories(
OxModel PUBLIC
include
)
install(
TARGETS OxModel
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+7
View File
@@ -0,0 +1,7 @@
<Type> : <TypeName><FieldList>
<FieldList> : <FieldList> | <FieldList><Field>
<Field> : <FieldType><TypeID><FieldName>
<TypeID> : <TypeName> | <TypeName><Type>
<TypeName> : <string>
<FieldType> : <0: single> | <1: list>
<FieldName> : <string>
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/concepts.hpp>
// oxModelFwdDecl is necessary because Apple-Clang is broken...
#define OX_MODEL_FWD_DECL(modelName) constexpr ox::Error model(auto *io, ox::CommonPtrWith<modelName> auto *o) noexcept
#define OX_MODEL_BEGIN(modelName) constexpr ox::Error model(auto *io, [[maybe_unused]] ox::CommonPtrWith<modelName> auto *o) noexcept { OX_RETURN_ERROR(io->template setTypeInfo<modelName>());
#define OX_MODEL_END() return {}; }
#define OX_MODEL_FIELD(fieldName) OX_RETURN_ERROR(io->field(#fieldName, &o->fieldName));
#define OX_MODEL_FIELD_RENAME(objFieldName, serFieldName) OX_RETURN_ERROR(io->field(#serFieldName, &o->objFieldName));
#define OX_MODEL_FRIEND(modelName) friend constexpr ox::Error model(auto *io, ox::CommonPtrWith<modelName> auto *o) noexcept
@@ -0,0 +1,7 @@
<Type> : <TypeName><FieldList>
<FieldList> : <FieldList> | <FieldList><Field>
<Field> : <FieldType><TypeID><FieldName>
<TypeID> : <TypeName> | <TypeName><Type>
<TypeName> : <string>
<FieldType> : <0: single> | <1: list>
<FieldName> : <string>
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "typestore.hpp"
#include "desctypes.hpp"
namespace ox {
template<typename ReaderBase>
class TypeDescReader: public ReaderBase {
private:
TypeStore m_typeStore;
public:
constexpr TypeDescReader(const uint8_t *buff, std::size_t buffLen) noexcept;
[[nodiscard]]
constexpr const auto &typeStore() const noexcept;
};
template<typename ReaderBase>
constexpr TypeDescReader<ReaderBase>::TypeDescReader(const uint8_t *buff, std::size_t buffLen) noexcept:
ReaderBase(buff, buffLen) {
}
template<typename ReaderBase>
constexpr const auto &TypeDescReader<ReaderBase>::typeStore() const noexcept {
return m_typeStore;
}
template<typename ReaderBase, typename T>
constexpr int readMCDef(const uint8_t *buff, std::size_t buffLen, T *val) noexcept {
TypeDescReader<ReaderBase> reader(buff, buffLen);
return model(&reader, val);
}
}
+251
View File
@@ -0,0 +1,251 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/vector.hpp>
#include "optype.hpp"
#include "types.hpp"
#include "typenamecatcher.hpp"
namespace ox {
using FieldName = String;
using TypeParamPack = Vector<String>;
template<typename T>
constexpr auto buildTypeId() noexcept {
constexpr auto name = requireModelTypeName<T>();
constexpr auto version = requireModelTypeVersion<T>();
return ox::sfmt("{};{}", name, version);
}
static constexpr auto buildTypeId(
StringViewCR name, int version,
const TypeParamPack &typeParams = {}) noexcept {
String tp;
if (!typeParams.empty()) {
tp = "#";
for (const auto &p : typeParams) {
tp += p + ",";
}
tp.resize(tp.size() - 1);
tp += "#";
}
return ox::sfmt("{}{};{}", name, tp, version);
}
enum class PrimitiveType: uint8_t {
UnsignedInteger = 0,
SignedInteger = 1,
Bool = 2,
// Float = 3, reserved, but not implemented
String = 4,
Struct = 5,
Union = 6,
};
struct Subscript {
static constexpr auto TypeName = "net.drinkingtea.ox.Subscript";
static constexpr auto TypeVersion = 1;
enum class SubscriptType: uint32_t {
None = 0,
Ptr = 1,
PtrArray = 2,
InlineArray = 3,
Vector = 4,
};
SubscriptType subscriptType = SubscriptType::None;
uint64_t length = 0;
uint64_t smallSzLen = 0;
};
template<typename T>
constexpr Error model(T *io, CommonPtrWith<Subscript> auto *type) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<Subscript>());
if constexpr(T::opType() == OpType::Reflect) {
uint32_t st = 0;
OX_RETURN_ERROR(io->field("subscriptType", &st));
} else if constexpr(T::opType() == OpType::Write) {
auto pt = type ? static_cast<uint8_t>(type->subscriptType) : 0;
OX_RETURN_ERROR(io->field("subscriptType", &pt));
} else {
auto pt = type ? static_cast<uint32_t>(type->subscriptType) : 0;
OX_RETURN_ERROR(io->field("subscriptType", &pt));
type->subscriptType = static_cast<Subscript::SubscriptType>(pt);
}
OX_RETURN_ERROR(io->field("length", &type->length));
OX_RETURN_ERROR(io->field("smallSzLen", &type->smallSzLen));
return {};
}
using SubscriptStack = Vector<Subscript, 3>;
struct DescriptorField {
// order of fields matters
static constexpr auto TypeName = "net.drinkingtea.ox.DescriptorField";
static constexpr auto TypeVersion = 3;
// do not serialize type
const struct DescriptorType *type = nullptr;
String fieldName;
int subscriptLevels = 0;
SubscriptStack subscriptStack;
String typeId; // gives reference to type for lookup if type is null
constexpr DescriptorField() noexcept = default;
constexpr DescriptorField(const DescriptorType *pType, String pFieldName,
int pSubscriptLevels,
SubscriptStack pSubscriptType,
String pTypeId) noexcept:
type(pType),
fieldName(std::move(pFieldName)),
subscriptLevels(pSubscriptLevels),
subscriptStack(std::move(pSubscriptType)),
typeId(std::move(pTypeId)) {
oxAssert(subscriptLevels <= static_cast<int>(subscriptStack.size()), "Subscript level mismatch");
}
constexpr DescriptorField(const DescriptorField &other) noexcept:
type(other.type),
fieldName(other.fieldName),
subscriptLevels(other.subscriptLevels),
subscriptStack(other.subscriptStack),
typeId(other.typeId) {
}
constexpr DescriptorField(DescriptorField &&other) noexcept:
type(other.type),
fieldName(std::move(other.fieldName)),
subscriptLevels(other.subscriptLevels),
subscriptStack(std::move(other.subscriptStack)),
typeId(std::move(other.typeId)) {
other.type = {};
other.subscriptLevels = {};
other.subscriptStack = {};
}
constexpr ~DescriptorField() noexcept = default;
constexpr DescriptorField &operator=(const DescriptorField &other) noexcept = delete;
constexpr DescriptorField &operator=(DescriptorField &&other) noexcept = delete;
};
using FieldList = Vector<DescriptorField>;
struct DescriptorType {
static constexpr auto TypeName = "net.drinkingtea.ox.TypeDescriptor";
static constexpr auto TypeVersion = 1;
String typeName;
int typeVersion = 0;
PrimitiveType primitiveType = PrimitiveType::UnsignedInteger;
TypeParamPack typeParams;
// fieldList only applies to structs and unions
FieldList fieldList;
// - number of bytes for integer and float types
// - number of fields for structs and lists
int64_t length = 0;
bool preloadable = false;
constexpr DescriptorType() noexcept = default;
constexpr explicit DescriptorType(String tn, int typeVersion, PrimitiveType t, TypeParamPack pTypeParams) noexcept:
typeName(std::move(tn)),
typeVersion(typeVersion),
primitiveType(t),
typeParams(std::move(pTypeParams)) {
}
};
[[nodiscard]]
constexpr auto buildTypeId(const DescriptorType &t) noexcept {
return buildTypeId(t.typeName, t.typeVersion, t.typeParams);
}
template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorType> auto *type) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<DescriptorType>());
OX_RETURN_ERROR(io->field("typeName", &type->typeName));
OX_RETURN_ERROR(io->field("typeVersion", &type->typeVersion));
if constexpr(T::opType() == OpType::Reflect) {
uint8_t pt = 0;
OX_RETURN_ERROR(io->field("primitiveType", &pt));
} else if constexpr(T::opType() == OpType::Write) {
auto pt = type ? static_cast<uint8_t>(type->primitiveType) : 0;
OX_RETURN_ERROR(io->field("primitiveType", &pt));
} else {
auto pt = type ? static_cast<uint8_t>(type->primitiveType) : 0;
OX_RETURN_ERROR(io->field("primitiveType", &pt));
type->primitiveType = static_cast<PrimitiveType>(pt);
}
OX_RETURN_ERROR(io->field("typeParams", &type->typeParams));
OX_RETURN_ERROR(io->field("fieldList", &type->fieldList));
OX_RETURN_ERROR(io->field("length", &type->length));
OX_RETURN_ERROR(io->field("preloadable", &type->preloadable));
return {};
}
template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<DescriptorField>());
OX_RETURN_ERROR(io->field("typeId", &field->typeId));
OX_RETURN_ERROR(io->field("fieldName", &field->fieldName));
OX_RETURN_ERROR(io->field("subscriptLevels", &field->subscriptLevels));
OX_RETURN_ERROR(io->field("subscriptStack", &field->subscriptStack));
// defaultValue is unused now, but leave placeholder for backwards compatibility
int defaultValue = 0;
OX_RETURN_ERROR(io->field("defaultValue", &defaultValue));
return {};
}
template<typename ReaderBase>
class TypeDescReader;
#if 0 // unused right now
template<typename T>
constexpr Error model(TypeDescReader<T> *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
io->template setTypeInfo<DescriptorField>();
oxReturnError(io->field("typeName", &field->typeName));
oxReturnError(io->field("typeVersion", &field->typeVersion));
auto &typeStore = io->typeStore();
auto &[type, err] = typeStore->at(field->typeName).value;
oxReturnError(err);
field->type = type.get();
oxReturnError(io->field("fieldName", &field->fieldName));
oxReturnError(io->field("subscriptLevels", &field->subscriptLevels));
if constexpr(T::opType() != ox::OpType::Reflect) {
auto subscriptStack = static_cast<uint32_t>(field->subscriptStack);
oxReturnError(io->field("subscriptStack", &subscriptStack));
field->subscriptStack = static_cast<DescriptorField::SubscriptType>(subscriptStack);
} else {
oxReturnError(io->field("subscriptStack", &field->subscriptStack));
}
// defaultValue is unused now, but placeholder for backwards compatibility
int defaultValue = 0;
oxReturnError(io->field("defaultValue", &defaultValue));
return {};
}
#endif
}
+404
View File
@@ -0,0 +1,404 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/byteswap.hpp>
#include <ox/std/istring.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>
#include "desctypes.hpp"
#include "fieldcounter.hpp"
#include "metadata.hpp"
#include "modelhandleradaptor.hpp"
#include "optype.hpp"
#include "typenamecatcher.hpp"
#include "types.hpp"
#include "typestore.hpp"
namespace ox {
namespace detail {
template<typename T>
constexpr int indirectionLevels_v = 0;
template<typename T>
constexpr int indirectionLevels_v<T*> = 1 + indirectionLevels_v<T>;
template<typename T, std::size_t sz>
constexpr int indirectionLevels_v<T[sz]> = 1 + indirectionLevels_v<T>;
template<typename T, std::size_t SmallVecSz>
constexpr int indirectionLevels_v<::ox::Vector<T, SmallVecSz>> = 1 + indirectionLevels_v<T>;
template<typename T>
constexpr auto buildSubscriptStack(const T*, SubscriptStack*) noexcept {
}
template<typename T>
constexpr auto buildSubscriptStack(const T**, SubscriptStack *s) noexcept {
s->push_back({.subscriptType = Subscript::SubscriptType::Ptr});
}
template<typename T>
constexpr auto buildSubscriptStack(const UPtr<T>*, SubscriptStack *s) noexcept {
s->push_back({.subscriptType = Subscript::SubscriptType::Ptr});
}
template<typename T, std::size_t sz>
constexpr auto buildSubscriptStack(const Array<T, sz>*, SubscriptStack *s) noexcept {
s->push_back({.subscriptType = Subscript::SubscriptType::InlineArray, .length = sz});
buildSubscriptStack(static_cast<T*>(nullptr), s);
}
template<typename T, std::size_t SmallVecSz>
constexpr auto buildSubscriptStack(const Vector<T, SmallVecSz>*, SubscriptStack *s) noexcept {
s->push_back({
.subscriptType = Subscript::SubscriptType::Vector,
.smallSzLen = SmallVecSz,
});
buildSubscriptStack(static_cast<T*>(nullptr), s);
}
template<typename T>
constexpr auto buildSubscriptStack(const T*) {
SubscriptStack s;
buildSubscriptStack(static_cast<const T*>(nullptr), &s);
return s;
}
}
class TypeDescWriter {
private:
TypeStore *m_typeStore = nullptr;
DescriptorType *m_type = nullptr;
public:
explicit constexpr TypeDescWriter(TypeStore *typeStore = nullptr) noexcept;
constexpr ~TypeDescWriter() noexcept = default;
template<typename T = std::nullptr_t>
constexpr ox::Error setTypeInfo(StringViewCR name = T::TypeName,
int version = T::TypeVersion,
const TypeParamPack &typeParams = {},
std::size_t fields = ModelFieldCount_v<T>) noexcept;
template<typename T>
constexpr Error field(
StringViewCR name,
T const*val,
std::size_t valLen,
SubscriptStack const&subscriptStack) noexcept;
template<typename T>
constexpr Error field(StringViewCR name, T const*val, std::size_t valLen) noexcept;
template<typename T, bool force>
constexpr Error field(StringViewCR name, UnionView<T, force> val) noexcept;
template<typename T>
constexpr Error field(StringViewCR name, const T *val) noexcept;
template<typename ...Args>
constexpr Error fieldCString(StringViewCR name, Args&&...) noexcept;
[[nodiscard]]
constexpr DescriptorType *definition() noexcept {
return m_type;
}
static constexpr auto opType() noexcept {
return OpType::Reflect;
}
private:
[[nodiscard]]
constexpr const DescriptorType *type(const int8_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const int16_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const int32_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const int64_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const uint8_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const uint16_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const uint32_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const uint64_t *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const bool *val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *type(const char *val) const noexcept;
template<std::size_t SmallStrSz>
[[nodiscard]]
constexpr const DescriptorType *type(const BasicString<SmallStrSz>*) const noexcept {
constexpr auto PT = PrimitiveType::String;
return getType(types::BasicString, 1, PT, 0, {sfmt("{}", SmallStrSz)});
}
template<std::size_t sz>
[[nodiscard]]
constexpr const DescriptorType *type(const IString<sz> *val) const noexcept;
template<typename T>
[[nodiscard]]
constexpr const DescriptorType *type(const T *val) const noexcept;
template<typename T>
[[nodiscard]]
constexpr const DescriptorType *type(const HashMap<String, T> *val) const noexcept;
template<typename U>
[[nodiscard]]
constexpr const DescriptorType *type(UnionView<U> val) const noexcept;
[[nodiscard]]
constexpr const DescriptorType *getType(StringViewCR tn, int typeVersion, PrimitiveType t, int b,
const TypeParamPack &typeParams = {}) const noexcept;
};
constexpr TypeDescWriter::TypeDescWriter(TypeStore *typeStore) noexcept: m_typeStore(typeStore) {}
template<typename T>
constexpr ox::Error TypeDescWriter::setTypeInfo(
StringViewCR typeName, int typeVersion,
const TypeParamPack &typeParams, std::size_t) noexcept {
PrimitiveType pt;
if constexpr(is_union_v<T>) {
pt = PrimitiveType::Union;
} else if constexpr(isBasicString_v<T> || isIString_v<T>) {
pt = PrimitiveType::String;
} else {
pt = PrimitiveType::Struct;
}
m_type = m_typeStore->getInit(typeName, typeVersion, pt, typeParams);
m_type->preloadable = preloadable<T>::value;
return {};
}
// array handler
template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t, SubscriptStack const&subscriptStack) noexcept {
if (m_type) {
constexpr typename remove_pointer<T>::type *p = nullptr;
const auto t = type(p);
oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
m_type->fieldList.emplace_back(t, String(name), detail::indirectionLevels_v<T> + 1, subscriptStack, buildTypeId(*t));
return {};
}
return ox::Error(1);
}
// array handler
template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t) noexcept {
if (m_type) {
constexpr typename remove_pointer<T>::type *p = nullptr;
const auto t = type(p);
oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
auto const lvls = detail::indirectionLevels_v<T> + 1;
SubscriptStack subscriptStack{lvls};
m_type->fieldList.emplace_back(t, String(name), lvls, subscriptStack, buildTypeId(*t));
return {};
}
return ox::Error(1);
}
template<typename T, bool force>
constexpr Error TypeDescWriter::field(StringViewCR name, UnionView<T, force> val) noexcept {
if (m_type) {
const auto t = type(val);
oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, ox::String(t->typeName));
return {};
}
return ox::Error(1);
}
template<typename T>
constexpr Error TypeDescWriter::field(StringViewCR name, const T *val) noexcept {
if (m_type) {
if constexpr(isVector_v<T> || isArray_v<T>) {
typename T::value_type *data = nullptr;
return field(name, data, 0, detail::buildSubscriptStack(val));
} else if constexpr(isSmartPtr_v<T>) {
typename T::value_type *data = nullptr;
return field(name, data, 0, detail::buildSubscriptStack(val));
} else if constexpr(is_pointer_v<T>) {
return field(name, val, 0, detail::buildSubscriptStack(val));
} else {
const auto t = type(val);
oxAssert(t != nullptr, "field(const char *name, T *val): Type not found or generated");
m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, buildTypeId(*t));
return {};
}
}
return ox::Error(1);
}
template<typename ...Args>
constexpr Error TypeDescWriter::fieldCString(StringViewCR name, Args&&...) noexcept {
constexpr auto s = "";
const auto t = type(s);
m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, ox::String(t->typeName));
return {};
}
template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const T *val) const noexcept {
if constexpr(isVector_v<T>) {
return type(static_cast<typename T::value_type*>(nullptr));
} else {
auto [t, err] = m_typeStore->template get<T>();
if (!err) {
return t;
} else {
TypeDescWriter dw(m_typeStore);
const auto reflectErr = model(&dw, val);
oxLogError(reflectErr);
oxAssert(reflectErr, "field(const char *name, T val): Type info could not be generated");
return dw.m_type;
}
}
}
template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const HashMap<String, T>*) const noexcept {
return type(static_cast<T*>(nullptr));
}
template<typename U>
constexpr const DescriptorType *TypeDescWriter::type(UnionView<U> val) const noexcept {
const auto t = type(val.get());
oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
return t;
}
constexpr const DescriptorType *TypeDescWriter::type(const int8_t*) const noexcept {
constexpr auto PT = PrimitiveType::SignedInteger;
constexpr auto Bytes = 1;
return getType(types::Int8, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const int16_t*) const noexcept {
constexpr auto PT = PrimitiveType::SignedInteger;
constexpr auto Bytes = 2;
return getType(types::Int16, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const int32_t*) const noexcept {
constexpr auto PT = PrimitiveType::SignedInteger;
constexpr auto Bytes = 4;
return getType(types::Int32, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const int64_t*) const noexcept {
constexpr auto PT = PrimitiveType::SignedInteger;
constexpr auto Bytes = 8;
return getType(types::Int64, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const uint8_t*) const noexcept {
constexpr auto PT = PrimitiveType::UnsignedInteger;
constexpr auto Bytes = 1;
return getType(types::Uint8, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const uint16_t*) const noexcept {
constexpr auto PT = PrimitiveType::UnsignedInteger;
constexpr auto Bytes = 2;
return getType(types::Uint16, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const uint32_t*) const noexcept {
constexpr auto PT = PrimitiveType::UnsignedInteger;
constexpr auto Bytes = 4;
return getType(types::Uint32, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const uint64_t*) const noexcept {
constexpr auto PT = PrimitiveType::UnsignedInteger;
constexpr auto Bytes = 8;
return getType(types::Uint64, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const bool*) const noexcept {
constexpr auto PT = PrimitiveType::Bool;
constexpr auto Bytes = 0;
return getType(types::Bool, 0, PT, Bytes);
}
constexpr const DescriptorType *TypeDescWriter::type(const char*) const noexcept {
constexpr auto PT = PrimitiveType::String;
return getType(types::String, 0, PT, 0);
}
template<std::size_t sz>
constexpr const DescriptorType *TypeDescWriter::type(const IString<sz>*) const noexcept {
constexpr auto PT = PrimitiveType::String;
return getType(types::IString, 0, PT, 0);
}
constexpr const DescriptorType *TypeDescWriter::getType(StringViewCR tn, int typeVersion, PrimitiveType pt, int b,
const TypeParamPack &typeParams) const noexcept {
auto t = m_typeStore->get(tn, typeVersion, typeParams);
if (!t.error) {
auto type = t.value;
oxAssert(type != nullptr, "TypeDescWriter::getType returning null DescriptorType");
return type;
} else {
auto dt = ox::make_unique<DescriptorType>(String(tn), typeVersion, pt, typeParams);
dt->length = b;
const auto out = dt.get();
const auto typeId = buildTypeId(tn, typeVersion, typeParams);
m_typeStore->set(typeId, std::move(dt));
return out;
}
}
template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore &typeStore) noexcept {
TypeDescWriter writer(&typeStore);
ModelHandlerInterface<TypeDescWriter, ox::OpType::Reflect> handler(writer);
if (std::is_constant_evaluated()) {
std::allocator<T> a;
T *t = a.allocate(1);
OX_RETURN_ERROR(model(&handler, t));
a.deallocate(t, 1);
} else {
auto t = ox_malloca(sizeof(T), T);
OX_RETURN_ERROR(model(&handler, t.get()));
}
return writer.definition();
}
template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore &typeStore, T &val) noexcept {
TypeDescWriter writer(&typeStore);
ModelHandlerInterface<TypeDescWriter, ox::OpType::Reflect> handler(writer);
OX_RETURN_ERROR(model(&handler, &val));
return writer.definition();
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/assert.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/error.hpp>
#include "optype.hpp"
namespace ox {
namespace detail {
template<typename T>
class FieldCounter {
public:
std::size_t fields = 0;
template<typename U = std::nullptr_t>
constexpr ox::Error setTypeInfo(StringViewCR = "", int = 0, const Vector<String>& = {}, std::size_t = 0) {
return {};
}
template<typename U>
constexpr ox::Error field(StringViewCR, U) noexcept {
++fields;
return {};
}
template<typename U>
constexpr ox::Error field(StringViewCR, U, std::size_t) noexcept {
++fields;
return {};
}
template<typename U, typename Handler>
constexpr Error field(StringViewCR, Handler) {
++fields;
return {};
}
template<typename ...Args>
constexpr Error fieldCString(Args&&...) noexcept {
++fields;
return {};
}
static constexpr auto opType() noexcept {
return OpType::Reflect;
}
};
}
namespace detail {
template<typename T>
[[nodiscard]]
consteval auto modelFieldCount() noexcept {
auto a = std::allocator<T>();
auto t = a.allocate(1);
detail::FieldCounter<T> c;
const auto err = model(&c, t);
oxAssert(err, "Count failed");
a.deallocate(t, 1);
return c.fields;
}
}
template<typename T>
constexpr auto ModelFieldCount_v = detail::modelFieldCount<T>();
}
+66
View File
@@ -0,0 +1,66 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/byteswap.hpp>
#include <ox/std/istring.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>
#include "desctypes.hpp"
#include "fieldcounter.hpp"
#include "optype.hpp"
#include "types.hpp"
namespace ox {
namespace detail {
template<bool>
struct BoolWrapper {
};
template<int>
struct IntWrapper {
};
}
template<typename T, typename = detail::BoolWrapper<true>>
struct preloadable: false_type {
};
template<typename T>
struct preloadable<T, detail::BoolWrapper<T::Preloadable>> {
static constexpr bool value = T::Preloadable;
};
// cannot be done until C++20
//struct PseudoString {
// constexpr PseudoString(const char* = "") noexcept {}
//};
//
//template<PseudoString>
//struct StringWrapper {
//};
//
//template<typename T, typename = StringWrapper<"">>
//struct ModelTypeName {
// static constexpr const char *value = "";
//};
//
//template<typename T>
//struct ModelTypeName<T, detail::StringWrapper<T::TypeName>> {
// static constexpr const char *value = T::TypeName;
//};
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "def.hpp"
#include "descread.hpp"
#include "desctypes.hpp"
#include "descwrite.hpp"
#include "fieldcounter.hpp"
#include "modelhandleradaptor.hpp"
#include "modelops.hpp"
#include "modelvalue.hpp"
#include "typenamecatcher.hpp"
#include "types.hpp"
#include "typestore.hpp"
#include "walk.hpp"
@@ -0,0 +1,243 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/utility.hpp>
#include "modelvalue.hpp"
namespace ox {
template<typename Handler, OpType opType_v = Handler::opType()>
class ModelHandlerInterface {
private:
Handler &m_handler;
public:
constexpr explicit ModelHandlerInterface(Handler &handler) noexcept: m_handler(handler) {
}
template<typename T = std::nullptr_t>
constexpr ox::Error setTypeInfo(
CString name = T::TypeName,
int version = T::TypeVersion,
Vector<String> const &typeParams = {}) noexcept {
return m_handler.template setTypeInfo<T>(name, version, typeParams, ModelFieldCount_v<T>);
}
template<typename T = std::nullptr_t>
constexpr ox::Error setTypeInfo(
CString name,
int version,
Vector<String> const &typeParams,
size_t fields) noexcept {
return m_handler.template setTypeInfo<T>(name, version, typeParams, fields);
}
template<size_t len>
constexpr Error fieldCString(CString name, char val[len]) noexcept {
return m_handler.fieldCString(name, &val[0], len);
}
template<size_t len>
constexpr Error fieldCString(CString name, char const val[len]) noexcept requires(opType_v != OpType::Read) {
if constexpr(opType_v != OpType::Read) {
return m_handler.fieldCString(name, &val[0], len);
} else {
return {};
}
}
constexpr Error fieldCString(CString name, char **val) noexcept {
return m_handler.fieldCString(name, val);
}
constexpr Error fieldCString(CString name, char const *const*val) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug
if constexpr(opType_v != OpType::Read) {
return m_handler.fieldCString(name, val);
} else {
return {};
}
}
constexpr Error fieldCString(CString name, char const **val) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug
if constexpr(opType_v != OpType::Read) {
return m_handler.fieldCString(name, val);
} else {
return {};
}
}
constexpr Error fieldCString(CString name, char **val, size_t buffLen) noexcept {
return m_handler.fieldCString(name, val, buffLen);
}
constexpr Error fieldCString(CString name, char const **val, size_t buffLen) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug
if constexpr(opType_v != OpType::Read) {
return m_handler.fieldCString(name, val, buffLen);
} else {
return {};
}
}
constexpr Error fieldCString(CString name, char *val, size_t buffLen) noexcept {
return m_handler.fieldCString(name, val, buffLen);
}
constexpr Error fieldModelValue(char const *name, CommonPtrWith<ModelValue> auto *v) noexcept {
switch (v->type()) {
case ModelValue::Type::Undefined:
break;
case ModelValue::Type::Bool:
return m_handler.field(name, &v->template get<bool>());
case ModelValue::Type::UnsignedInteger8:
return m_handler.field(name, &v->template get<uint8_t>());
case ModelValue::Type::UnsignedInteger16:
return m_handler.field(name, &v->template get<uint16_t>());
case ModelValue::Type::UnsignedInteger32:
return m_handler.field(name, &v->template get<uint32_t>());
case ModelValue::Type::UnsignedInteger64:
return m_handler.field(name, &v->template get<uint64_t>());
case ModelValue::Type::SignedInteger8:
return m_handler.field(name, &v->template get<int8_t>());
case ModelValue::Type::SignedInteger16:
return m_handler.field(name, &v->template get<int16_t>());
case ModelValue::Type::SignedInteger32:
return m_handler.field(name, &v->template get<int32_t>());
case ModelValue::Type::SignedInteger64:
return m_handler.field(name, &v->template get<int64_t>());
case ModelValue::Type::String:
return m_handler.field(name, &v->template get<String>());
case ModelValue::Type::Object:
return m_handler.field(name, &v->template get<ModelObject>());
case ModelValue::Type::Union:
{
auto &u = v->template get<ModelUnion>();
if constexpr(opType_v == OpType::Read) {
u.setActiveField(whichFieldPresent(m_handler, name, u));
return m_handler.field(name, UnionView<ModelUnion, true>(&u, u.unionIdx()));
} else {
return m_handler.field(name, UnionView<ModelUnion const, true>(&u, u.unionIdx()));
}
}
case ModelValue::Type::Vector:
return m_handler.field(name, &v->template get<ModelValueVector>());
case ModelValue::Type::InlineArray:
return m_handler.field(name, &v->template get<ModelValueArray>());
}
oxErrf("invalid type: {}: {}\n", name, static_cast<int>(v->type()));
ox::panic("invalid type");
return ox::Error(1, "invalid type");
}
// array handler, with callback to allow handling individual elements
template<typename T, typename Callback>
constexpr Error field(CString name, Callback cb) noexcept {
return m_handler.template field<T, Callback>(name, cb);
}
template<typename T>
constexpr Error field(CString name, const T *v) noexcept {
if constexpr(ox::is_same_v<T, ModelValue>) {
return fieldModelValue(name, v);
} else {
return m_handler.field(name, v);
}
}
template<typename T>
constexpr Error field(CString name, T *v) noexcept {
if constexpr(ox::is_same_v<T, ModelValue>) {
return fieldModelValue(name, v);
} else {
return m_handler.field(name, v);
}
}
template<typename U, bool force = false>
constexpr Error field(CString name, UnionView<U, force> val) noexcept {
return m_handler.field(name, val);
}
constexpr Error field(CString name, auto *val, size_t len) noexcept {
return m_handler.field(name, val, len);
}
/**
* Reads an array length from the current location in the buffer.
* @param name
* @param pass indicates that the parsing should iterate past the array length
*/
[[nodiscard]]
constexpr auto arrayLength(CString name, bool pass = true) noexcept {
return m_handler.arrayLength(name, pass);
}
/**
* Reads an string length from the current location in the buffer.
*/
[[nodiscard]]
constexpr auto stringLength(CString name) noexcept {
return m_handler.stringLength(name);
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return Handler::opType();
}
[[nodiscard]]
constexpr auto handler() noexcept {
return m_handler;
}
private:
template<typename H>
static constexpr int whichFieldPresent(H &h, CString name, ModelUnion const &u) noexcept
requires(H::opType() == OpType::Read) {
return h.whichFieldPresent(name, u);
}
template<typename H>
static constexpr int whichFieldPresent(H&, CString, ModelUnion const&) noexcept
requires(H::opType() != OpType::Read) {
return 0;
}
};
template<typename Handler, OpType opType_v = Handler::opType()>
class ModelHandlerBase {
private:
ModelHandlerInterface<Handler, opType_v> m_interface{*static_cast<Handler*>(this)};
public:
[[nodiscard]]
constexpr auto interface() noexcept {
return &m_interface;
}
[[nodiscard]]
static constexpr OpType opType() noexcept {
return opType_v;
}
};
constexpr ox::Error resizeVector(auto &vec, size_t sz) {
if constexpr(ox::is_same_v<decltype(vec.resize(0)), ox::Error>) {
return vec.resize(sz);
} else {
vec.resize(sz);
return {};
}
}
}
+303
View File
@@ -0,0 +1,303 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/types.hpp>
#include <ox/std/utility.hpp>
#include "fieldcounter.hpp"
#include "modelvalue.hpp"
#include "optype.hpp"
#include "types.hpp"
namespace ox {
namespace detail {
class Wrap {
public:
virtual ~Wrap() = default;
};
template<typename T>
class WrapT: public Wrap {
private:
T *m_obj = nullptr;
public:
constexpr WrapT(T *obj) noexcept: m_obj(obj) {
}
constexpr WrapT() = default;
[[nodiscard]]
constexpr auto obj() noexcept {
return m_obj;
}
};
template<std::size_t size>
class MemberList {
private:
std::size_t m_i = 0;
public:
Array<void*, size> vars;
template<typename T>
constexpr Error field(const char*, T *v) noexcept {
vars[m_i++] = static_cast<void*>(v);
return {};
}
template<typename T>
constexpr Error field(const char*, T *v, int) noexcept {
vars[m_i++] = static_cast<void*>(v);
return {};
}
template<typename U, bool force = false>
constexpr Error field(const char*, UnionView<U, force> u) noexcept {
vars[m_i++] = static_cast<void*>(u.get());
return {};
}
template<typename T>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion,
const Vector<String>& = {},
std::size_t = ModelFieldCount_v<T>) noexcept {
return {};
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Reflect;
}
};
template<std::size_t size>
class Copier {
private:
std::size_t m_i = 0;
MemberList<size> *m_dst = nullptr;
public:
constexpr explicit Copier(MemberList<size> *dst) noexcept: m_dst(dst) {
}
template<typename FT>
constexpr Error field(const char *name, const FT *v) noexcept {
if constexpr(isVector_v<FT>) {
return field(name, v->data(), v->size());
} else {
auto &src = *v;
auto &dst = *cbit_cast<FT*>(m_dst->vars[m_i]);
dst = src;
++m_i;
return {};
}
}
template<typename FT>
constexpr Error field(const char*, const FT *list, int elements) {
for (auto i = 0l; i < elements; ++i) {
auto &src = list[i];
auto &dst = cbit_cast<FT*>(m_dst->vars[m_i])[i];
dst = src;
}
++m_i;
return {};
}
template<typename U, bool force = false>
constexpr Error field(const char*, UnionView<U, force> u) {
auto &dst = *cbit_cast<U*>(m_dst->vars[m_i]);
auto &src = *u.get();
dst = src;
++m_i;
return {};
}
template<typename T = void>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion,
const Vector<String>& = {},
int = ModelFieldCount_v<T>) noexcept {
return {};
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Read;
}
};
template<std::size_t size>
class Mover {
private:
std::size_t m_i = 0;
MemberList<size> *m_dst = nullptr;
public:
constexpr explicit Mover(MemberList<size> *dst) noexcept: m_dst(dst) {
}
template<typename FT>
constexpr Error field(const char *name, FT *v) noexcept {
if constexpr(isVector_v<FT>) {
return field(name, v->data(), v->size());
} else {
auto &src = *v;
auto &dst = *cbit_cast<FT*>(m_dst->vars[m_i]);
dst = std::move(src);
src = FT{};
++m_i;
return {};
}
}
template<typename FT>
constexpr Error field(const char*, FT *list, int elements) noexcept {
for (auto i = 0l; i < elements; ++i) {
auto &src = list[i];
auto &dst = cbit_cast<FT*>(m_dst->vars[m_i])[i];
dst = std::move(src);
src = FT{};
}
++m_i;
return {};
}
template<typename U, bool force = false>
constexpr Error field(const char*, UnionView<U, force> u) noexcept {
auto &dst = *cbit_cast<U*>(m_dst->vars[m_i]);
auto &src = *u.get();
dst = std::move(src);
++m_i;
return {};
}
template<typename T = void>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion,
const Vector<String>& = {},
int = ModelFieldCount_v<T>) noexcept {
return {};
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Read;
}
};
template<std::size_t size>
class Equals {
private:
std::size_t m_i = 0;
MemberList<size> *m_other = nullptr;
public:
bool value = false;
constexpr Equals(MemberList<size> *other) noexcept: m_other(other) {
}
template<typename FT>
constexpr Error field(const char*, const FT *v) noexcept {
const auto &src = *v;
const auto &dst = std::bit_cast<FT>(*m_other->vars[m_i]);
++m_i;
if (dst == src) {
return {};
} else {
this->value = false;
return ox::Error(1);
}
}
template<typename FT>
constexpr Error field(const char*, const FT *list, int elements) noexcept {
for (auto i = 0l; i < elements; ++i) {
const auto &src = list[i];
const auto &dst = cbit_cast<FT*>(m_other->vars[m_i])[i];
if (!(dst == src)) {
this->value = false;
return ox::Error(1);
}
}
++m_i;
return {};
}
template<typename U, bool force = false>
constexpr Error field(const char*, UnionView<U, force> u) noexcept {
const auto &dst = *cbit_cast<U*>(m_other->vars[m_i]);
const auto &src = *u.get();
++m_i;
if (dst == src) {
return {};
} else {
this->value = false;
return ox::Error(1);
}
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Read;
}
};
}
template<typename T>
constexpr void moveModel(T *dst, T *src) noexcept {
constexpr auto size = ModelFieldCount_v<T>;
detail::MemberList<size> dstFields;
detail::Mover<size> mover(&dstFields);
std::ignore = model(&dstFields, dst);
std::ignore = model(&mover, src);
}
template<typename T>
constexpr void copyModel(T *dst, const T *src) noexcept {
constexpr auto size = ModelFieldCount_v<T>;
detail::MemberList<size> dstFields;
detail::Copier<size> copier(&dstFields);
std::ignore = model(&dstFields, dst);
std::ignore = model(&copier, src);
}
template<typename T>
[[nodiscard]]
constexpr bool equalsModel(T *a, T *b) noexcept {
constexpr auto size = T::Fields;
detail::MemberList<size> aFields;
detail::Equals<size> equals(&aFields);
std::ignore = model(&aFields, a);
std::ignore = model(&equals, b);
return equals.value;
}
}
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/stringview.hpp>
namespace ox {
enum class OpType {
Read = 1,
Write,
Reflect,
};
}
@@ -0,0 +1,123 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include "fieldcounter.hpp"
#include "optype.hpp"
namespace ox {
struct TypeInfoCatcher {
CString name = "";
int version = 0;
template<typename T = std::nullptr_t>
constexpr Error setTypeInfo(
CString const n = T::TypeName,
int const v = T::TypeVersion,
Vector<String> const& = {},
size_t = 0) noexcept {
this->name = n;
this->version = v;
return {};
}
template<typename T>
constexpr Error field(CString, T*, size_t) noexcept {
return {};
}
template<typename T>
constexpr Error field(CString, T const&) noexcept {
return {};
}
template<typename T>
constexpr Error fieldCString(CString, T const&) noexcept {
return {};
}
static constexpr auto opType() noexcept {
return OpType::Reflect;
}
};
template<typename T>
[[nodiscard]]
constexpr int getModelTypeVersion(T *val) noexcept {
TypeInfoCatcher nc;
std::ignore = model(&nc, val);
return nc.version;
}
template<typename T>
[[nodiscard]]
constexpr int getModelTypeVersion() noexcept {
std::allocator<T> a;
const auto t = a.allocate(1);
const auto out = getModelTypeVersion(t);
a.deallocate(t, 1);
return out;
}
template<typename T>
[[nodiscard]]
consteval int requireModelTypeVersion() noexcept {
constexpr auto version = getModelTypeVersion<T>();
static_assert(version != 0, "TypeVersion is required");
return version;
}
template<typename T, typename Str = const char*>
[[nodiscard]]
constexpr Str getModelTypeName(T *val) noexcept {
TypeInfoCatcher nc;
std::ignore = model(&nc, val);
return nc.name;
}
template<typename T, typename Str = const char*>
[[nodiscard]]
consteval Str getModelTypeName() noexcept {
std::allocator<T> a;
auto t = a.allocate(1);
auto out = getModelTypeName(t);
a.deallocate(t, 1);
return out;
}
template<typename T, typename Str = StringLiteral>
[[nodiscard]]
consteval auto requireModelTypeName() noexcept {
constexpr auto name = getModelTypeName<T, Str>();
static_assert(StringView{name}.size(), "Type lacks required TypeName");
return name;
}
template<typename T, typename Str = StringLiteral>
constexpr auto ModelTypeName_v = requireModelTypeName<T, Str>();
template<typename T, typename Str = const char*>
constexpr auto ModelTypeVersion_v = requireModelTypeVersion<T>();
template<typename T>
constexpr auto ModelTypeId_v = [] {
constexpr auto name = ModelTypeName_v<T, StringView>;
constexpr auto version = ModelTypeVersion_v<T>;
constexpr auto versionStr = ox::sfmt<IString<19>>("{}", version);
return ox::sfmt<IString<name.size() + versionStr.size() + 1>>("{};{}", name, versionStr);
}();
}
+200
View File
@@ -0,0 +1,200 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#if __has_include(<vector>)
#include <vector>
#endif
#if __has_include(<array>)
#include <array>
#endif
#if __has_include(<QVector>)
#include <QVector>
#endif
#include <ox/std/array.hpp>
#include <ox/std/istring.hpp>
#include <ox/std/string.hpp>
#include <ox/std/strops.hpp>
#include <ox/std/types.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/vector.hpp>
namespace ox {
namespace types {
constexpr StringLiteral BasicString = "net.drinkingtea.ox.BasicString";
constexpr StringLiteral IString = "net.drinkingtea.ox.IString";
constexpr StringLiteral String = "B.string";
constexpr StringLiteral Bool = "B.bool";
constexpr StringLiteral Uint8 = "B.uint8";
constexpr StringLiteral Uint16 = "B.uint16";
constexpr StringLiteral Uint32 = "B.uint32";
constexpr StringLiteral Uint64 = "B.uint64";
constexpr StringLiteral Int8 = "B.int8";
constexpr StringLiteral Int16 = "B.int16";
constexpr StringLiteral Int32 = "B.int32";
constexpr StringLiteral Int64 = "B.int64";
}
template<typename T>
consteval bool isBasicString(const T*) noexcept {
return false;
}
template<std::size_t SmallVecSize>
consteval bool isBasicString(const BasicString<SmallVecSize>*) noexcept {
return true;
}
template<typename T>
constexpr bool isBasicString_v = isBasicString(static_cast<const T*>(nullptr));
static_assert(isBasicString_v<ox::BasicString<0ul>>);
static_assert(isBasicString_v<ox::BasicString<8ul>>);
static_assert(isBasicString_v<ox::String>);
template<typename T>
consteval bool isIString(const T*) noexcept {
return false;
}
template<std::size_t SmallVecSize>
consteval bool isIString(const BasicString<SmallVecSize>*) noexcept {
return true;
}
template<typename T>
constexpr bool isIString_v = isIString(static_cast<const T*>(nullptr));
static_assert(isBasicString_v<ox::BasicString<0ul>>);
static_assert(isBasicString_v<ox::BasicString<8ul>>);
static_assert(isBasicString_v<ox::String>);
template<typename T>
consteval bool isOxVector(const T*) noexcept {
return false;
}
template<typename T, std::size_t SmallVecSize>
consteval bool isOxVector(const Vector<T, SmallVecSize>*) noexcept {
return true;
}
template<typename T>
constexpr bool isOxVector_v = isVector(static_cast<const T*>(nullptr));
template<typename T>
consteval bool isVector(const T*) noexcept {
return false;
}
template<typename T, std::size_t SmallVecSize>
consteval bool isVector(const Vector<T, SmallVecSize>*) noexcept {
return true;
}
#if __has_include(<vector>)
template<typename T>
consteval bool isVector(const std::vector<T>*) noexcept {
return true;
}
#endif
#if __has_include(<QVector>)
template<typename T>
constexpr bool isVector(const QVector<T>*) noexcept {
return true;
}
#endif
template<typename T>
constexpr bool isVector_v = isVector(static_cast<const T*>(nullptr));
static_assert(isVector_v<ox::Vector<unsigned int, 0ul>>);
template<typename T>
constexpr bool isBareArray_v = false;
template<typename T>
constexpr bool isBareArray_v<T[]> = true;
template<typename T, std::size_t sz>
constexpr bool isBareArray_v<T[sz]> = true;
template<typename T>
consteval bool isArray(T*) noexcept {
return false;
}
template<typename T>
constexpr bool isArray_v = isArray(static_cast<T*>(nullptr));
template<typename T>
constexpr bool isArray_v<T[]> = true;
template<typename T, std::size_t sz>
constexpr bool isArray_v<T[sz]> = true;
template<typename T, std::size_t sz>
constexpr bool isArray_v<Array<T, sz>> = true;
template<typename T, std::size_t sz>
consteval bool isArray(T[sz]) noexcept {
return true;
}
template<typename T, std::size_t sz>
consteval bool isArray(Array<T, sz>) noexcept {
return true;
}
template<typename T>
constexpr bool isSmartPtr_v = false;
template<typename T>
constexpr bool isSmartPtr_v<::ox::UPtr<T>> = true;
#if __has_include(<array>)
template<typename T>
constexpr bool isSmartPtr_v<::std::unique_ptr<T>> = true;
#endif
template<typename Union, bool force = false> requires(force || is_union_v<Union>)
class UnionView {
protected:
int m_idx = -1;
Union *m_union = nullptr;
public:
constexpr UnionView(Union *u, int idx) noexcept: m_idx(idx), m_union(u) {
}
[[nodiscard]]
constexpr auto idx() const noexcept {
return m_idx;
}
[[nodiscard]]
constexpr const Union *get() const noexcept {
return m_union;
}
[[nodiscard]]
constexpr Union *get() noexcept {
return m_union;
}
};
}
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/fmt.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>
#include "typenamecatcher.hpp"
#include "desctypes.hpp"
namespace ox {
class TypeStore {
private:
HashMap<String, UPtr<DescriptorType>> m_cache;
public:
constexpr TypeStore() noexcept = default;
constexpr virtual ~TypeStore() noexcept = default;
constexpr Result<const DescriptorType*> get(const auto &name, int typeVersion,
const TypeParamPack &typeParams) const noexcept {
const auto typeId = buildTypeId(name, typeVersion, typeParams);
OX_REQUIRE(out, m_cache.at(typeId));
return out->get();
}
template<typename T>
constexpr Result<const DescriptorType*> get() const noexcept {
constexpr auto typeName = ModelTypeName_v<T>;
constexpr auto typeVersion = ModelTypeVersion_v<T>;
const auto typeId = buildTypeId(typeName, typeVersion, {});
OX_REQUIRE(out, m_cache.at(typeId));
return out->get();
}
constexpr DescriptorType *getInit(StringViewCR typeName, int typeVersion, PrimitiveType pt,
const TypeParamPack &typeParams) noexcept {
const auto typeId = buildTypeId(typeName, typeVersion, typeParams);
auto &out = m_cache[typeId];
out = ox::make_unique<DescriptorType>(String(typeName), typeVersion, pt, typeParams);
return out.get();
}
constexpr Result<const DescriptorType*> getLoad(const auto &typeId) noexcept {
auto [val, err] = m_cache.at(typeId);
if (err) {
if (!std::is_constant_evaluated()) {
OX_REQUIRE_M(dt, loadDescriptor(typeId));
for (auto &f : dt->fieldList) {
if (typeId == f.typeId) {
f.type = dt.get();
} else {
OX_RETURN_ERROR(this->getLoad(f.typeId).moveTo(f.type));
}
}
auto &out = m_cache[typeId];
out = std::move(dt);
return out.get();
} else {
return ox::Error(1, "Type not available");
}
}
return val->get();
}
constexpr Result<const DescriptorType*> getLoad(const auto &typeName, auto typeVersion,
const TypeParamPack &typeParams = {}) noexcept {
return getLoad(buildTypeId(typeName, typeVersion, typeParams));
}
template<typename T>
constexpr Result<const DescriptorType*> getLoad() noexcept {
constexpr auto typeName = requireModelTypeName<T>();
constexpr auto typeVersion = requireModelTypeVersion<T>();
return getLoad(typeName, typeVersion);
}
constexpr void set(const auto &typeId, UPtr<DescriptorType> dt) noexcept {
m_cache[typeId] = std::move(dt);
}
constexpr void set(const auto &typeId, DescriptorType *dt) noexcept {
m_cache[typeId] = UPtr<DescriptorType>(dt);
}
[[nodiscard]]
constexpr auto typeList() const noexcept {
const auto &keys = m_cache.keys();
ox::Vector<DescriptorType*> descs;
for (const auto &k : keys) {
descs.emplace_back(m_cache.at(k).unwrap()->get());
}
return descs;
}
protected:
virtual Result<UPtr<DescriptorType>> loadDescriptor(ox::StringView) noexcept {
return ox::Error(1);
}
Result<UPtr<DescriptorType>> loadDescriptor(ox::StringViewCR name, int version,
const ox::TypeParamPack &typeParams) noexcept {
const auto typeId = buildTypeId(name, version, typeParams);
return loadDescriptor(typeId);
}
};
}
+161
View File
@@ -0,0 +1,161 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/error.hpp>
#include "desctypes.hpp"
namespace ox {
template<typename Reader, typename T>
class DataWalker {
template<typename ReaderBase, typename FH>
friend constexpr Error parseField(const DescriptorField &field, ReaderBase *rdr, DataWalker<ReaderBase, FH> *walker) noexcept;
private:
Vector<const DescriptorType*> m_typeStack;
T m_fieldHandler;
Vector<FieldName> m_path;
Vector<String> m_typePath;
public:
constexpr DataWalker(DescriptorType *type, T fieldHandler) noexcept;
constexpr Result<const DescriptorType*> type() const noexcept;
constexpr Error read(const DescriptorField&, Reader *rdr) noexcept;
protected:
constexpr void pushNamePath(const FieldName &fn) noexcept;
constexpr void popNamePath() noexcept;
constexpr void pushType(const DescriptorType *type) noexcept;
constexpr void popType() noexcept;
};
template<typename Reader, typename T>
constexpr DataWalker<Reader, T>::DataWalker(DescriptorType *type, T fieldHandler) noexcept: m_fieldHandler(fieldHandler) {
m_typeStack.push_back(type);
}
template<typename Reader, typename T>
constexpr Result<const DescriptorType*> DataWalker<Reader, T>::type() const noexcept {
OX_REQUIRE(out, m_typeStack.back());
return *out;
}
template<typename Reader, typename T>
constexpr Error DataWalker<Reader, T>::read(const DescriptorField &f, Reader *rdr) noexcept {
// get const ref of paths
const auto &pathCr = m_path;
const auto &typePathCr = m_typePath;
return m_fieldHandler(pathCr, typePathCr, f, rdr);
}
template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::pushNamePath(const FieldName &fn) noexcept {
m_path.emplace_back(fn);
}
template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::popNamePath() noexcept {
m_path.pop_back();
}
template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::pushType(const DescriptorType *type) noexcept {
m_typeStack.push_back(type);
}
template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::popType() noexcept {
m_typeStack.pop_back();
}
template<typename Reader, typename FH>
static constexpr Error parseField(const DescriptorField &field, Reader *rdr, DataWalker<Reader, FH> *walker) noexcept {
walker->pushNamePath(field.fieldName);
if (field.subscriptLevels) {
// add array handling
OX_REQUIRE(arrayLen, rdr->arrayLength(field.fieldName.c_str(), true));
auto child = rdr->child(field.fieldName.c_str());
OX_RETURN_ERROR(child.setTypeInfo(field.type->typeName.c_str(), field.type->typeVersion, field.type->typeParams, arrayLen));
DescriptorField f(field); // create mutable copy
--f.subscriptLevels;
String subscript;
for (std::size_t i = 0; i < arrayLen; i++) {
subscript = "[";
subscript += static_cast<uint64_t>(i);
subscript += "]";
walker->pushNamePath(subscript);
OX_RETURN_ERROR(parseField(f, &child, walker));
walker->popNamePath();
}
rdr->nextField();
} else {
switch (field.type->primitiveType) {
case PrimitiveType::UnsignedInteger:
case PrimitiveType::SignedInteger:
case PrimitiveType::Bool:
case PrimitiveType::String:
OX_RETURN_ERROR(walker->read(field, rdr));
break;
case PrimitiveType::Struct:
case PrimitiveType::Union:
if (rdr->fieldPresent(field.fieldName.c_str())) {
auto child = rdr->child(field.fieldName.c_str());
walker->pushType(field.type);
OX_RETURN_ERROR(model(&child, walker));
walker->popType();
rdr->nextField();
} else {
// skip and discard absent field
int discard;
OX_RETURN_ERROR(rdr->field(field.fieldName.c_str(), &discard));
}
break;
}
}
walker->popNamePath();
return {};
}
template<typename Reader, typename FH>
constexpr Error model(Reader *rdr, DataWalker<Reader, FH> *walker) noexcept {
OX_REQUIRE(type, walker->type());
auto typeName = type->typeName.c_str();
auto typeVersion = type->typeVersion;
auto typeParams = type->typeParams;
auto &fields = type->fieldList;
OX_RETURN_ERROR(rdr->setTypeInfo(typeName, typeVersion, typeParams, fields.size()));
for (const auto &field : fields) {
OX_RETURN_ERROR(parseField(field, rdr, walker));
}
return {};
}
template<typename Reader, typename Handler>
constexpr Error walkModel(DescriptorType *type, Reader_c auto &reader, Handler handler) noexcept {
DataWalker<Reader, Handler> walker(type, handler);
Reader rdr(reader);
return model(&rdr, &walker);
}
template<typename Reader, typename Handler>
constexpr Error walkModel(DescriptorType *type, const char *data, std::size_t dataLen, Handler handler) noexcept {
DataWalker<Reader, Handler> walker(type, handler);
Reader rdr(reinterpret_cast<const uint8_t*>(data), dataLen);
return model(&rdr, &walker);
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/model/desctypes.hpp>
namespace ox {
}
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/model/descwrite.hpp>
namespace ox {
namespace detail {
struct preloadable_type {
static constexpr auto Preloadable = true;
};
struct non_preloadable_type {
static constexpr auto Preloadable = false;
};
struct non_preloadable_type2 {
};
static_assert(preloadable<preloadable_type>::value);
static_assert(!preloadable<non_preloadable_type>::value);
static_assert(!preloadable<non_preloadable_type2>::value);
}
static_assert([] {
return detail::indirectionLevels_v<int> == 0;
}(), "indirectionLevels broken: indirectionLevels(int)");
static_assert([] {
return detail::indirectionLevels_v<int*> == 1;
}(), "indirectionLevels broken: indirectionLevels(int*)");
static_assert([] {
return detail::indirectionLevels_v<int[2]> == 1;
}(), "indirectionLevels broken: indirectionLevels(int[])");
static_assert([] {
return detail::indirectionLevels_v<int**> == 2;
}(), "indirectionLevels broken: indirectionLevels(int[])");
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/hashmap.hpp>
#include <ox/model/modelvalue.hpp>
namespace ox {
static_assert([]() -> Error {
ModelValue v;
OX_RETURN_ERROR(v.setType<int32_t>());
if (v.type() != ModelValue::Type::SignedInteger32) {
return Error(1, "type is wrong");
}
//oxReturnError(v.set<int32_t>(5));
return {};
}() == Error{});
// a dummy function to prevent linker errors in a library that has no other symbols
void modelDummyFunc() noexcept {}
}
+13
View File
@@ -0,0 +1,13 @@
add_executable(
ModelTest
tests.cpp
)
target_link_libraries(
ModelTest
OxModel
)
add_test("[ox/model] ModelValue" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ModelTest ModelValue)
add_test("[ox/model] getModelTypeName" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ModelTest getModelTypeName)
add_test("[ox/model] getModelTypeVersion" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ModelTest getModelTypeVersion)
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#undef NDEBUG
#include <map>
#include <ox/model/model.hpp>
#include <ox/std/std.hpp>
struct TestType {
static constexpr auto TypeName = "net.drinkingtea.model.test.TestType";
static constexpr auto TypeVersion = 1;
};
OX_MODEL_BEGIN(TestType)
OX_MODEL_END()
struct TestType2 {
};
template<typename Str = ox::StringView>
constexpr auto getModelTypeName(TestType2*) noexcept {
return "net.drinkingtea.model.test.TestType2";
}
constexpr auto getModelTypeVersion(TestType2*) noexcept {
return 2;
}
std::map<ox::StringView, ox::Error(*)()> tests = {
{
{
"ModelValue",
[] {
ox::ModelValue v;
OX_RETURN_ERROR(v.setType<int32_t>());
//v.m_type = ox::ModelValue::getType<int32_t>();
if (v.type() != ox::ModelValue::Type::SignedInteger32) {
return ox::Error(1, "type is wrong");
}
OX_RETURN_ERROR(v.set<int32_t>(5));
return ox::Error{};
}
},
{
"getModelTypeName",
[] {
oxAssert(ox::getModelTypeName<TestType>() == TestType::TypeName, "getModelTypeName call failed");
oxAssert(ox::getModelTypeName<TestType2>() == ox::StringView("net.drinkingtea.model.test.TestType2"), "getModelTypeName call failed");
return ox::Error{};
}
},
{
"getModelTypeVersion",
[] {
oxAssert(ox::getModelTypeVersion<TestType>() == TestType::TypeVersion, "getModelTypeVersion call failed");
oxAssert(ox::getModelTypeVersion<TestType2>() == 2, "getModelTypeVersion call failed");
return ox::Error{};
}
},
}
};
int main(int argc, const char **args) {
if (argc < 2) {
oxError("Must specify test to run");
}
auto const testName = args[1];
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(), "Test returned Error");
return 0;
}
return -1;
}
+50
View File
@@ -0,0 +1,50 @@
add_library(
OxOrganicClaw
src/read.cpp
src/write.cpp
)
if(NOT MSVC)
target_compile_options(OxOrganicClaw PRIVATE -Wsign-conversion)
target_compile_options(OxOrganicClaw PRIVATE -Wconversion)
endif()
target_link_libraries(
OxOrganicClaw PUBLIC
OxModel
jsoncpp_object
)
target_include_directories(
OxOrganicClaw PUBLIC
${JSONCPP_INCLUDE_DIR}
)
set_property(
TARGET
OxOrganicClaw
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
target_include_directories(
OxOrganicClaw PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS OxOrganicClaw
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+12
View File
@@ -0,0 +1,12 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "read.hpp"
#include "write.hpp"
+313
View File
@@ -0,0 +1,313 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/def.hpp>
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
#include <json/json.h>
OX_ALLOW_UNSAFE_BUFFERS_END
#include <ox/model/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/model/types.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/uuid.hpp>
namespace ox {
class OrganicClawReader {
private:
Json::Value m_json;
Json::ArrayIndex m_fieldIt = 0;
int const m_unionIdx = -1;
public:
OrganicClawReader() noexcept = default;
OrganicClawReader(uint8_t const *buff, size_t buffSize);
OrganicClawReader(CString json, size_t buffSize);
explicit OrganicClawReader(Json::Value json, int unionIdx = -1) noexcept;
Error field(CString key, bool *val) noexcept;
// array handler
template<typename T>
Error field(CString key, T *val, size_t len) noexcept;
template<typename T>
Error field(CString, HashMap<String, T> *val) noexcept;
template<typename T>
Error field(CString key, T *val) noexcept;
template<typename U, bool force = false>
Error field(CString key, UnionView<U, force> val) noexcept;
template<size_t L>
Error field(CString key, BasicString<L> *val) noexcept;
template<size_t L>
Error field(CString key, IString<L> *val) noexcept;
Error fieldCString(CString key, char *val, size_t buffLen) noexcept;
Error fieldCString(CString key, char **val) noexcept;
Error fieldCString(CString key, char **val, size_t buffLen) noexcept;
Error field(CString key, UUID *val) noexcept;
/**
* Reads an array length from the current location in the buffer.
* @param key
* @param pass indicates that the parsing should iterate past the array length
*/
Result<size_t> arrayLength(CString key, bool pass = true) noexcept;
/**
* Reads an string length from the current location in the buffer.
*/
[[nodiscard]]
size_t stringLength(CString key) noexcept;
template<typename T = void>
constexpr Error setTypeInfo() noexcept {
return {};
}
template<typename T = void>
constexpr Error setTypeInfo(CString) noexcept {
return {};
}
template<typename T = void>
constexpr Error setTypeInfo(CString, int, const Vector<String>& = {}) noexcept {
return {};
}
template<typename T = void>
constexpr Error setTypeInfo(CString, int, const Vector<String>& = {}, size_t = {}) noexcept {
return {};
}
/**
* Returns a OrganicClawReader to parse a child object.
*/
[[nodiscard]]
OrganicClawReader child(CString key, int unionIdx = -1) noexcept;
// compatibility stub
constexpr void nextField() noexcept {}
[[nodiscard]]
bool fieldPresent(CString key) noexcept;
[[nodiscard]]
int whichFieldPresent(CString name, const ModelUnion &u) const noexcept;
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Read;
}
private:
[[nodiscard]]
Json::Value &value(CString key) noexcept;
[[nodiscard]]
bool targetValid() const noexcept;
};
template<typename T>
Error OrganicClawReader::field(CString key, T *val) noexcept {
Error err{};
try {
if constexpr (is_integer_v<T>) {
if (targetValid()) {
auto const &jv = value(key);
auto const rightType = sizeof(T) == 8 ?
(is_signed_v<T> ? jv.isInt64() : jv.isUInt64()) :
(is_signed_v<T> ? jv.isInt() : jv.isUInt());
if (jv.empty()) {
*val = 0;
} else if (rightType) {
if constexpr(is_signed_v<T>) {
*val = static_cast<T>(jv.asInt64());
} else {
*val = static_cast<T>(jv.asUInt64());
}
} else {
err = Error{1, "Type mismatch"};
}
}
} else if constexpr (isVector_v<T>) {
auto const &srcVal = value(key);
auto const srcSize = srcVal.size();
OX_RETURN_ERROR(resizeVector(*val, srcSize));
err = field(key, val->data(), val->size());
} else if constexpr (isArray_v<T>) {
auto const &srcVal = value(key);
auto const srcSize = srcVal.size();
if (srcSize > val->size()) {
err = Error{1, "Input array is too long"};
} else {
err = field(key, val->data(), val->size());
}
} else if (targetValid()) {
auto const &jv = value(key);
if (jv.empty() || jv.isObject()) {
auto reader = child(key);
ModelHandlerInterface handler(reader);
err = model(&handler, val);
} else {
err = Error{1, "Type mismatch"};
}
}
} catch (Json::LogicError const &e) {
err = Error{1, "error reading JSON data"};
}
++m_fieldIt;
return err;
}
template<typename U, bool force>
Error OrganicClawReader::field(CString const key, UnionView<U, force> val) noexcept {
Error err{};
if (targetValid()) {
auto const &jv = value(key);
if (jv.empty() || jv.isObject()) {
auto reader = child(key, val.idx());
ModelHandlerInterface handler(reader);
err = model(&handler, val.get());
} else {
err = Error{1, "Type mismatch"};
}
}
++m_fieldIt;
return err;
}
template<size_t L>
Error OrganicClawReader::field(CString const key, BasicString<L> *val) noexcept {
Error err{};
if (targetValid()) {
auto const &jv = value(key);
if (jv.empty()) {
*val = BasicString<L>{};
} else if (jv.isString()) {
*val = jv.asString().c_str();
} else {
err = Error{1, "Type mismatch"};
}
}
++m_fieldIt;
return err;
}
template<size_t L>
Error OrganicClawReader::field(CString const key, IString<L> *val) noexcept {
Error err{};
if (targetValid()) {
auto const &jv = value(key);
if (jv.empty()) {
*val = IString<L>{};
} else if (jv.isString()) {
*val = jv.asString().c_str();
} else {
err = Error{1, "Type mismatch"};
}
}
++m_fieldIt;
return err;
}
// array handler
template<typename T>
Error OrganicClawReader::field(CString const key, T *val, size_t valLen) noexcept {
auto const &srcVal = value(key);
if (!srcVal.isNull() && !srcVal.isArray()) {
return Error{1, "Type mismatch"};
}
auto srcSize = srcVal.size();
if (srcSize > valLen) {
return Error{1};
}
OrganicClawReader r(srcVal);
ModelHandlerInterface handler{r};
for (decltype(srcSize) i = 0; i < srcSize; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
OX_RETURN_ERROR(handler.field("", &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
}
return {};
}
template<typename T>
Error OrganicClawReader::field(CString const key, HashMap<String, T> *val) noexcept {
auto const &srcVal = value(key);
if (!srcVal.isObject()) {
return Error{1, "Type mismatch"};
}
auto keys = srcVal.getMemberNames();
auto srcSize = srcVal.size();
OrganicClawReader r(srcVal);
ModelHandlerInterface handler{r};
for (decltype(srcSize) i = 0; i < srcSize; ++i) {
auto const k = keys[i].c_str();
OX_RETURN_ERROR(handler.field(k, &val->operator[](k)));
}
return {};
}
Error readOC(BufferView const buff, auto &val) noexcept {
// OrganicClawReader constructor can throw, but readOC should return its errors.
try {
Json::Value doc;
Json::CharReaderBuilder parserBuilder;
auto parser = UPtr<Json::CharReader>(parserBuilder.newCharReader());
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
if (!parser->parse(buff.data(), buff.data() + buff.size(), &doc, nullptr)) {
OX_ALLOW_UNSAFE_BUFFERS_END
return Error{1, "Could not parse JSON"};
}
OrganicClawReader reader(buff.data(), buff.size());
ModelHandlerInterface handler(reader);
return model(&handler, &val);
} catch (Error const &err) {
return err;
} catch (...) {
return Error{1, "Unknown Error"};
}
}
template<typename T>
Result<T> readOC(BufferView buff) noexcept {
Result<T> val;
OX_RETURN_ERROR(readOC(buff, val.value));
return val;
}
template<typename T>
Result<T> readOC(StringViewCR json) noexcept {
return readOC<T>(BufferView{json.data(), json.size()});
}
}
+285
View File
@@ -0,0 +1,285 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/def.hpp>
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
#include <json/json.h>
OX_ALLOW_UNSAFE_BUFFERS_END
#include <ox/model/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/string.hpp>
#include <ox/std/uuid.hpp>
namespace ox {
class OrganicClawWriter {
friend Result<Buffer> writeOC(const auto &val) noexcept;
friend Result<String> writeOCString(const auto &val) noexcept;
protected:
Json::Value m_json{Json::Value(Json::objectValue)};
Json::ArrayIndex m_fieldIt = 0;
int const m_unionIdx = -1;
public:
explicit OrganicClawWriter(int unionIdx = -1) noexcept;
explicit OrganicClawWriter(Json::Value json, int unionIdx = -1) noexcept;
Error field(CString const key, int8_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, int16_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, int32_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, int64_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, uint8_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, uint16_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, uint32_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(CString const key, uint64_t const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error field(char const*key, bool const *val) noexcept {
if (targetValid() && (*val || m_json.isArray())) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
template<typename U, bool force = true>
Error field(char const*, UnionView<U, force> val) noexcept;
template<typename T>
Error field(char const*key, HashMap<String, T> const *val) noexcept {
if (targetValid()) {
const auto &keys = val->keys();
OrganicClawWriter w;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{w};
for (size_t i = 0; i < keys.size(); ++i) {
const auto k = keys[i].c_str();
if (k) [[likely]] {
OX_REQUIRE_M(value, val->at(k));
OX_RETURN_ERROR(handler.field(k, value));
}
}
value(key) = w.m_json;
}
++m_fieldIt;
return {};
}
template<size_t L>
Error field(char const*key, IString<L> const *val) noexcept {
if (targetValid() && val->size()) {
value(key) = val->c_str();
}
++m_fieldIt;
return {};
}
template<size_t L>
Error field(char const*key, BasicString<L> const *val) noexcept {
if (targetValid() && val->size()) {
value(key) = val->c_str();
}
++m_fieldIt;
return {};
}
Error fieldCString(CString, CString const *val, int len) noexcept;
Error fieldCString(CString name, CString const*val) noexcept;
Error field(CString key, UUID const *uuid) noexcept;
template<typename T>
Error field(CString, T const *val, size_t len) noexcept;
template<typename T>
Error field(CString, T const *val) noexcept;
template<typename T>
constexpr Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion) noexcept {
return {};
}
template<typename T>
constexpr Error setTypeInfo(
const char*,
int,
Vector<String> const&,
size_t) noexcept {
return {};
}
[[nodiscard]]
static constexpr auto opType() noexcept {
return OpType::Write;
}
private:
[[nodiscard]]
constexpr bool targetValid() const noexcept {
return static_cast<int>(m_fieldIt) == m_unionIdx || m_unionIdx == -1;
}
[[nodiscard]]
Json::Value &value(CString key) noexcept;
};
template<typename T>
Error OrganicClawWriter::field(CString key, T const *val, size_t const len) noexcept {
if (targetValid() && len) {
OrganicClawWriter w((Json::Value(Json::arrayValue)));
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{w};
for (size_t i = 0; i < len; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
OX_RETURN_ERROR(handler.field({}, &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
}
value(key) = w.m_json;
}
++m_fieldIt;
return {};
}
template<typename T>
Error OrganicClawWriter::field(CString key, T const *val) noexcept {
if constexpr(is_integer_v<T>) {
if (targetValid() && (*val || m_json.isArray())) {
// the int type needs to be normalized because jsoncpp doesn't
// factor in every permutation unsigned long, etc.
if constexpr(is_signed_v<T>) {
value(key) = static_cast<Int<8 * sizeof(*val)>>(*val);
} else {
value(key) = static_cast<Uint<8 * sizeof(*val)>>(*val);
}
}
} else if constexpr(isVector_v<T> || isArray_v<T>) {
return field(key, val->data(), val->size());
} else if (val && targetValid()) {
OrganicClawWriter w;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{w};
OX_RETURN_ERROR(model(&handler, val));
if (!w.m_json.empty() || m_json.isArray()) {
value(key) = w.m_json;
}
}
++m_fieldIt;
return {};
}
template<typename U, bool force>
Error OrganicClawWriter::field(CString key, UnionView<U, force> val) noexcept {
if (targetValid()) {
OrganicClawWriter w(val.idx());
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{w};
OX_RETURN_ERROR(model(&handler, val.get()));
if (!w.m_json.isNull()) {
value(key) = w.m_json;
}
}
++m_fieldIt;
return {};
}
Result<Buffer> writeOC(auto const &val) noexcept {
OrganicClawWriter writer;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler(writer);
OX_RETURN_ERROR(model(&handler, &val));
Json::StreamWriterBuilder const jsonBuilder;
auto const str = Json::writeString(jsonBuilder, writer.m_json);
Result<Buffer> buff;
buff.value.resize(str.size() + 1);
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
memcpy(buff.value.data(), str.data(), str.size() + 1);
OX_ALLOW_UNSAFE_BUFFERS_END
return buff;
}
Result<String> writeOCString(auto const &val) noexcept {
OrganicClawWriter writer;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler(writer);
OX_RETURN_ERROR(model(&handler, &val));
Json::StreamWriterBuilder const jsonBuilder;
auto const str = Json::writeString(jsonBuilder, writer.m_json);
Result<String> buff;
buff.value.resize(str.size());
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
memcpy(buff.value.data(), str.data(), str.size() + 1);
OX_ALLOW_UNSAFE_BUFFERS_END
return buff;
}
}
+199
View File
@@ -0,0 +1,199 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/std/bit.hpp>
#include <utility>
#include <ox/oc/read.hpp>
namespace ox {
OrganicClawReader::OrganicClawReader(const uint8_t *buff, size_t const buffSize) {
auto json = reinterpret_cast<const char*>(buff);
auto jsonLen = ox::strnlen_s(json, buffSize);
Json::CharReaderBuilder parserBuilder;
auto parser = std::unique_ptr<Json::CharReader>(parserBuilder.newCharReader());
if (!parser->parse(json, json + jsonLen, &m_json, nullptr)) {
throw Exception(1, "Could not parse JSON");
}
}
OrganicClawReader::OrganicClawReader(CString const json, size_t const jsonLen) {
Json::CharReaderBuilder parserBuilder;
auto parser = std::unique_ptr<Json::CharReader>(parserBuilder.newCharReader());
if (!parser->parse(json, json + jsonLen, &m_json, nullptr)) {
throw Exception(1, "Could not parse JSON");
}
}
OrganicClawReader::OrganicClawReader(Json::Value json, int const unionIdx) noexcept:
m_json(std::move(json)),
m_unionIdx(unionIdx) {
}
Error OrganicClawReader::field(CString const key, bool *val) noexcept {
Error err{};
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = false;
} else if (jv.isBool()) {
*val = jv.asBool();
} else {
err = Error(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::fieldCString(CString const key, char *val, size_t const buffLen) noexcept {
Error err{};
CString begin = nullptr, end = nullptr;
const auto &jv = value(key);
if (targetValid()) {
if (jv.empty()) {
auto data = val;
if (data) {
data[0] = 0;
}
} else if (jv.isString()) {
jv.getString(&begin, &end);
const auto strSize = static_cast<size_t>(end - begin);
auto data = val;
if (strSize >= buffLen) {
err = Error(2, "String size exceeds capacity of destination");
} else {
memcpy(data, begin, static_cast<size_t>(strSize));
data[strSize] = 0;
}
} else {
err = Error(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::fieldCString(CString const key, char **val) noexcept {
Error err{};
CString begin = nullptr, end = nullptr;
const auto &jv = value(key);
auto &data = *val;
if (targetValid()) {
if (jv.empty()) {
if (data) {
data[0] = 0;
}
} else if (jv.isString()) {
jv.getString(&begin, &end);
const auto strSize = static_cast<size_t>(end - begin);
safeDelete(*val);
*val = new char[strSize + 1];
memcpy(data, begin, static_cast<size_t>(strSize));
data[strSize] = 0;
} else {
err = Error(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::fieldCString(CString const key, char **val, size_t const buffLen) noexcept {
Error err{};
CString begin = nullptr, end = nullptr;
const auto &jv = value(key);
if (targetValid()) {
if (jv.empty()) {
auto data = val;
if (data) {
data[0] = nullptr;
}
} else if (jv.isString()) {
jv.getString(&begin, &end);
const auto strSize = static_cast<size_t>(end - begin);
auto data = val;
if (strSize >= buffLen) {
safeDelete(*val);
*val = new char[strSize + 1];
}
memcpy(data, begin, static_cast<size_t>(strSize));
data[strSize] = nullptr;
} else {
err = Error(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(CString const key, UUID *val) noexcept {
UUIDStr str;
OX_RETURN_ERROR(field(key, &str));
return UUID::fromString(str).moveTo(*val);
}
Result<size_t> OrganicClawReader::arrayLength(CString const key, bool) noexcept {
const auto &jv = value(key);
if (jv.empty()) {
return 0;
}
if (jv.isArray()) {
return jv.size();
}
return Error(1, "Type mismatch");
}
[[nodiscard]]
size_t OrganicClawReader::stringLength(CString const key) noexcept {
CString begin = nullptr, end = nullptr;
const auto &jv = value(key);
if (jv.empty()) {
return 0;
}
if (jv.isString()) {
jv.getString(&begin, &end);
return static_cast<size_t>(end - begin);
}
return Error(1, "Type mismatch");
}
OrganicClawReader OrganicClawReader::child(CString const key, int const unionIdx) noexcept {
return OrganicClawReader(value(key), unionIdx);
}
bool OrganicClawReader::fieldPresent(CString const key) noexcept {
return !m_json[key].empty();
}
int OrganicClawReader::whichFieldPresent(CString const name, ModelUnion const &u) const noexcept {
const auto &obj = m_json[name];
if (!obj.isObject()) {
return -1;
}
const auto &keys = obj.getMemberNames();
if (keys.size() != 1) {
return -1;
}
return u.getKeyIdx(keys.front().c_str());
}
Json::Value &OrganicClawReader::value(CString const key) noexcept {
if (m_json.isArray()) {
return m_json[m_fieldIt];
} else {
return m_json[key];
}
}
bool OrganicClawReader::targetValid() const noexcept {
return static_cast<int>(m_fieldIt) == m_unionIdx || m_unionIdx == -1;
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/oc/write.hpp>
namespace ox {
OrganicClawWriter::OrganicClawWriter(int const unionIdx) noexcept: m_unionIdx(unionIdx) {
}
OrganicClawWriter::OrganicClawWriter(Json::Value json, int const unionIdx) noexcept:
m_json(std::move(json)),
m_unionIdx(unionIdx) {
}
Error OrganicClawWriter::fieldCString(const char *key, const char *const *val, int const len) noexcept {
if (targetValid() && len) {
value(key) = *val;
}
++m_fieldIt;
return {};
}
Error OrganicClawWriter::fieldCString(const char *key, const char *const *val) noexcept {
return fieldCString(key, const_cast<const char**>(val), static_cast<int>(ox::strlen(val)));
}
Error OrganicClawWriter::field(const char *key, UUID const *uuid) noexcept {
const auto uuidStr = uuid->toString();
if (targetValid() && uuidStr.size()) {
value(key) = uuidStr.c_str();
}
++m_fieldIt;
return {};
}
Json::Value &OrganicClawWriter::value(const char *key) noexcept {
if (m_json.isArray()) {
return m_json[m_fieldIt];
} else {
return m_json[key];
}
}
}
+14
View File
@@ -0,0 +1,14 @@
add_executable(
OcTest
tests.cpp
)
target_link_libraries(
OcTest
OxOrganicClaw
)
add_test("[ox/oc] Writer" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/OcTest OrganicClawWriter)
add_test("[ox/oc] Reader" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/OcTest OrganicClawReader)
add_test("[ox/oc] OrganicClawModelValue" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/OcTest OrganicClawModelValue)
add_test("[ox/oc] OrganicClawDef" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/OcTest OrganicClawDef)
+364
View File
@@ -0,0 +1,364 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#undef NDEBUG
#include <map>
#include <ox/model/model.hpp>
#include <ox/oc/oc.hpp>
#include <ox/std/std.hpp>
template<typename T>
union U {
T t;
int i;
};
U<short> u;
union TestUnion {
static constexpr auto TypeName = "TestUnion";
static constexpr auto TypeVersion = 1;
bool Bool;
uint32_t Int = 5;
char *String;
};
struct TestStructNest {
static constexpr auto TypeName = "TestStructNest";
static constexpr auto TypeVersion = 1;
bool Bool = false;
uint32_t Int = 0;
ox::IString<32> String = "";
};
struct TestStruct {
static constexpr auto TypeName = "TestStruct";
static constexpr auto TypeVersion = 1;
bool Bool = false;
int32_t Int = 0;
int32_t Int1 = 0;
int32_t Int2 = 0;
int32_t Int3 = 0;
int32_t Int4 = 0;
int32_t Int5 = 0;
int32_t Int6 = 0;
int32_t Int7 = 0;
int32_t Int8 = 0;
int unionIdx = 1;
TestUnion Union;
ox::String String{""};
uint32_t List[4] = {0, 0, 0, 0};
ox::HashMap<ox::String, int> Map;
TestStructNest EmptyStruct;
TestStructNest Struct;
TestStruct() noexcept = default;
TestStruct(TestStruct &&other) noexcept;
constexpr ~TestStruct() noexcept {
if (unionIdx == 2) {
ox::safeDelete(Union.String);
}
}
constexpr TestStruct &operator=(TestStruct&&) noexcept;
};
constexpr ox::Error model(auto *io, ox::CommonPtrWith<TestUnion> auto *obj) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<TestUnion>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->fieldCString("String", &obj->String));
return ox::Error(0);
}
constexpr ox::Error model(auto *io, ox::CommonPtrWith<TestStructNest> auto *obj) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<TestStructNest>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->field("String", &obj->String));
return ox::Error(0);
}
constexpr ox::Error model(auto *io, ox::CommonPtrWith<TestStruct> auto *obj) noexcept {
OX_RETURN_ERROR(io->template setTypeInfo<TestStruct>());
OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
OX_RETURN_ERROR(io->field("Int", &obj->Int));
OX_RETURN_ERROR(io->field("Int1", &obj->Int1));
OX_RETURN_ERROR(io->field("Int2", &obj->Int2));
OX_RETURN_ERROR(io->field("Int3", &obj->Int3));
OX_RETURN_ERROR(io->field("Int4", &obj->Int4));
OX_RETURN_ERROR(io->field("Int5", &obj->Int5));
OX_RETURN_ERROR(io->field("Int6", &obj->Int6));
OX_RETURN_ERROR(io->field("Int7", &obj->Int7));
OX_RETURN_ERROR(io->field("Int8", &obj->Int8));
OX_RETURN_ERROR(io->field("unionIdx", &obj->unionIdx));
if (io->opType() == ox::OpType::Reflect) {
OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, 0}));
} else {
OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, obj->unionIdx}));
}
OX_RETURN_ERROR(io->field("String", &obj->String));
OX_RETURN_ERROR(io->field("List", obj->List, 4));
OX_RETURN_ERROR(io->field("Map", &obj->Map));
OX_RETURN_ERROR(io->field("EmptyStruct", &obj->EmptyStruct));
OX_RETURN_ERROR(io->field("Struct", &obj->Struct));
return ox::Error(0);
}
TestStruct::TestStruct(TestStruct &&other) noexcept {
ox::moveModel(this, &other);
}
constexpr TestStruct &TestStruct::operator=(TestStruct &&other) noexcept {
ox::moveModel(this, &other);
return *this;
}
const std::map<ox::StringView, ox::Error(*)()> tests = {
{
{
"OrganicClawWriter",
[] {
// This test doesn't confirm much, but it does show that the writer
// doesn't segfault
TestStruct ts;
return ox::writeOC(ts).error;
}
},
{
"OrganicClawReader",
[] {
TestStruct testIn;
testIn.Bool = true;
testIn.Int = 42;
testIn.Union.Int = 52;
testIn.String = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Map["asdf"] = 93;
testIn.Map["aoeu"] = 94;
testIn.Struct.Bool = false;
testIn.Struct.Int = 300;
testIn.Struct.String = "Test String 2";
auto [oc, writeErr] = ox::writeOC(testIn);
oxAssert(writeErr, "writeOC failed");
oxOutf("{}\n", oc.data());
auto [testOut, readErr] = ox::readOC<TestStruct>(oc.data());
oxAssert(readErr, "readOC failed");
oxAssert(testIn.Bool == testOut.Bool, "Bool value mismatch");
oxAssert(testIn.Int == testOut.Int, "Int value mismatch");
oxAssert(testIn.Int1 == testOut.Int1, "Int1 value mismatch");
oxAssert(testIn.Int2 == testOut.Int2, "Int2 value mismatch");
oxAssert(testIn.Int3 == testOut.Int3, "Int3 value mismatch");
oxAssert(testIn.Int4 == testOut.Int4, "Int4 value mismatch");
oxAssert(testIn.Int5 == testOut.Int5, "Int5 value mismatch");
oxAssert(testIn.Int6 == testOut.Int6, "Int6 value mismatch");
oxAssert(testIn.Int7 == testOut.Int7, "Int7 value mismatch");
oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch");
oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch");
oxAssert(testIn.String == testOut.String, "String value mismatch");
oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch");
oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch");
oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch");
oxAssert(testIn.List[3] == testOut.List[3], "List[3] value mismatch");
oxAssert(testIn.Map["asdf"] == testOut.Map["asdf"], "Map[\"asdf\"] value mismatch");
oxAssert(testIn.Map["aoeu"] == testOut.Map["aoeu"], "Map[\"aoeu\"] value mismatch");
oxAssert(testIn.EmptyStruct.Bool == testOut.EmptyStruct.Bool, "EmptyStruct.Bool value mismatch");
oxAssert(testIn.EmptyStruct.Int == testOut.EmptyStruct.Int, "EmptyStruct.Int value mismatch");
oxAssert(testIn.EmptyStruct.String == testOut.EmptyStruct.String, "EmptyStruct.String value mismatch");
oxAssert(testIn.Struct.Int == testOut.Struct.Int, "Struct.Int value mismatch");
oxAssert(testIn.Struct.String == testOut.Struct.String, "Struct.String value mismatch");
oxAssert(testIn.Struct.Bool == testOut.Struct.Bool, "Struct.Bool value mismatch");
return ox::Error(0);
}
},
{
"OrganicClawModelValue",
[] {
ox::Buffer dataBuff;
TestStruct testIn;
testIn.Bool = true;
testIn.Int = 42;
testIn.String = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = false;
testIn.Struct.Int = 300;
testIn.Struct.String = "Test String 2";
testIn.unionIdx = 1;
testIn.Union.Int = 93;
oxAssert(ox::writeOC(testIn).moveTo(dataBuff), "Data generation failed");
ox::TypeStore typeStore;
auto type = ox::buildTypeDef(typeStore, testIn);
oxAssert(type.error, "Descriptor write failed");
ox::ModelObject testOut;
OX_RETURN_ERROR(testOut.setType(type.value));
oxAssert(ox::readOC(dataBuff, testOut), "Data read failed");
oxAssert(testOut.get("Int").unwrap()->get<int>() == testIn.Int, "testOut.Int failed");
oxAssert(testOut.get("Bool").unwrap()->get<bool>() == testIn.Bool, "testOut.Bool failed");
oxAssert(testOut.get("String").unwrap()->get<ox::String>() == testIn.String, "testOut.String failed");
auto &testOutStruct = testOut.get("Struct").unwrap()->get<ox::ModelObject>();
auto &testOutUnion = testOut.get("Union").unwrap()->get<ox::ModelUnion>();
auto &testOutList = testOut.get("List").unwrap()->get<ox::ModelValueVector>();
auto testOutStructCopy = testOut.get("Struct").unwrap()->get<ox::ModelObject>();
auto testOutUnionCopy = testOut.get("Union").unwrap()->get<ox::ModelUnion>();
auto testOutListCopy = testOut.get("List").unwrap()->get<ox::ModelValueVector>();
oxAssert(testOutStruct.typeName() == TestStructNest::TypeName, "ModelObject TypeName failed");
oxAssert(testOutStruct.typeVersion() == TestStructNest::TypeVersion, "ModelObject TypeVersion failed");
oxAssert(testOutStruct.get("Bool").unwrap()->get<bool>() == testIn.Struct.Bool, "testOut.Struct.Bool failed");
oxAssert(testOutStruct.get("String").unwrap()->get<ox::String>() == testIn.Struct.String.c_str(), "testOut.Struct.String failed");
oxAssert(testOut.get("unionIdx").unwrap()->get<int>() == testIn.unionIdx, "testOut.unionIdx failed");
oxAssert(testOutUnion.unionIdx() == testIn.unionIdx, "testOut.Union idx wrong");
oxAssert(testOutUnion.get("Int").unwrap()->get<uint32_t>() == testIn.Union.Int, "testOut.Union.Int failed");
oxAssert(testOutList[0].get<uint32_t>() == testIn.List[0], "testOut.List[0] failed");
oxAssert(testOutList[1].get<uint32_t>() == testIn.List[1], "testOut.Struct.List[1] failed");
oxAssert(testOutStructCopy.get("Bool").unwrap()->get<bool>() == testIn.Struct.Bool, "testOut.Struct.Bool (copy) failed");
oxAssert(testOutStructCopy.get("String").unwrap()->get<ox::String>() == testIn.Struct.String.c_str(), "testOut.Struct.String (copy) failed");
oxAssert(testOutListCopy[0].get<uint32_t>() == testIn.List[0], "testOut.Struct.List[0] (copy) failed");
oxAssert(testOutListCopy[1].get<uint32_t>() == testIn.List[1], "testOut.Struct.List[1] (copy) failed");
return ox::Error(0);
}
},
{
"OrganicClawDef",
[] {
TestStruct testIn, testOut;
testIn.Bool = true;
testIn.Int = 42;
testIn.String = "Test String 1";
testIn.List[0] = 1;
testIn.List[1] = 2;
testIn.List[2] = 3;
testIn.List[3] = 4;
testIn.Struct.Bool = false;
testIn.Struct.Int = 300;
testIn.Struct.String = "Test String 2";
auto [oc, ocErr] = ox::writeOC(testIn);
oxAssert(ocErr, "Data generation failed");
ox::TypeStore typeStore;
auto type = ox::buildTypeDef(typeStore, testIn);
oxAssert(type.error, "Descriptor write failed");
OX_RETURN_ERROR(ox::walkModel<ox::OrganicClawReader>(type.value, oc.data(), oc.size(),
[](const ox::Vector<ox::FieldName>&, const ox::Vector<ox::String>&, const ox::DescriptorField &f,
ox::OrganicClawReader *rdr) -> ox::Error {
auto fieldName = f.fieldName.c_str();
switch (f.type->primitiveType) {
case ox::PrimitiveType::UnsignedInteger:
oxOutf("{}:\tuint{}_t:\t", fieldName, f.type->length * 8);
switch (f.type->length) {
case 1: {
uint8_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 2: {
uint16_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 4: {
uint32_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 8: {
uint64_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
}
oxOut("\n");
break;
case ox::PrimitiveType::SignedInteger:
oxOutf("{}:\tint{}_t:\t", fieldName, f.type->length * 8);
switch (f.type->length) {
case 1: {
int8_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 2: {
int16_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 4: {
int32_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
case 8: {
int64_t i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}", i);
break;
}
}
oxOut("\n");
break;
case ox::PrimitiveType::Bool: {
bool i = {};
oxAssert(rdr->field(fieldName, &i), "Walking model failed.");
oxOutf("{}:\tbool:\t{}\n", fieldName, i ? "true" : "false");
break;
}
case ox::PrimitiveType::String: {
ox::Vector<char> v(rdr->stringLength(fieldName) + 1);
oxAssert(rdr->fieldCString(fieldName, v.data(), v.size()), "Walking model failed.");
oxOutf("{}:\tstring:\t{}\n", fieldName, v.data());
break;
}
case ox::PrimitiveType::Struct:
break;
case ox::PrimitiveType::Union:
break;
}
return ox::Error(0);
}
));
return ox::Error(0);
}
},
}
};
int main(int argc, const char **args) {
if (argc < 2) {
oxError("Must specify test to run");
}
auto const testName = args[1];
auto const func = tests.find(testName);
if (func != tests.end()) {
oxAssert(func->second(), "Test returned Error");
return 0;
}
return -1;
}
+36
View File
@@ -0,0 +1,36 @@
add_library(
OxPreloader
src/preloader.cpp
)
if(NOT MSVC)
target_compile_options(OxPreloader PRIVATE -Wsign-conversion)
target_compile_options(OxPreloader PRIVATE -Wconversion)
endif()
target_link_libraries(
OxPreloader PUBLIC
OxClaw
OxModel
OxStd
)
target_include_directories(
OxPreloader PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS
OxPreloader
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
@@ -0,0 +1,73 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
namespace ox {
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t alignOf(const T &v) noexcept;
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t sizeOf(const T *t) noexcept;
template<typename PlatSpec>
struct AlignmentCatcher: public ModelHandlerBase<AlignmentCatcher<PlatSpec>, OpType::Reflect> {
std::size_t biggestAlignment = 1;
template<typename T>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion) noexcept {
return {};
}
template<typename T>
constexpr ox::Error setTypeInfo(
const char*,
int,
const Vector<String>&,
std::size_t) noexcept {
return {};
}
template<typename T, bool force>
constexpr ox::Error field(StringViewCR name, const UnionView<T, force> val) noexcept {
return field(name, val.get());
}
template<typename T>
constexpr ox::Error field(StringViewCR, const T *val) noexcept {
if constexpr(ox::is_pointer_v<T> || ox::is_integer_v<T>) {
biggestAlignment = ox::max(biggestAlignment, PlatSpec::alignOf(*val));
} else {
biggestAlignment = ox::max(biggestAlignment, alignOf<PlatSpec>(*val));
}
return {};
}
template<typename T>
constexpr ox::Error field(StringViewCR, const T *val, std::size_t cnt) noexcept {
for (std::size_t i = 0; i < cnt; ++i) {
OX_RETURN_ERROR(field(nullptr, &val[i]));
}
return {};
}
};
}
@@ -0,0 +1,61 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/serialize.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>
#include "alignmentcatcher.hpp"
#include "sizecatcher.hpp"
namespace ox {
struct NativePlatSpec {
static constexpr std::size_t RomStart = 1024;
using PtrType = uintptr_t;
using size_t = std::size_t;
template<typename T>
[[nodiscard]]
static constexpr std::size_t alignOf(const T &v) noexcept {
if constexpr(ox::is_integral_v<T>) {
return alignof(T);
} else if constexpr(ox::is_pointer_v<T>) {
const PtrType p = 0;
return alignOf(p);
} else {
AlignmentCatcher<NativePlatSpec> c;
oxAssert(model(c.interface(), &v), "Could not get alignment for type");
return c.biggestAlignment;
}
}
[[nodiscard]]
static constexpr auto correctEndianness(auto v) noexcept {
return v;
}
};
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t alignOf(const T &v) noexcept {
if constexpr(ox::is_integral_v<T>) {
return alignof(T);
} else if constexpr(ox::is_pointer_v<T>) {
typename PlatSpec::PtrType p = 0;
return PlatSpec::alignOf(p);
} else {
AlignmentCatcher<PlatSpec> c;
oxAssert(model(c.interface(), &v), "Could not get alignment for type");
return c.biggestAlignment;
}
}
}
@@ -0,0 +1,414 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/error.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/units.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/modelvalue.hpp>
#include "platspecs.hpp"
namespace ox {
template<typename PlatSpec>
class Preloader;
template<typename PlatSpec>
class Preloader: public ModelHandlerBase<Preloader<PlatSpec>, OpType::Reflect> {
private:
using PtrType = typename PlatSpec::PtrType;
static constexpr auto PtrSize = sizeof(PtrType);
class UnionIdxTracker {
private:
int m_unionIdx = -1;
int m_it = 0;
public:
constexpr UnionIdxTracker() noexcept = default;
constexpr explicit UnionIdxTracker(int idx) noexcept: m_unionIdx(idx) {}
constexpr auto checkAndIterate() noexcept {
return m_unionIdx == -1 || m_it++ == m_unionIdx;
}
};
ox::Buffer m_buff;
ox::BufferWriter m_writer;
// list of all the places where ptrs were written to buffer
struct PtrPair {
std::size_t loc = 0;
typename PlatSpec::PtrType value = 0;
constexpr PtrPair() noexcept = default;
constexpr PtrPair(std::size_t pLoc, typename PlatSpec::PtrType pValue) noexcept:
loc(pLoc), value(pValue) {}
};
ox::Vector<PtrPair> m_ptrs;
ox::Vector<UnionIdxTracker, 8> m_unionIdx = {{}};
class AllocStackItem {
public:
PtrType restore = 0;
ox::ios_base::seekdir seekdir = ox::ios_base::end;
constexpr AllocStackItem(PtrType pRestore, ox::ios_base::seekdir pSeekdir = ox::ios_base::end) noexcept:
restore(pRestore), seekdir(pSeekdir) {}
};
ox::Vector<AllocStackItem, 8> m_allocStack;
ox::Vector<size_t> m_allocStart{};
constexpr Preloader() noexcept: m_writer(&m_buff) {}
public:
Preloader(const Preloader &src) = delete;
Preloader(Preloader &&src) = delete;
const Preloader &operator=(const Preloader &src) = delete;
const Preloader &operator=(Preloader &&src) = delete;
constexpr static ox::Result<ox::UPtr<Preloader>> make(ox::ios_base::seekdir anchor = ox::ios_base::cur,
std::size_t sz = 0) noexcept;
template<typename T>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion) noexcept {
return {};
}
template<typename T>
constexpr ox::Error setTypeInfo(
const char*,
int,
const Vector<String>&,
std::size_t) noexcept {
return {};
}
template<typename U, bool force>
constexpr ox::Error field(StringViewCR, ox::UnionView<U, force> val) noexcept;
template<typename T>
constexpr ox::Error field(StringViewCR, const T *val) noexcept;
template<std::size_t SmallStringSize>
constexpr ox::Error field(StringViewCR, const ox::BasicString<SmallStringSize> *val) noexcept;
template<typename T, std::size_t sz>
constexpr ox::Error field(StringViewCR, const ox::Array<T, sz> *valArray) noexcept;
template<typename T>
constexpr ox::Error field(StringViewCR, const T **val, std::size_t cnt) noexcept;
constexpr ox::Result<std::size_t> startAlloc(size_t sz, size_t align) noexcept;
constexpr ox::Result<std::size_t> startAlloc(size_t sz, size_t align, std::size_t restore) noexcept;
constexpr ox::Error endAlloc() noexcept;
constexpr ox::Error offsetPtrs(std::size_t offset) noexcept;
[[nodiscard]]
constexpr auto &buff() const noexcept {
return m_buff;
}
template<typename T>
constexpr ox::Error pad(const T*) noexcept;
private:
constexpr ox::Error fieldVector(StringViewCR name, const ox::ModelValueVector *val) noexcept;
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr ox::Error fieldVector(StringViewCR, const ox::Vector<T, SmallVectorSize, Allocator> *val) noexcept;
constexpr ox::Error fieldVector(StringViewCR, const auto *val, ox::VectorMemMap<PlatSpec> vecVal) noexcept;
constexpr ox::Error fieldArray(StringViewCR name, ox::ModelValueArray const*val) noexcept;
constexpr bool unionCheckAndIt() noexcept;
[[nodiscard]]
constexpr size_t calcPadding(size_t align) const noexcept;
};
template<typename PlatSpec>
constexpr ox::Result<ox::UPtr<Preloader<PlatSpec>>>
Preloader<PlatSpec>::make(ox::ios_base::seekdir anchor, std::size_t sz) noexcept {
auto p = ox::UPtr<Preloader>(new Preloader);
if (const auto err = p->m_writer.seekp(0, anchor)) {
return {std::move(p), err};
}
if (const auto err = p->m_writer.write(nullptr, sz)) {
return {std::move(p), err};
}
if (const auto err = p->m_writer.seekp(p->m_writer.tellp() - sz)) {
return {std::move(p), err};
}
return p;
}
template<typename PlatSpec>
template<typename U, bool force>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const ox::UnionView<U, force> val) noexcept {
if (!unionCheckAndIt()) {
return {};
}
OX_RETURN_ERROR(pad(val.get()));
m_unionIdx.emplace_back(val.idx());
const auto err = preload<PlatSpec, U>(this, val.get());
m_unionIdx.pop_back();
return err;
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR name, const T *val) noexcept {
if (!unionCheckAndIt()) {
return {};
}
OX_RETURN_ERROR(pad(val));
if constexpr(ox::is_integral_v<T>) {
return ox::serialize(m_writer, PlatSpec::correctEndianness(*val));
} else if constexpr(ox::is_pointer_v<T>) {
const PtrType a = startAlloc(sizeOf<PlatSpec>(val), alignOf<PlatSpec>(*val), m_writer.tellp()) + PlatSpec::RomStart;
OX_RETURN_ERROR(field(name, *val));
OX_RETURN_ERROR(endAlloc());
return ox::serialize(m_writer, PlatSpec::correctEndianness(a));
} else if constexpr(ox::isVector_v<T>) {
return fieldVector(name, val);
} else if constexpr(ox::is_same_v<T, ox::ModelValueVector>) {
val->types();
return fieldVector(name, val);
} else if constexpr(ox::is_same_v<T, ox::ModelValueArray>) {
return fieldArray(name, val);
} else {
m_unionIdx.emplace_back(-1);
const auto out = preload<PlatSpec, T>(this, val);
m_unionIdx.pop_back();
return out;
}
}
template<typename PlatSpec>
template<std::size_t SmallStringSize>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const ox::BasicString<SmallStringSize> *val) noexcept {
if (!unionCheckAndIt()) {
return {};
}
using VecMap = ox::VectorMemMap<PlatSpec>;
const auto sz = val->bytes();
VecMap vecVal{
.smallVecSize = SmallStringSize,
.size = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(sz)),
.cap = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(sz)),
};
OX_RETURN_ERROR(pad(&vecVal));
const auto restore = m_writer.tellp();
std::size_t a = 0;
if (sz && sz >= SmallStringSize) {
OX_RETURN_ERROR(ox::allocate(m_writer, sz).moveTo(a));
} else {
a = restore;
}
vecVal.items = PlatSpec::correctEndianness(static_cast<PtrType>(a) + PlatSpec::RomStart);
OX_RETURN_ERROR(m_writer.seekp(a));
OX_RETURN_ERROR(m_writer.write(val->data(), sz));
OX_RETURN_ERROR(m_writer.seekp(restore));
OX_RETURN_ERROR(serialize(m_writer, vecVal));
m_ptrs.emplace_back(restore + offsetof(VecMap, items), vecVal.items);
return {};
}
template<typename PlatSpec>
template<typename T, std::size_t sz>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR name, const ox::Array<T, sz> *val) noexcept {
if (!unionCheckAndIt()) {
return {};
}
OX_RETURN_ERROR(pad(&(*val)[0]));
// serialize the Array elements
if constexpr(sz) {
m_unionIdx.emplace_back(-1);
for (std::size_t i = 0; i < val->size(); ++i) {
OX_RETURN_ERROR(this->interface()->field(name, &(*val)[i]));
}
m_unionIdx.pop_back();
}
return {};
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const T **val, std::size_t cnt) noexcept {
if (!unionCheckAndIt()) {
return {};
}
if (cnt) {
OX_RETURN_ERROR(pad(*val));
// serialize the array
m_unionIdx.emplace_back(-1);
for (std::size_t i = 0; i < cnt; ++i) {
OX_RETURN_ERROR(this->interface()->field(nullptr, &val[i]));
}
m_unionIdx.pop_back();
}
return {};
}
template<typename PlatSpec>
constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(size_t sz, size_t align) noexcept {
m_allocStack.emplace_back(static_cast<typename PlatSpec::PtrType>(m_writer.tellp()));
OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
auto const padding = calcPadding(align);
OX_REQUIRE_M(a, ox::allocate(m_writer, sz + padding));
a += padding;
OX_RETURN_ERROR(m_writer.seekp(a));
m_allocStart.push_back(a);
return a;
}
template<typename PlatSpec>
constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(
std::size_t sz, size_t align, std::size_t restore) noexcept {
m_allocStack.emplace_back(restore, ox::ios_base::beg);
OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
auto const padding = calcPadding(align);
OX_REQUIRE_M(a, ox::allocate(m_writer, sz + padding));
a += padding;
OX_RETURN_ERROR(m_writer.seekp(a));
m_allocStart.push_back(a);
return a;
}
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::endAlloc() noexcept {
if (m_allocStack.empty()) {
return m_writer.seekp(0, ox::ios_base::end);
}
const auto &si = *m_allocStack.back().unwrap();
OX_RETURN_ERROR(m_writer.seekp(static_cast<ox::ssize_t>(si.restore), si.seekdir));
m_allocStack.pop_back();
m_allocStart.pop_back();
return {};
}
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::offsetPtrs(std::size_t offset) noexcept {
for (const auto &p : m_ptrs) {
OX_RETURN_ERROR(m_writer.seekp(p.loc));
const auto val = PlatSpec::template correctEndianness<typename PlatSpec::PtrType>(
static_cast<typename PlatSpec::PtrType>(p.value + offset));
OX_RETURN_ERROR(ox::serialize(m_writer, val));
}
OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
return {};
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::pad(const T *v) noexcept {
const auto a = alignOf<PlatSpec>(*v);
const auto excess = m_writer.tellp() % a;
if (excess) {
return m_writer.write(nullptr, a - excess);
} else {
return {};
}
}
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
StringViewCR name, const ox::ModelValueVector *val) noexcept {
// serialize the Vector
ox::VectorMemMap<PlatSpec> vecVal{
.size = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(val->size())),
.cap = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(val->size())),
};
return fieldVector(name, val, vecVal);
}
template<typename PlatSpec>
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
StringViewCR name, const ox::Vector<T, SmallVectorSize, Allocator> *val) noexcept {
// serialize the Vector
ox::VectorMemMap<PlatSpec> vecVal{
.smallVecSize = SmallVectorSize * sizeOf<PlatSpec>(static_cast<T*>(nullptr)),
.size = PlatSpec::correctEndianness(
static_cast<typename PlatSpec::size_t>(val->size())),
.cap = PlatSpec::correctEndianness(
static_cast<typename PlatSpec::size_t>(val->size())),
};
return fieldVector(name, val, vecVal);
}
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
StringViewCR, const auto *val, ox::VectorMemMap<PlatSpec> vecVal) noexcept {
OX_RETURN_ERROR(pad(&vecVal));
const auto vecValPt = m_writer.tellp();
// serialize the Vector elements
if (val->size()) {
const auto sz = sizeOf<PlatSpec>(&(*val)[0]) * val->size();
const auto align = alignOf<PlatSpec>((*val)[0]);
OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
auto const padding = calcPadding(align);
OX_REQUIRE_M(p, ox::allocate(m_writer, sz + padding));
p += padding;
OX_RETURN_ERROR(m_writer.seekp(p));
m_unionIdx.emplace_back(-1);
for (std::size_t i = 0; i < val->size(); ++i) {
OX_RETURN_ERROR(this->interface()->field(nullptr, &val->operator[](i)));
}
m_unionIdx.pop_back();
vecVal.items = PlatSpec::correctEndianness(
static_cast<typename PlatSpec::size_t>(p + PlatSpec::RomStart));
OX_RETURN_ERROR(m_writer.seekp(vecValPt));
} else {
vecVal.items = 0;
}
// serialize the Vector
OX_RETURN_ERROR(serialize(m_writer, vecVal));
m_ptrs.emplace_back(m_writer.tellp() - PtrSize, vecVal.items);
return {};
}
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldArray(StringViewCR, ox::ModelValueArray const*val) noexcept {
OX_RETURN_ERROR(pad(&(*val)[0]));
for (auto const&v : *val) {
OX_RETURN_ERROR(this->interface()->field({}, &v));
}
return {};
}
template<typename PlatSpec>
constexpr bool Preloader<PlatSpec>::unionCheckAndIt() noexcept {
auto &u = *m_unionIdx.back().unwrap();
return u.checkAndIterate();
}
template<typename PlatSpec>
constexpr size_t Preloader<PlatSpec>::calcPadding(size_t align) const noexcept {
auto const excess = m_writer.tellp() % align;
return (align * (excess != 0)) - excess;
}
template<typename PlatSpec, typename T>
constexpr ox::Error preload(Preloader<PlatSpec> *pl, ox::CommonPtrWith<T> auto *obj) noexcept {
OX_RETURN_ERROR(model(pl->interface(), obj));
return pl->pad(obj);
}
extern template class Preloader<NativePlatSpec>;
}
@@ -0,0 +1,123 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include "unionsizecatcher.hpp"
namespace ox {
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t alignOf(const T &t) noexcept;
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t sizeOf(const T *t) noexcept;
template<typename PlatSpec>
class SizeCatcher: public ModelHandlerBase<SizeCatcher<PlatSpec>, OpType::Reflect> {
private:
std::size_t m_size = 0;
public:
constexpr explicit SizeCatcher() noexcept = default;
template<typename T>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion) noexcept {
return {};
}
template<typename T>
constexpr ox::Error setTypeInfo(
const char*,
int,
const Vector<String>&,
std::size_t) noexcept {
return {};
}
template<typename T, bool force>
constexpr ox::Error field(const char*, UnionView<T, force>) noexcept;
template<typename T>
constexpr ox::Error field(const char*, const T *val) noexcept;
template<typename T>
constexpr ox::Error field(const char*, const T **val, std::size_t cnt) noexcept;
[[nodiscard]]
constexpr auto size() const noexcept {
return m_size;
}
private:
constexpr void pad(const auto *val) noexcept;
};
template<typename PlatSpec>
template<typename T, bool force>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const UnionView<T, force> val) noexcept {
pad(val.get());
UnionSizeCatcher<PlatSpec> sc;
OX_RETURN_ERROR(model(sc.interface(), val.get()));
m_size += sc.size();
return {};
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const T *val) noexcept {
pad(val);
m_size += sizeOf<PlatSpec>(val);
return {};
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const T **val, std::size_t cnt) noexcept {
for (std::size_t i = 0; i < cnt; ++i) {
OX_RETURN_ERROR(field("", &val[i]));
}
return {};
}
template<typename PlatSpec>
constexpr void SizeCatcher<PlatSpec>::pad(const auto *val) noexcept {
const auto a = alignOf<PlatSpec>(*val);
const auto excess = m_size % a;
if (excess) {
m_size += a - excess;
}
}
template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t sizeOf(const T *t) noexcept {
if constexpr(ox::is_integral_v<T>) {
return sizeof(T);
} else if constexpr(ox::is_pointer_v<T>) {
return sizeof(typename PlatSpec::PtrType);
} else {
SizeCatcher<PlatSpec> sc;
const auto err = model(sc.interface(), t);
oxAssert(err, "Could not get size of type");
return sc.size();
}
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
namespace ox {
template<typename PlatSpec>
class UnionSizeCatcher: public ModelHandlerBase<UnionSizeCatcher<PlatSpec>, OpType::Reflect> {
private:
std::size_t m_size = 0;
public:
template<typename T>
constexpr ox::Error setTypeInfo(
const char* = T::TypeName,
int = T::TypeVersion) noexcept {
return {};
}
template<typename T>
constexpr ox::Error setTypeInfo(
const char*,
int,
const Vector<String>&,
std::size_t) noexcept {
return {};
}
template<typename T, bool force>
constexpr ox::Error field(StringViewCR, const UnionView<T, force> val) noexcept {
UnionSizeCatcher<PlatSpec> sc;
OX_RETURN_ERROR(model(sc.interface(), val.get()));
m_size += sc.size();
return {};
}
template<typename T>
constexpr ox::Error field(StringViewCR, const T *val) noexcept;
template<typename T>
constexpr ox::Error field(StringViewCR, const T **val, std::size_t cnt) noexcept;
[[nodiscard]]
constexpr auto size() const noexcept {
return m_size;
}
private:
template<typename T, std::size_t SmallVecSize>
constexpr ox::Error fieldStr(StringViewCR, const ox::BasicString<SmallVecSize> *val) noexcept;
template<typename T, std::size_t SmallVecSize>
constexpr ox::Error fieldVector(StringViewCR, const ox::Vector<T, SmallVecSize> *val) noexcept;
constexpr void setSize(std::size_t sz) noexcept;
};
template<typename PlatSpec>
template<typename T>
constexpr ox::Error UnionSizeCatcher<PlatSpec>::field(StringViewCR, const T *val) noexcept {
setSize(sizeOf<PlatSpec>(val));
return {};
}
template<typename PlatSpec>
template<typename T>
constexpr ox::Error UnionSizeCatcher<PlatSpec>::field(StringViewCR, const T **val, std::size_t cnt) noexcept {
for (std::size_t i = 0; i < cnt; ++i) {
OX_RETURN_ERROR(field("", &val[i]));
}
return {};
}
template<typename PlatSpec>
template<typename T, std::size_t SmallStrSize>
constexpr ox::Error UnionSizeCatcher<PlatSpec>::fieldStr(StringViewCR, const ox::BasicString<SmallStrSize>*) noexcept {
ox::VectorMemMap<PlatSpec> v;
setSize(sizeOf<PlatSpec>(v));
return {};
}
template<typename PlatSpec>
template<typename T, std::size_t SmallVecSize>
constexpr ox::Error UnionSizeCatcher<PlatSpec>::fieldVector(StringViewCR, const ox::Vector<T, SmallVecSize>*) noexcept {
ox::VectorMemMap<PlatSpec> v;
setSize(sizeOf<PlatSpec>(v));
return {};
}
template<typename PlatSpec>
constexpr void UnionSizeCatcher<PlatSpec>::setSize(std::size_t sz) noexcept {
m_size = ox::max(m_size, sz);
}
}
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include <ox/preloader/preloader.hpp>
namespace ox {
template class Preloader<NativePlatSpec>;
}
+119
View File
@@ -0,0 +1,119 @@
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
# enable warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
endif()
if(OX_USE_STDLIB AND OX_ENABLE_TRACEHOOK)
add_library(
OxTraceHook SHARED
src/tracehook.cpp
)
else()
add_library(
OxTraceHook
src/tracehook.cpp
)
endif()
target_compile_definitions(
OxTraceHook PUBLIC
$<$<BOOL:${OX_BARE_METAL}>:OX_BARE_METAL>
$<$<BOOL:${OX_USE_STDLIB}>:OX_USE_STDLIB>
$<$<BOOL:${OX_NODEBUG}>:OX_NODEBUG>
)
add_library(
OxStd
src/assert.cpp
src/bit.cpp
src/buffer.cpp
src/buildinfo.cpp
src/byteswap.cpp
src/concepts.cpp
src/fmt.cpp
src/heapmgr.cpp
src/istreamreader.cpp
src/math.cpp
src/memops.cpp
src/random.cpp
src/reader.cpp
src/substitutes.cpp
src/stacktrace.cpp
src/string.cpp
src/stringview.cpp
src/strops.cpp
src/trace.cpp
src/typetraits.cpp
src/uuid.cpp
src/vec.cpp
)
if(NOT MSVC)
target_compile_options(OxStd PRIVATE -Wsign-conversion)
target_compile_options(OxStd PRIVATE -Wconversion)
if(${OX_OS_LINUX} AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(OxStd PUBLIC -export-dynamic)
#target_link_options(OxStd PUBLIC -W1,-E)
elseif(${OX_OS_FREEBSD})
target_link_libraries(
OxStd PUBLIC
execinfo
)
endif()
endif()
if(NOT OX_BARE_METAL)
set_property(
TARGET
OxStd
PROPERTY
POSITION_INDEPENDENT_CODE ON
)
endif()
target_compile_definitions(
OxStd PUBLIC
$<$<BOOL:${OX_USE_STDLIB}>:OX_USE_STDLIB>
$<$<BOOL:${OX_NODEBUG}>:OX_NODEBUG>
)
if(NOT WIN32)
target_link_libraries(
OxStd PUBLIC
$<$<CXX_COMPILER_ID:GNU>:$<$<BOOL:${OX_USE_STDLIB}>:dl>>
)
endif()
target_link_libraries(
OxStd PUBLIC
$<$<CXX_COMPILER_ID:GNU>:gcc>
OxTraceHook
CityHash
)
target_include_directories(
OxStd PUBLIC
include
)
target_include_directories(
OxTraceHook PUBLIC
include
)
install(
DIRECTORY
include/ox
DESTINATION
include
)
install(
TARGETS OxStd OxTraceHook
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
if(OX_RUN_TESTS)
add_subdirectory(test)
endif()
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "def.hpp"
#include "error.hpp"
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
template<typename It, typename T>
constexpr ox::Result<size_t> findIdx(It begin, It end, T const&value) {
auto it = begin;
for (; it != end; ++it) {
if (*it == value) {
return it.offset();
}
}
return ox::Error{1, "item not found"};
}
template<typename It, typename T>
constexpr It find(It begin, It end, T const&value) {
for (; begin != end; ++begin) {
if (*begin == value) {
return begin;
}
}
return end;
}
template<typename It>
constexpr It find_if(It begin, It end, auto predicate) {
for (; begin != end; ++begin) {
if (predicate(*begin)) {
return begin;
}
}
return end;
}
template<typename It, typename Size, typename OutIt>
constexpr OutIt copy_n(It in, Size cnt, OutIt out) {
for (Size i = 0; i < cnt; ++i) {
*out = *in;
++out;
++in;
}
return out;
}
}
OX_CLANG_NOWARN_END
+202
View File
@@ -0,0 +1,202 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "assert.hpp"
#include "array.hpp"
#include "def.hpp"
#include "span.hpp"
namespace ox {
namespace detail {
template<bool unique>
class AnyPtrT {
private:
struct WrapBase {
virtual constexpr ~WrapBase() = default;
virtual constexpr WrapBase *copyTo(Span<char> s) const noexcept = 0;
virtual constexpr operator bool() const noexcept = 0;
virtual void free() noexcept = 0;
};
template<typename T>
struct Wrap final: WrapBase {
T *data{};
explicit constexpr Wrap(T *pData) noexcept: data(pData) {
}
constexpr WrapBase *copyTo(Span<char> s) const noexcept override {
oxAssert(s.size() >= sizeof(Wrap), "too small buffer");
if (std::is_constant_evaluated()) {
return new Wrap(data);
} else {
return new(s.data()) Wrap(data);
}
}
constexpr operator bool() const noexcept override {
return data != nullptr;
}
constexpr void free() noexcept override {
safeDelete(data);
data = {};
}
};
union {
WrapBase *m_wrapPtr{};
AllocAlias<Wrap<void*>> m_wrapData;
} m_data;
public:
constexpr AnyPtrT() noexcept = default;
template<typename T>
constexpr AnyPtrT(T *ptr) noexcept {
if (std::is_constant_evaluated()) {
setWrapPtr(new Wrap<T>(ptr));
} else {
new(m_data.m_wrapData.data()) Wrap<T>(ptr);
}
}
constexpr AnyPtrT(AnyPtrT const &other) noexcept requires(!unique) {
if (other) {
setWrapPtr(other.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
}
}
constexpr AnyPtrT(AnyPtrT &&other) noexcept {
if (other) {
setWrapPtr(other.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
m_data.m_wrapData = {};
}
}
constexpr ~AnyPtrT() noexcept {
if constexpr(unique) {
free();
}
if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
}
template<typename T>
constexpr AnyPtrT &operator=(T *ptr) noexcept {
if constexpr(unique) {
free();
} else if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
if (std::is_constant_evaluated()) {
setWrapPtr(new Wrap(ptr));
} else {
new(m_data.m_wrapData.data()) Wrap(ptr);
}
return *this;
}
constexpr AnyPtrT &operator=(AnyPtrT const &ptr) noexcept requires(!unique) {
if (this != &ptr) {
if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
if (std::is_constant_evaluated()) {
if (ptr) {
ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff);
}
} else {
if (ptr) {
setWrapPtr(ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
} else {
setWrapPtr(nullptr);
}
}
}
return *this;
}
constexpr AnyPtrT &operator=(AnyPtrT &&ptr) noexcept {
if (this != &ptr) {
if constexpr(unique) {
free();
} else if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
if (ptr) {
setWrapPtr(ptr.getWrapPtr()->copyTo(m_data.m_wrapData.buff));
if (std::is_constant_evaluated()) {
ox::safeDelete(ptr.m_data.m_wrapPtr);
setWrapPtr(nullptr);
}
} else {
m_data = {};
}
}
return *this;
}
constexpr operator bool() const noexcept {
return getWrapPtr() && *getWrapPtr();
}
constexpr void free() noexcept {
if (auto p = getWrapPtr()) {
p->free();
}
if (std::is_constant_evaluated()) {
ox::safeDelete(m_data.m_wrapPtr);
}
m_data.m_wrapData = {};
}
template<typename T>
[[nodiscard]]
constexpr T *get() const noexcept {
if constexpr(defines::HasRTTI) {
return dynamic_cast<Wrap<T> const*>(getWrapPtr())->data;
}
return static_cast<Wrap<T> const*>(getWrapPtr())->data;
}
private:
constexpr void setWrapPtr(WrapBase *ptr) noexcept {
if (std::is_constant_evaluated()) {
m_data.m_wrapPtr = ptr;
}
}
constexpr WrapBase *getWrapPtr() noexcept {
if (std::is_constant_evaluated()) {
return m_data.m_wrapPtr;
} else {
return std::launder(reinterpret_cast<WrapBase*>(m_data.m_wrapData.data()));
}
}
constexpr WrapBase const *getWrapPtr() const noexcept {
if (std::is_constant_evaluated()) {
return m_data.m_wrapPtr;
} else {
return std::launder(reinterpret_cast<WrapBase const*>(m_data.m_wrapData.data()));
}
}
};
}
using AnyPtr = detail::AnyPtrT<false>;
using UAnyPtr = detail::AnyPtrT<true>;
}
+211
View File
@@ -0,0 +1,211 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "bit.hpp"
#include "def.hpp"
#include "error.hpp"
#include "initializerlist.hpp"
#include "iterator.hpp"
#include "math.hpp"
#include "new.hpp"
#include "types.hpp"
#include "utility.hpp"
OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage)
namespace ox {
template<typename T, std::size_t ArraySize>
class Array {
public:
using value_type = T;
using size_type = std::size_t;
template<typename RefType = T&, typename PtrType = T*, bool reverse = false>
using iterator = SpanIterator<T, RefType, PtrType, reverse>;
private:
T m_items[ArraySize]{};
public:
constexpr Array() noexcept = default;
template<typename ...Args>
constexpr Array(Args ...list) noexcept: m_items{std::move(list)...} {
}
constexpr Array(std::initializer_list<T> list) noexcept;
constexpr Array(const Array &other);
constexpr Array(Array &&other) noexcept;
constexpr ~Array() = default;
constexpr iterator<> begin() noexcept {
return iterator<>(&m_items[0], 0, ArraySize);
}
constexpr iterator<> end() noexcept {
return iterator<>(&m_items[0], ArraySize, ArraySize);
}
constexpr iterator<const T&, const T*> begin() const noexcept {
return iterator<const T&, const T*>(&m_items[0], 0, ArraySize);
}
constexpr iterator<const T&, const T*> end() const noexcept {
return iterator<const T&, const T*>(&m_items[0], ArraySize, ArraySize);
}
constexpr iterator<T&, T*, true> rbegin() noexcept {
return iterator<T&, T*, true>(&m_items[0], ArraySize - 1, ArraySize);
}
constexpr iterator<T&, T*, true> rend() noexcept {
return iterator<T&, T*, true>(&m_items[0], MaxValue<size_type>, ArraySize);
}
constexpr iterator<const T&, const T*, true> rbegin() const noexcept {
return iterator<const T&, const T*, true>(m_items, ArraySize - 1, ArraySize);
}
constexpr iterator<const T&, const T*, true> rend() const noexcept {
return iterator<const T&, const T*, true>(m_items, MaxValue<size_type>, ArraySize);
}
constexpr bool operator==(const Array &other) const;
constexpr Array &operator=(const Array &other);
constexpr Array &operator=(Array &&other) noexcept;
[[nodiscard]]
constexpr T &operator[](std::size_t i) noexcept;
[[nodiscard]]
constexpr const T &operator[](std::size_t i) const noexcept;
[[nodiscard]]
constexpr std::size_t size() const noexcept;
[[nodiscard]]
constexpr T *data() noexcept {
return m_items;
}
[[nodiscard]]
constexpr const T *data() const noexcept {
return m_items;
}
[[nodiscard]]
constexpr bool contains(const T&) const;
};
template<typename T, std::size_t ArraySize, typename RefType, bool reverse>
using ArrayIt = typename Array<T, ArraySize>::template iterator<RefType, reverse>;
template<typename T, std::size_t ArraySize, typename RefType, bool reverse>
constexpr ArrayIt<T, ArraySize, RefType, reverse> operator+(std::size_t n, const ArrayIt<T, ArraySize, RefType, reverse> &a) {
return a + n;
}
template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(std::initializer_list<T> list) noexcept {
for (auto i = 0ul; auto &item : list) {
this->operator[](i) = item;
++i;
}
}
template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(const Array &other) {
for (std::size_t i = 0; i < ArraySize; ++i) {
m_items[i] = T(other.m_items[i]);
}
}
template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize>::Array(Array &&other) noexcept {
if (this != &other) {
for (std::size_t i = 0; i < ArraySize; ++i) {
m_items[i] = T(std::move(other.m_items[i]));
}
}
}
template<typename T, std::size_t ArraySize>
constexpr bool Array<T, ArraySize>::operator==(const Array &other) const {
if (std::is_constant_evaluated()) {
for (std::size_t i = 0; i < ArraySize; i++) {
if (!(m_items[i] == other.m_items[i])) {
return false;
}
}
return true;
} else {
return memcmp(this, &other, sizeof(*this)) == 0;
}
}
template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize> &Array<T, ArraySize>::operator=(const Array &other) {
if (this != &other) {
for (std::size_t i = 0; i < ArraySize; ++i) {
m_items[i] = other.m_items[i];
}
}
return *this;
return *this;
}
template<typename T, std::size_t ArraySize>
constexpr Array<T, ArraySize> &Array<T, ArraySize>::operator=(Array &&other) noexcept {
if (this != &other) {
for (std::size_t i = 0; i < ArraySize; ++i) {
m_items[i] = std::move(other.m_items[i]);
}
}
return *this;
}
template<typename T, std::size_t ArraySize>
constexpr T &Array<T, ArraySize>::operator[](std::size_t i) noexcept {
boundsCheck(i, size(), "Array access overflow");
return m_items[i];
}
template<typename T, std::size_t ArraySize>
constexpr const T &Array<T, ArraySize>::operator[](std::size_t i) const noexcept {
boundsCheck(i, size(), "Array access overflow");
return m_items[i];
}
template<typename T, std::size_t ArraySize>
constexpr std::size_t Array<T, ArraySize>::size() const noexcept {
return ArraySize;
}
template<typename T, std::size_t ArraySize>
constexpr bool Array<T, ArraySize>::contains(const T &v) const {
for (std::size_t i = 0; i < ArraySize; i++) {
if (m_items[i] == v) {
return true;
}
}
return false;
}
}
OX_CLANG_NOWARN_END
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright 2015 - 2025 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "def.hpp"
#include "defines.hpp"
#include "error.hpp"
#include "realstd.hpp"
#include "stacktrace.hpp"
#include "trace.hpp"
#include "typetraits.hpp"
namespace ox {
[[noreturn]]
void panic(
Error const&err,
StringViewCR panicMsg,
std::source_location const &src = std::source_location::current()) noexcept;
[[noreturn]]
inline void panic(
StringViewCR panicMsg,
std::source_location const &src = std::source_location::current()) noexcept {
panic(Error{1}, panicMsg, src);
}
[[noreturn]]
constexpr void constexprPanic(
StringViewCR panicMsg,
Error const &err = {},
std::source_location const &src = std::source_location::current()) noexcept {
if (!std::is_constant_evaluated()) {
panic(err, panicMsg, src);
} else {
while (true);
}
}
void assertFailFuncRuntime(
StringViewCR assertTxt,
StringViewCR msg,
std::source_location const &src = std::source_location::current()) noexcept;
void assertFailFuncRuntime(
Error const &err,
StringViewCR,
StringViewCR assertMsg,
std::source_location const &src = std::source_location::current()) noexcept;
constexpr void assertFunc(
bool const pass,
[[maybe_unused]]StringViewCR assertTxt,
[[maybe_unused]]StringViewCR msg,
std::source_location const &src = std::source_location::current()) noexcept {
if (!pass) {
if (!std::is_constant_evaluated()) {
assertFailFuncRuntime(assertTxt, msg, src);
} else {
while (true);
}
}
}
constexpr void assertFunc(
Error const &err,
StringViewCR,
StringViewCR assertMsg,
std::source_location const &src = std::source_location::current()) noexcept {
if (err) {
if (!std::is_constant_evaluated()) {
assertFailFuncRuntime(err, {}, assertMsg, src);
} else {
while (true);
}
}
}
constexpr void expect(
auto const &actual,
auto const &expected,
std::source_location const &src = std::source_location::current()) noexcept {
if (actual != expected) {
if (!std::is_constant_evaluated()) {
#if defined(OX_USE_STDLIB)
oxErrf(
"\n\033[31;1;1mASSERT FAILURE:\033[0m [{}:{}]: {}\n",
src.file_name(),
src.line(),
"Value incorrect");
oxErrf(
"expected: {}\nactual: {}\n",
detail::toStringView<true>(expected),
detail::toStringView<true>(actual));
printStackTrace(2);
oxTracef(
"assert.expect", "Failed assert: {} == {} [{}:{}]",
detail::toStringView<true>(actual),
detail::toStringView<true>(expected),
src.file_name(),
src.line());
std::abort();
#else
constexprPanic("Comparison failed", {}, src);
#endif
} else {
while (true);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More