nf7/nf7.hh

502 lines
13 KiB
C++

#pragma once
#include <cstdint>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include <source_location.hh>
#include <yas/serialize.hpp>
namespace nf7 {
class Exception;
class File;
class Context;
class Env;
class SerializerStream;
class Serializer;
class Deserializer;
class Exception : public std::nested_exception {
public:
Exception() = delete;
Exception(std::string_view msg, std::source_location loc = std::source_location::current()) noexcept :
nested_exception(), msg_(msg), srcloc_(loc) {
}
virtual ~Exception() = default;
Exception(const Exception&) = default;
Exception(Exception&&) = default;
Exception& operator=(const Exception&) = delete;
Exception& operator=(Exception&&) = delete;
virtual void UpdatePanic() const noexcept;
virtual std::string Stringify() const noexcept;
std::string StringifyRecursive() const noexcept;
const std::string& msg() const noexcept { return msg_; }
const std::source_location& srcloc() const noexcept { return srcloc_; }
std::exception_ptr reason() const noexcept { return nested_ptr(); }
private:
const std::string msg_;
const std::source_location srcloc_;
};
class DeserializeException : public Exception {
public:
using Exception::Exception;
};
class ExpiredException : public Exception {
public:
using Exception::Exception;
};
class File {
public:
struct Event;
class TypeInfo;
class Interface;
class Path;
class NotFoundException;
class NotImplementedException;
using Id = uint64_t;
static const std::map<std::string, const TypeInfo*>& registry() noexcept;
static const TypeInfo& registry(std::string_view);
File() = delete;
File(const TypeInfo&, Env&) noexcept;
virtual ~File() noexcept;
File(const File&) = delete;
File(File&&) = delete;
File& operator=(const File&) = delete;
File& operator=(File&&) = delete;
virtual void Serialize(Serializer&) const noexcept = 0;
virtual std::unique_ptr<File> Clone(Env&) const noexcept = 0;
void MoveUnder(File& parent, std::string_view) noexcept;
void MakeAsRoot() noexcept;
void Isolate() noexcept;
void Touch() noexcept;
void RequestFocus() noexcept;
virtual void Update() noexcept { }
virtual void Handle(const Event&) noexcept { }
virtual File* Find(std::string_view) const noexcept { return nullptr; }
File& FindOrThrow(std::string_view name) const;
File& ResolveOrThrow(const Path&) const;
File& ResolveOrThrow(std::string_view) const;
File& ResolveUpwardOrThrow(const Path&) const;
File& ResolveUpwardOrThrow(std::string_view) const;
virtual Interface* interface(const std::type_info&) noexcept = 0;
Interface& interfaceOrThrow(const std::type_info&);
template <typename T>
T* interface() noexcept { return dynamic_cast<T*>(interface(typeid(T))); }
template <typename T>
T& interfaceOrThrow() { return dynamic_cast<T&>(interfaceOrThrow(typeid(T))); }
Path abspath() const noexcept;
File& ancestorOrThrow(size_t) const;
const TypeInfo& type() const noexcept { return *type_; }
Env& env() const noexcept { return *env_; }
Id id() const noexcept { return id_; }
File* parent() const noexcept { return parent_; }
const std::string& name() const noexcept { return name_; }
private:
const TypeInfo* const type_;
Env* const env_;
Id id_ = 0;
File* parent_ = nullptr;
std::string name_;
bool touch_ = false;
};
struct File::Event final {
public:
enum Type {
kAdd,
kRemove,
kUpdate,
kReqFocus,
};
Id id;
Type type;
};
class File::TypeInfo {
public:
TypeInfo() = delete;
TypeInfo(const std::string& name, std::unordered_set<std::string>&&) noexcept;
~TypeInfo() noexcept;
TypeInfo(const TypeInfo&) = delete;
TypeInfo(TypeInfo&&) = delete;
TypeInfo& operator=(const TypeInfo&) = delete;
TypeInfo& operator=(TypeInfo&&) = delete;
virtual std::unique_ptr<File> Deserialize(Deserializer&) const = 0;
virtual std::unique_ptr<File> Create(Env&) const = 0;
virtual void UpdateTooltip() const noexcept = 0;
const std::string& name() const noexcept { return name_; }
const std::unordered_set<std::string>& flags() const noexcept { return flags_; }
private:
const std::string name_;
const std::unordered_set<std::string> flags_;
};
class File::Interface {
public:
Interface() = default;
virtual ~Interface() = default;
Interface(const Interface&) = default;
Interface(Interface&&) = default;
Interface& operator=(const Interface&) = default;
Interface& operator=(Interface&&) = default;
};
class File::Path {
public:
Path() = default;
Path(std::initializer_list<std::string> terms) noexcept :
terms_(terms.begin(), terms.end()) {
}
Path(std::vector<std::string>&& terms) noexcept : terms_(std::move(terms)) {
}
Path(const Path&) = default;
Path(Path&&) = default;
Path& operator=(const Path&) = default;
Path& operator=(Path&&) = default;
bool operator==(const Path& p) const noexcept { return terms_ == p.terms_; }
bool operator!=(const Path& p) const noexcept { return terms_ != p.terms_; }
Path(Deserializer&);
void Serialize(Serializer&) const noexcept;
static Path Parse(std::string_view);
std::string Stringify() const noexcept;
static void ValidateTerm(std::string_view);
void Validate() const;
std::span<const std::string> terms() const noexcept { return terms_; }
const std::string& terms(size_t i) const noexcept { return terms_[i]; }
private:
std::vector<std::string> terms_;
};
class File::NotFoundException : public Exception {
public:
using Exception::Exception;
};
class File::NotImplementedException : public Exception {
public:
using Exception::Exception;
};
class Context {
public:
Context() = delete;
Context(File&, const std::shared_ptr<Context>& = nullptr) noexcept;
Context(Env&, File::Id, const std::shared_ptr<Context>& = nullptr) noexcept;
virtual ~Context() noexcept;
virtual void CleanUp() noexcept { }
virtual void Abort() noexcept { }
virtual size_t GetMemoryUsage() const noexcept { return 0; }
virtual std::string GetDescription() const noexcept { return ""; }
Env& env() const noexcept { return *env_; }
File::Id initiator() const noexcept { return initiator_; }
std::shared_ptr<Context> parent() const noexcept { return parent_.lock(); }
size_t depth() const noexcept { return depth_; }
private:
Env* const env_;
const File::Id initiator_;
const std::weak_ptr<Context> parent_;
const size_t depth_;
};
class Env {
public:
using Clock = std::chrono::system_clock;
using Time = Clock::time_point;
using Task = std::function<void()>;
enum Executor { kMain, kSub, kAsync, kGL, };
class Watcher;
Env() = delete;
Env(const std::filesystem::path& npath) noexcept : npath_(npath) {
}
virtual ~Env() = default;
Env(const Env&) = delete;
Env(Env&&) = delete;
Env& operator=(const Env&) = delete;
Env& operator=(Env&&) = delete;
// thread-safe
virtual void Exec(Executor, const std::shared_ptr<Context>&, Task&&, Time = Time::min()) noexcept = 0;
void ExecMain(const std::shared_ptr<Context>& ctx, Task&& task) noexcept {
Exec(kMain, ctx, std::move(task));
}
void ExecSub(const std::shared_ptr<Context>& ctx, Task&& task, Time t = Time::min()) noexcept {
Exec(kSub, ctx, std::move(task), t);
}
void ExecAsync(const std::shared_ptr<Context>& ctx, Task&& task, Time t = Time::min()) noexcept {
Exec(kAsync, ctx, std::move(task), t);
}
void ExecGL(const std::shared_ptr<Context>& ctx, Task&& task, Time t = Time::min()) noexcept {
Exec(kGL, ctx, std::move(task), t);
}
// thread-safe
virtual void Exit() noexcept = 0;
virtual void Save() noexcept = 0;
virtual void Throw(std::exception_ptr&&) noexcept = 0;
// returns the target file if alive
virtual nf7::File* Handle(const File::Event&) noexcept = 0;
virtual File* GetFile(File::Id) const noexcept = 0;
File& GetFileOrThrow(File::Id) const;
const std::filesystem::path& npath() const noexcept { return npath_; }
protected:
friend class nf7::File;
virtual File::Id AddFile(File&) noexcept = 0;
virtual void RemoveFile(File::Id) noexcept = 0;
friend class nf7::Context;
virtual void AddContext(nf7::Context&) noexcept = 0;
virtual void RemoveContext(nf7::Context&) noexcept = 0;
virtual void AddWatcher(File::Id, Watcher&) noexcept = 0;
virtual void RemoveWatcher(File::Id, Watcher&) noexcept = 0;
private:
std::filesystem::path npath_;
};
class Env::Watcher {
public:
Watcher(Env&) noexcept;
virtual ~Watcher() noexcept;
Watcher(const Watcher&) = delete;
Watcher(Watcher&&) = delete;
Watcher& operator=(const Watcher&) = delete;
Watcher& operator=(Watcher&&) = delete;
void Watch(File::Id) noexcept;
void Unwatch(File::Id) noexcept;
virtual void Handle(const File::Event&) noexcept = 0;
const std::vector<File::Id>& targets() const noexcept { return targets_; }
private:
Env* const env_;
std::vector<File::Id> targets_;
};
class SerializerStream final {
public:
SerializerStream(const char* path, const char* mode);
~SerializerStream() noexcept {
std::fclose(fp_);
}
// serializer requires write/flush
size_t write(const void* ptr, size_t size) {
const auto ret = static_cast<size_t>(std::fwrite(ptr, 1, size, fp_));
off_ += ret;
return ret;
}
void flush() {
std::fflush(fp_);
}
// deserializer requires read/available/empty/peekch/getch/ungetch
size_t read(void* ptr, size_t size) {
const auto ret = static_cast<size_t>(std::fread(ptr, 1, size, fp_));
off_ += ret;
return ret;
}
size_t available() const {
return size_ - off_;
}
bool empty() const {
return available() == 0;
}
char peekch() const {
const auto c = std::getc(fp_);
std::ungetc(c, fp_);
return static_cast<char>(c);
}
char getch() {
return static_cast<char>(std::getc(fp_));
}
void ungetch(char c) {
std::ungetc(c, fp_);
}
void Seek(size_t off) {
if (0 != std::fseek(fp_, static_cast<long int>(off), SEEK_SET)) {
throw nf7::Exception {"failed to seek"};
}
off_ = off;
}
size_t offset() const noexcept { return off_; }
private:
std::FILE* fp_;
size_t off_;
size_t size_;
};
class Serializer final :
public yas::detail::binary_ostream<nf7::SerializerStream, yas::binary>,
public yas::detail::oarchive_header<yas::binary> {
public:
using this_type = Serializer;
class ChunkGuard final {
public:
ChunkGuard(nf7::Serializer&);
ChunkGuard(nf7::Serializer& ar, nf7::Env&) : ChunkGuard(ar) { }
~ChunkGuard() noexcept; // use Env::Throw to handle errors
private:
Serializer* const ar_;
size_t begin_;
};
static void Save(nf7::Env& env, const char* path, auto& v) {
SerializerStream st {path, "wb"};
Serializer ar {env, st};
ar(v);
}
Serializer(nf7::Env& env, nf7::SerializerStream& st) :
binary_ostream(st), oarchive_header(st), env_(&env), st_(&st) {
}
template<typename T>
Serializer& operator& (const T& v) {
return yas::detail::serializer<
yas::detail::type_properties<T>::value,
yas::detail::serialization_method<T, this_type>::value, yas::binary, T>::
save(*this, v);
}
Serializer& serialize() {
return *this;
}
template<typename Head, typename... Tail>
Serializer& serialize(const Head& head, const Tail&... tail) {
return operator& (head).serialize(tail...);
}
template<typename... Args>
Serializer& operator()(const Args&... args) {
return serialize(args...);
}
private:
nf7::Env* const env_;
nf7::SerializerStream* const st_;
};
class Deserializer final :
public yas::detail::binary_istream<nf7::SerializerStream, yas::binary>,
public yas::detail::iarchive_header<yas::binary> {
public:
using this_type = Deserializer;
class ChunkGuard final {
public:
ChunkGuard(Deserializer& ar);
ChunkGuard(Deserializer&, nf7::Env&);
~ChunkGuard() noexcept; // use Env::Throw to handle errors
void ValidateEnd();
private:
Deserializer* const ar_;
size_t expect_;
size_t begin_;
nf7::Env* env_prev_ = nullptr;
};
static void Load(nf7::Env& env, const char* path, auto& v) {
try {
SerializerStream st {path, "rb"};
Deserializer ar {env, st};
ar(v);
} catch (nf7::Exception&) {
throw;
} catch (std::exception&) {
throw nf7::Exception {"deserialization failure"};
}
}
Deserializer(nf7::Env& env, SerializerStream& st) :
binary_istream(st), iarchive_header(st), env_(&env), st_(&st) {
}
template<typename T>
Deserializer& operator& (T&& v) {
using RealType =
typename std::remove_reference<typename std::remove_const<T>::type>::type;
return yas::detail::serializer<
yas::detail::type_properties<RealType>::value,
yas::detail::serialization_method<RealType, Deserializer>::value,
yas::binary, RealType>
::load(*this, v);
}
Deserializer& serialize() {
return *this;
}
template<typename Head, typename... Tail>
Deserializer& serialize(Head&& head, Tail&&... tail) {
return operator& (std::forward<Head>(head)).serialize(std::forward<Tail>(tail)...);
}
template<typename... Args>
Deserializer& operator()(Args&& ... args) {
return serialize(std::forward<Args>(args)...);
}
Env& env() const noexcept { return *env_; }
private:
nf7::Env* env_;
nf7::SerializerStream* const st_;
};
} // namespace nf7