Skip to content

Instantly share code, notes, and snippets.

@oreyg
Created December 8, 2023 22:11
Show Gist options
  • Save oreyg/125851611d0fece3675aa3a43a9c125a to your computer and use it in GitHub Desktop.
Save oreyg/125851611d0fece3675aa3a43a9c125a to your computer and use it in GitHub Desktop.
// Stream-like archive, that treats data as a flat blob
template<typename TArchive>
concept TStreamArchiveConcept = requires(TArchive& archive,
char& c8,
b8& b8,
u16& u16,
u32& u32,
u64& u64,
i16& i16,
i32& i32,
i64& i64,
f32& f32,
f64& f64,
size_t& size,
std::string& str,
std::string_view sv
)
{
// --- Arithmetic types ---
{
archive.Map(c8)
} -> std::same_as<void>;
{
archive.Map(b8)
} -> std::same_as<void>;
{
archive.Map(u16)
} -> std::same_as<void>;
{
archive.Map(u32)
} -> std::same_as<void>;
{
archive.Map(u64)
} -> std::same_as<void>;
{
archive.Map(i16)
} -> std::same_as<void>;
{
archive.Map(i32)
} -> std::same_as<void>;
{
archive.Map(i64)
} -> std::same_as<void>;
{
archive.Map(f32)
} -> std::same_as<void>;
{
archive.Map(f64)
} -> std::same_as<void>;
// --- std::string types ---
{
// For input we are forced to provide strings, as we can resize them
archive.Map(str)
} -> std::same_as<void>;
{
// TO-DO: implement dynamic strings for input (maybe string-hash matching?)
archive.Map(sv)
} -> std::same_as<void>;
// --- Array types ---
{
// PopArray receives the output of PushArray
archive.PopArray(archive.PushArray(size))
};
// --- Object types ---
{
// PopArray receives the output of PushArray
archive.PopObject(archive.PushObject())
};
};
// DOM-like archive, that does support named objects and arrays
template<typename TArchive>
concept TDOMArchiveConcept = requires(TArchive& archive,
char& c8,
b8& b8,
u16& u16,
u32& u32,
u64& u64,
i16& i16,
i32& i32,
i64& i64,
f32& f32,
f64& f64,
size_t& size,
std::string& str,
std::string_view sv
)
{
// Access object by Names
// --- Arithmetic types ---
{
archive.Map(std::string_view(), b8)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), c8)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), u16)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), u32)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), u64)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), i16)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), i32)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), i64)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), f32)
} -> std::same_as<void>;
{
archive.Map(std::string_view(), f64)
} -> std::same_as<void>;
// --- std::string types ---
{
// For input we are forced to provide strings, as we can resize them
archive.Map(std::string_view(), str)
} -> std::same_as<void>;
{
// TO-DO: implement dynamic strings for input (maybe string-hash matching?)
archive.Map(std::string_view(), sv)
} -> std::same_as<void>;
// --- Array types ---
{
// PopArray receives the output of PushArray
archive.PopArray(archive.PushArray(std::string_view(), size))
};
// --- Object types ---
{
// PopArray receives the output of PushArray
archive.PopObject(archive.PushObject(std::string_view()))
};
// Access array element by indices
// --- Arithmetic types ---
{
archive.Map(size_t(), b8)
} -> std::same_as<void>;
{
archive.Map(size_t(), c8)
} -> std::same_as<void>;
{
archive.Map(size_t(), u16)
} -> std::same_as<void>;
{
archive.Map(size_t(), u32)
} -> std::same_as<void>;
{
archive.Map(size_t(), u64)
} -> std::same_as<void>;
{
archive.Map(size_t(), i16)
} -> std::same_as<void>;
{
archive.Map(size_t(), i32)
} -> std::same_as<void>;
{
archive.Map(size_t(), i64)
} -> std::same_as<void>;
{
archive.Map(size_t(), f32)
} -> std::same_as<void>;
{
archive.Map(size_t(), f64)
} -> std::same_as<void>;
// --- std::string types ---
{
// For input we are forced to provide strings, as we can resize them
archive.Map(size_t(), str)
} -> std::same_as<void>;
{
// TO-DO: implement dynamic strings for input (maybe string-hash matching?)
archive.Map(size_t(), sv)
} -> std::same_as<void>;
// --- Array types ---
{
// PopArray receives the output of PushArray
archive.PopArray(archive.PushArray(size_t(), size))
};
// --- Object types ---
{
// PopArray receives the output of PushArray
archive.PopObject(archive.PushObject(size_t()))
};
};
// Extractor-like archive, that extracts schema out of the given class
template<typename TArchive>
concept TInspectorConcept = requires(TArchive& inspector, MetaField&& metaField, void* rootPtr)
{
{
inspector.emplace_back(std::move(metaField))
};
};
template<typename Archive, typename T>
inline void ReflectField(Archive& archive, std::string_view name, T& value)
{
static constexpr bool IsDOMArchive = TDOMArchiveConcept<Archive>;
static constexpr bool IsStreamArchive = TStreamArchiveConcept<Archive>;
static constexpr bool IsInspector = TInspectorConcept<Archive>;
static_assert(IsDOMArchive || IsStreamArchive || IsInspector, "Provided archive does not correspond to DOM or Stream interface.");
if constexpr (IsInspector)
{
InspectInternal(archive, value, name);
}
else if constexpr (IsDOMArchive)
{
ReflectInternal(archive, value, name);
}
else
{
ReflectInternal(archive, value);
}
}
template<typename Archive, typename T>
inline void ReflectIndex(Archive& archive, size_t index, T& value)
{
static constexpr bool IsDOMArchive = TDOMArchiveConcept<Archive>;
static constexpr bool IsStreamArchive = TStreamArchiveConcept<Archive>;
static_assert(IsDOMArchive || IsStreamArchive, "Provided archive does not correspond to DOM or Stream interface.");
if constexpr (IsDOMArchive)
{
ReflectInternal(archive, value, index);
}
else
{
ReflectInternal(archive, value);
}
}
template<typename Archive, typename T>
inline void Serialize(Archive& archive, T& value)
{
static_assert(IsClassType<T>, "Provided type is not a class.");
static constexpr bool IsDOMArchive = TDOMArchiveConcept<Archive>;
static constexpr bool IsStreamArchive = TStreamArchiveConcept<Archive>;
static constexpr bool IsInspector = TInspectorConcept<Archive>;
static_assert(IsDOMArchive || IsStreamArchive || IsInspector, "Provided archive does not correspond to DOM or Stream interface.");
using hqmeta::Reflect;
if constexpr (requires { Reflect(archive, value); })
{
Reflect(archive, value);
}
else
{
static_assert(requires { value.Reflect(archive); }, "Provided type does not implement metadata using HQMETA macro.");
value.Reflect(archive);
}
}
template<typename Archive, typename T>
inline void Serialize(Archive& archive, std::unique_ptr<T>& value)
{
Serialize(archive, *value.get());
}
template<TInspectorConcept Archive, typename T>
inline void InspectInternal(Archive& archive, T& value, std::string_view name)
{
if constexpr (IsArrayType<T>)
{
using TValue = decltype(value[0]);
constexpr auto typeName = hq::GetTypeName<TValue>();
if constexpr (TArrayConcept<T>)
{
MetaField field(typeName, name, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&value)), sizeof(T), EMetaField::TArray);
archive.emplace_back(std::move(field));
}
else
{
MetaField field(typeName, name, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&value)), sizeof(T), EMetaField::CArray);
archive.emplace_back(std::move(field));
}
}
else
{
constexpr auto typeName = hq::GetTypeName<T>();
MetaField field(typeName, name, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&value)), sizeof(T));
archive.emplace_back(std::move(field));
}
}
template<typename T>
inline void ExtractSchema(MetaRegistry& registry)
{
using TFieldInspector = std::vector<MetaField>;
TFieldInspector fields;
// YES
Serialize(fields, *reinterpret_cast<T*>(nullptr));
MetaClass metaClass{
hq::GetTypeName<T>(),
sizeof(T),
alignof(T),
std::move(fields),
VTable::Make<T>()
};
registry.AppendClass(std::move(metaClass));
}
///// ------------------------- JSON
class OutputJSONArchiveImpl
{
private:
using JSON = nlohmann::json;
std::unique_ptr<JSON> m_root;
JSON* m_json;
public:
template<typename T>
requires(!IsStringType<T>)
void Map(std::string_view name, T& value)
{
(*m_json)[name] = value;
}
void Map(std::string_view name, std::string_view value)
{
(*m_json)[name] = value;
}
void Map(std::string_view name, std::string& value)
{
(*m_json)[name] = value;
}
template<typename T>
requires(!IsStringType<T>)
void Map(size_t index, T& value)
{
(*m_json)[index] = value;
}
void Map(size_t index, std::string_view value)
{
(*m_json)[index] = value;
}
void Map(size_t index, std::string& value)
{
(*m_json)[index] = value;
}
OutputJSONArchiveImpl()
: m_root(std::make_unique<JSON>())
, m_json(m_root.get())
{
}
JSON* PushObject(std::string_view name)
{
JSON* oldValue = m_json;
JSON* json = &(*m_json)[name];
(*json) = JSON::object();
m_json = json;
return oldValue;
}
JSON* PushObject(size_t index)
{
JSON* oldValue = m_json;
(*m_json)[index] = JSON::object();
m_json = &m_json->back();
return oldValue;
}
void PopObject(JSON* obj)
{
m_json = obj;
}
JSON* PushArray(std::string_view name, size_t& outSize)
{
UNREFERENCED(outSize);
JSON* oldValue = m_json;
JSON& json = (*m_json)[name];
json = JSON::array();
m_json = &json;
return oldValue;
}
JSON* PushArray(size_t index, size_t& outSize)
{
UNREFERENCED(outSize);
JSON* oldValue = m_json;
(*m_json)[index] = JSON::array();
m_json = &m_json->back();
return oldValue;
}
void PopArray(JSON* obj)
{
m_json = obj;
}
std::vector<u8> Dump()
{
std::vector<u8> data;
auto str = m_json->dump();
data.resize(str.size());
memcpy(const_cast<u8*>(data.data()), str.data(), str.size());
return data;
}
std::string ToString() const
{
std::string data;
auto str = m_json->dump();
data.resize(str.size());
memcpy(const_cast<c8*>(data.data()), str.data(), str.size());
return data;
}
};
class InputJSONArchiveImpl
{
private:
using JSON = nlohmann::json;
std::unique_ptr<JSON> m_root;
JSON* m_json;
public:
template<typename T>
requires(!IsStringType<T>)
void Map(std::string_view name, T& value)
{
if (!m_json)
{
return;
}
if (m_json->is_null())
{
return;
}
if (m_json->is_array())
{
return;
}
if (m_json->contains(name))
{
value = m_json->at(name).get<T>();
}
}
void Map(std::string_view name, std::string& value)
{
if (m_json == nullptr || m_json->is_null())
{
value.resize(0);
return;
}
std::string_view out{};
if (m_json->contains(name))
{
out = m_json->at(name).get<std::string_view>();
}
value = out;
}
void Map(std::string_view name, std::string_view value)
{
}
template<typename T>
requires(!IsStringType<T>)
void Map(size_t index, T& value)
{
if (!m_json)
{
return;
}
if (m_json->is_null())
{
return;
}
if (!m_json->is_array())
{
return;
}
if (index < m_json->size())
{
value = (*m_json)[index].get<T>();
}
}
void Map(size_t index, std::string& value)
{
if (m_json == nullptr || m_json->is_null())
{
value.resize(0);
return;
}
std::string_view out{};
if (index < m_json->size())
{
out = (*m_json)[index].get<std::string_view>();
}
value = out;
}
void Map(size_t index, std::string_view value)
{
if (m_json == nullptr || m_json->is_null())
{
return;
}
std::string_view out{};
if (index < m_json->size())
{
out = (*m_json)[index].get<std::string_view>();
}
value = out;
}
InputJSONArchiveImpl(std::string_view data)
: m_root(std::make_unique<JSON>(JSON::parse(data)))
, m_json(m_root.get())
{}
JSON* PushObject(size_t index)
{
JSON* oldValue = m_json;
if (m_json == nullptr || m_json->is_null())
{
return oldValue;
}
if (index < m_json->size())
{
m_json = &(*m_json)[index];
}
else
{
m_json = nullptr;
}
return oldValue;
}
JSON* PushArray(size_t index, size_t& outSize)
{
JSON* oldValue = m_json;
if (m_json == nullptr || m_json->is_null())
{
return oldValue;
}
if (index < m_json->size())
{
m_json = &(*m_json)[index];
}
else
{
m_json = nullptr;
}
if (m_json && m_json->is_array())
{
outSize = m_json->size();
}
return oldValue;
}
JSON* PushObject(std::string_view name)
{
JSON* oldValue = m_json;
if (m_json == nullptr || m_json->is_null())
{
return oldValue;
}
if (m_json->contains(name))
{
m_json = &(*m_json)[name];
}
else
{
m_json = nullptr;
}
return oldValue;
}
void PopObject(JSON* obj)
{
m_json = obj;
}
JSON* PushArray(std::string_view name, size_t& outSize)
{
JSON* oldValue = m_json;
if (m_json == nullptr || m_json->is_null())
{
return oldValue;
}
if (m_json->contains(name))
{
m_json = &(*m_json)[name];
}
else
{
m_json = nullptr;
}
if (m_json && m_json->is_array())
{
outSize = m_json->size();
}
return oldValue;
}
void PopArray(JSON* obj)
{
m_json = obj;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment