502 lines
13 KiB
C++
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
|