merge conflict resolved

fb-doc-catchup
Claudius Holeksa 2021-12-28 11:28:15 +01:00
commit c0b2712476
48 changed files with 4672 additions and 3733 deletions

View File

@ -112,9 +112,6 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseTab: ForContinuationAndIndentation
...

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Claudius "keldu" Holeksa
Copyright (c) 2020,2021 Claudius "keldu" Holeksa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -2,17 +2,19 @@
Asynchronous framework mostly inspired by [Capn'Proto](https://github.com/capnproto/capnproto) with the key difference of not
using Promises, but more reusable Pipelines/Conveyors. This introduces some challenges since I can't assume that only one
element gets passed along the chain, but it is managable.
element gets passed along the chain, but it is managable. The advantage is that you have zero heap overhead by not recreating the chain after every use.
Very early stage. I am currently rewriting a lot of my software to use this library.
Very early stage. I am currently rewriting my software to find a good interface solution by checking if I am comfortable with the current design.
# Dependencies
You will need
* A compiler (g++/clang++)
* A compiler (std=c++20) (g++/clang++)
* scons
* gnutls
Currently the build script explicitly calls clang++ due to some occasional compiler error on archlinux with g++.
Optional dependencies are
* clang-format
@ -26,15 +28,50 @@ It's that simple.
`scons format` formats the sources.
`scons install` installs the library + headers locally.
# Schema Structure
Message description currently is achieved by a series of templated schema classes found in ```kelgin/schema.h``` as seen below
```
using BasicStruct = schema::Struct<
schema::NamedMember<Int32, "foo">,
schema::NamedMember<String, "bar">
>;
```
These schema classes are just meant to describe the schema itself. By itself, it can't do anything.
For a message we create an instance of any MessageRoot class such as `HeapMessageRoot`.
Using those schemas and appropriate container classes, we can now build a message class
```
HeapMessageRoot<BasicStruct, MessageContainer<BasicStruct>> buildBasicMessage(){
auto root = heapMessageRoot<BasicStruct>();
// This is equivalent to
// auto root = heapMessageRoot<BasicStruct, MessageContainer<BasicStruct>>();
auto builder = root.build();
auto bar_build = builder.init<"bar">().set("banana");
auto foo_build = builder.init<"foo">().set(5);
return root;
}
```
The current default message container stores each value in stl containers as `std::string`, `std::vector`, `std::tuple`, `std::variant`
or in its primitive form.
Though it is planned to allow storing those directly in buffers.
# Examples
Currently no examples except in test.
But [kelgin-graphics](https://github.com/keldu/kelgin-graphics) contains some programs which heavily use
this library. Though no schema or io features are used there.
# Roadmap
* Zerocopy for message templates during parsing
* Tls with gnutls
* Tls with gnutls (Client side partly done. Server side missing)
* Windows/Mac Support
* Buffer flexibility
* Multithreaded conveyor communication
* Logger implementation
* Reintroduce JSON without dynamic message parsing or at least with more streaming support

View File

@ -29,19 +29,25 @@ def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, ta
sources.append( self.StaticObject( target=target_name, source=path ) )
pass
env=Environment(CPPPATH=['#source','#','#driver'],
CXX='c++',
env=Environment(ENV=os.environ, CPPPATH=['#source/kelgin','#source','#','#driver'],
CXX='clang++',
CPPDEFINES=['GIN_UNIX'],
CXXFLAGS=['-std=c++17','-g','-Wall','-Wextra'],
LIBS=[])
CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
LIBS=['gnutls'])
env.__class__.add_source_files = add_kel_source_files
env.objects = []
env.sources = []
env.headers = []
env.objects = []
env.tls_sources = []
env.tls_headers = []
env.driver_sources = []
env.driver_headers = []
Export('env')
SConscript('source/SConscript')
SConscript('source/kelgin/SConscript')
SConscript('driver/SConscript')
# Library build
@ -49,11 +55,11 @@ SConscript('driver/SConscript')
env_library = env.Clone()
env.objects_shared = []
env_library.add_source_files(env.objects_shared, env.sources, shared=True)
env_library.add_source_files(env.objects_shared, env.sources + env.driver_sources + env.tls_sources, shared=True)
env.library_shared = env_library.SharedLibrary('#bin/kelgin', [env.objects_shared])
env.objects_static = []
env_library.add_source_files(env.objects_static, env.sources)
env_library.add_source_files(env.objects_static, env.sources + env.driver_sources + env.tls_sources)
env.library_static = env_library.StaticLibrary('#bin/kelgin', [env.objects_static])
env.Alias('library', [env.library_shared, env.library_static])
@ -75,7 +81,7 @@ def format_iter(env,files):
env.format_actions.append(env.AlwaysBuild(env.ClangFormat(target=f+"-clang-format",source=f)))
pass
format_iter(env,env.sources + env.headers)
format_iter(env,env.sources + env.driver_sources + env.headers + env.driver_headers)
env.Alias('format', env.format_actions)
@ -83,5 +89,7 @@ env.Alias('all', ['format', 'library_shared', 'library_static', 'test'])
env.Install('/usr/local/lib/', [env.library_shared, env.library_static])
env.Install('/usr/local/include/kelgin/', [env.headers])
env.Install('/usr/local/include/kelgin/tls/', [env.tls_headers])
env.Install('/usr/local/include/kelgin/test/', [env.test_headers])
env.Alias('install', '/usr/local/')

View File

@ -9,5 +9,7 @@ Import('env')
dir_path = Dir('.').abspath
env.sources += sorted(glob.glob(dir_path + "/*.cpp"))
env.headers += sorted(glob.glob(dir_path + "/*.h"))
env.driver_sources += sorted(glob.glob(dir_path + "/tls/*.cpp"))
env.driver_sources += sorted(glob.glob(dir_path + "/*.cpp"))
env.driver_headers += sorted(glob.glob(dir_path + "/*.h"))

View File

@ -3,6 +3,7 @@
#include <sstream>
namespace gin {
namespace unix {
IFdOwner::IFdOwner(UnixEventPort &event_port, int file_descriptor, int fd_flags,
uint32_t event_mask)
: event_port{event_port}, file_descriptor{file_descriptor},
@ -17,76 +18,12 @@ IFdOwner::~IFdOwner() {
}
}
void UnixIoStream::readStep() {
if (read_ready) {
read_ready->feed();
}
while (!read_tasks.empty()) {
ReadIoTask &task = read_tasks.front();
ssize_t n = ::read(fd(), task.buffer, task.max_length);
if (n <= 0) {
if (n == 0) {
if (on_read_disconnect) {
on_read_disconnect->feed();
}
break;
}
int error = errno;
if (error == EAGAIN || error == EWOULDBLOCK) {
break;
} else {
if (read_done) {
read_done->fail(criticalError("Read failed"));
}
read_tasks.pop();
}
} else if (static_cast<size_t>(n) >= task.min_length &&
static_cast<size_t>(n) <= task.max_length) {
if (read_done) {
read_done->feed(static_cast<size_t>(n));
}
size_t max_len = task.max_length;
read_tasks.pop();
} else {
task.buffer = reinterpret_cast<uint8_t *>(task.buffer) + n;
task.min_length -= static_cast<size_t>(n);
task.max_length -= static_cast<size_t>(n);
}
}
ssize_t unixRead(int fd, void *buffer, size_t length) {
return ::recv(fd, buffer, length, 0);
}
void UnixIoStream::writeStep() {
if (write_ready) {
write_ready->feed();
}
while (!write_tasks.empty()) {
WriteIoTask &task = write_tasks.front();
ssize_t n = ::write(fd(), task.buffer, task.length);
if (n < 0) {
int error = errno;
if (error == EAGAIN || error == EWOULDBLOCK) {
break;
} else {
if (write_done) {
write_done->fail(criticalError("Write failed"));
}
write_tasks.pop();
}
} else if (static_cast<size_t>(n) == task.length) {
if (write_done) {
write_done->feed(static_cast<size_t>(task.length));
}
write_tasks.pop();
} else {
task.buffer = reinterpret_cast<const uint8_t *>(task.buffer) +
static_cast<size_t>(n);
task.length -= static_cast<size_t>(n);
}
}
ssize_t unixWrite(int fd, const void *buffer, size_t length) {
return ::send(fd, buffer, length, 0);
}
UnixIoStream::UnixIoStream(UnixEventPort &event_port, int file_descriptor,
@ -94,18 +31,15 @@ UnixIoStream::UnixIoStream(UnixEventPort &event_port, int file_descriptor,
: IFdOwner{event_port, file_descriptor, fd_flags, event_mask | EPOLLRDHUP} {
}
void UnixIoStream::read(void *buffer, size_t min_length, size_t max_length) {
bool is_ready = read_tasks.empty();
read_tasks.push(ReadIoTask{buffer, min_length, max_length});
if (is_ready) {
readStep();
ErrorOr<size_t> UnixIoStream::read(void *buffer, size_t length) {
ssize_t read_bytes = unixRead(fd(), buffer, length);
if (read_bytes > 0) {
return static_cast<size_t>(read_bytes);
} else if (read_bytes == 0) {
return criticalError("Disconnected", Error::Code::Disconnected);
}
}
Conveyor<size_t> UnixIoStream::readDone() {
auto caf = newConveyorAndFeeder<size_t>();
read_done = std::move(caf.feeder);
return std::move(caf.conveyor);
return recoverableError("Currently busy");
}
Conveyor<void> UnixIoStream::readReady() {
@ -120,18 +54,19 @@ Conveyor<void> UnixIoStream::onReadDisconnected() {
return std::move(caf.conveyor);
}
void UnixIoStream::write(const void *buffer, size_t length) {
bool is_ready = write_tasks.empty();
write_tasks.push(WriteIoTask{buffer, length});
if (is_ready) {
writeStep();
ErrorOr<size_t> UnixIoStream::write(const void *buffer, size_t length) {
ssize_t write_bytes = unixWrite(fd(), buffer, length);
if (write_bytes > 0) {
return static_cast<size_t>(write_bytes);
}
}
Conveyor<size_t> UnixIoStream::writeDone() {
auto caf = newConveyorAndFeeder<size_t>();
write_done = std::move(caf.feeder);
return std::move(caf.conveyor);
int error = errno;
if (error == EAGAIN || error == EWOULDBLOCK) {
return recoverableError("Currently busy");
}
return criticalError("Disconnected", Error::Code::Disconnected);
}
Conveyor<void> UnixIoStream::writeReady() {
@ -142,11 +77,15 @@ Conveyor<void> UnixIoStream::writeReady() {
void UnixIoStream::notify(uint32_t mask) {
if (mask & EPOLLOUT) {
writeStep();
if (write_ready) {
write_ready->feed();
}
}
if (mask & EPOLLIN) {
readStep();
if (read_ready) {
read_ready->feed();
}
}
if (mask & EPOLLRDHUP) {
@ -208,26 +147,31 @@ Own<Server> UnixNetworkAddress::listen() {
::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
addresses.front().bind(fd);
bool failed = addresses.front().bind(fd);
if (failed) {
return nullptr;
}
::listen(fd, SOMAXCONN);
return heap<UnixServer>(event_port, fd, 0);
}
Own<IoStream> UnixNetworkAddress::connect() {
Conveyor<Own<IoStream>> UnixNetworkAddress::connect() {
assert(addresses.size() > 0);
if (addresses.size() == 0) {
return nullptr;
return Conveyor<Own<IoStream>>{criticalError("No address found")};
}
int fd = addresses.front().socket(SOCK_STREAM);
if (fd < 0) {
return nullptr;
return Conveyor<Own<IoStream>>{criticalError("Couldn't open socket")};
}
Own<UnixIoStream> io_stream =
heap<UnixIoStream>(event_port, fd, 0, EPOLLIN | EPOLLOUT);
bool success = false;
for (auto iter = addresses.begin(); iter != addresses.end(); ++iter) {
int status = ::connect(fd, iter->getRaw(), iter->getRawLength());
if (status < 0) {
@ -237,52 +181,57 @@ Own<IoStream> UnixNetworkAddress::connect() {
* But edge triggered epolling means that it'll
* be ready when the signal is triggered
*/
/// @todo Add limit node when implemented
if (error == EINPROGRESS) {
Conveyor<void> write_ready = io_stream->writeReady();
break;
/*
* Future function return
Conveyor<void> write_ready = io_stream->writeReady();
return write_ready.then(
[io_stream{std::move(io_stream)}]() mutable {
io_stream->write_ready = nullptr;
return std::move(io_stream);
[ios{std::move(io_stream)}]() mutable {
ios->write_ready = nullptr;
return std::move(ios);
});
*/
success = true;
break;
} else if (error != EINTR) {
return nullptr;
/// @todo Push error message from
return Conveyor<Own<IoStream>>{
criticalError("Couldn't connect")};
}
} else {
success = true;
break;
}
}
return io_stream;
// @todo change function into a safe return type.
// return Conveyor<Own<IoStream>>{std::move(io_stream)};
if (!success) {
return criticalError("Couldn't connect");
}
return Conveyor<Own<IoStream>>{std::move(io_stream)};
}
std::string UnixNetworkAddress::toString() const {
std::ostringstream oss;
oss << "Address: " << path;
if (port_hint > 0) {
oss << "\nPort: " << port_hint;
try {
std::ostringstream oss;
oss << "Address: " << path;
if (port_hint > 0) {
oss << "\nPort: " << port_hint;
}
return oss.str();
} catch (std::bad_alloc &) {
return {};
}
return oss.str();
}
const std::string &UnixNetworkAddress::address() const { return path; }
UnixAsyncIoProvider::UnixAsyncIoProvider(UnixEventPort &port_ref,
Own<EventPort> &&port)
: event_port{port_ref}, event_loop{std::move(port)}, wait_scope{
event_loop} {}
uint16_t UnixNetworkAddress::port() const { return port_hint; }
Own<NetworkAddress> UnixAsyncIoProvider::parseAddress(const std::string &path,
uint16_t port_hint) {
UnixEventPort *port =
reinterpret_cast<UnixEventPort *>(event_loop.eventPort());
if (!port) {
return nullptr;
}
UnixNetwork::UnixNetwork(UnixEventPort &event) : event_port{event} {}
Conveyor<Own<NetworkAddress>> UnixNetwork::parseAddress(const std::string &path,
uint16_t port_hint) {
std::string_view addr_view{path};
{
std::string_view begins_with = "unix:";
@ -294,26 +243,40 @@ Own<NetworkAddress> UnixAsyncIoProvider::parseAddress(const std::string &path,
std::list<SocketAddress> addresses =
SocketAddress::parse(addr_view, port_hint);
return heap<UnixNetworkAddress>(*port, *this, path, port_hint,
std::move(addresses));
return Conveyor<Own<NetworkAddress>>{heap<UnixNetworkAddress>(
event_port, path, port_hint, std::move(addresses))};
}
Own<InputStream> UnixAsyncIoProvider::wrapInputFd(int fd) {
UnixIoProvider::UnixIoProvider(UnixEventPort &port_ref, Own<EventPort> port)
: event_port{port_ref}, event_loop{std::move(port)}, unix_network{
port_ref} {}
Own<InputStream> UnixIoProvider::wrapInputFd(int fd) {
return heap<UnixIoStream>(event_port, fd, 0, EPOLLIN);
}
EventLoop &UnixAsyncIoProvider::eventLoop() { return event_loop; }
WaitScope &UnixAsyncIoProvider::waitScope() { return wait_scope; }
AsyncIoContext setupAsyncIo() {
Own<UnixEventPort> prt = heap<UnixEventPort>();
UnixEventPort &prt_ref = *prt;
Own<UnixAsyncIoProvider> io_provider =
heap<UnixAsyncIoProvider>(prt_ref, std::move(prt));
EventLoop &event_loop = io_provider->eventLoop();
WaitScope &wait_scope = io_provider->waitScope();
return {std::move(io_provider), prt_ref, wait_scope};
Network &UnixIoProvider::network() {
return static_cast<Network &>(unix_network);
}
} // namespace gin
EventLoop &UnixIoProvider::eventLoop() { return event_loop; }
} // namespace unix
ErrorOr<AsyncIoContext> setupAsyncIo() {
using namespace unix;
try {
Own<UnixEventPort> prt = heap<UnixEventPort>();
UnixEventPort &prt_ref = *prt;
Own<UnixIoProvider> io_provider =
heap<UnixIoProvider>(prt_ref, std::move(prt));
EventLoop &loop_ref = io_provider->eventLoop();
return {{std::move(io_provider), loop_ref, prt_ref}};
} catch (std::bad_alloc &) {
return criticalError("Out of memory");
}
}
} // namespace gin

View File

@ -7,6 +7,7 @@
#include <csignal>
#include <sys/signalfd.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/epoll.h>
@ -25,9 +26,11 @@
#include <unordered_map>
#include <vector>
#include "io.h"
#include "./io.h"
#include "kelgin/io.h"
namespace gin {
namespace unix {
constexpr int MAX_EPOLL_EVENTS = 256;
class UnixEventPort;
@ -107,7 +110,6 @@ private:
if (nfds < 0) {
/// @todo error_handling
assert(false);
return false;
}
@ -130,7 +132,7 @@ private:
continue;
}
while (1) {
ssize_t n = ::read(pipefds[0], &i, sizeof(i));
ssize_t n = ::recv(pipefds[0], &i, sizeof(i), 0);
if (n < 0) {
break;
}
@ -169,7 +171,7 @@ public:
event.data.u64 = 0;
::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, signal_fd, &event);
int rc = ::pipe(pipefds);
int rc = ::pipe2(pipefds, O_NONBLOCK | O_CLOEXEC);
if (rc < 0) {
return;
}
@ -232,7 +234,7 @@ public:
return;
}
uint8_t i = 0;
::write(pipefds[1], &i, sizeof(i));
::send(pipefds[1], &i, sizeof(i), MSG_DONTWAIT);
}
void subscribe(IFdOwner &owner, int fd, uint32_t event_mask) {
@ -261,45 +263,41 @@ public:
}
};
ssize_t unixRead(int fd, void *buffer, size_t length);
ssize_t unixWrite(int fd, const void *buffer, size_t length);
class UnixIoStream final : public IoStream, public IFdOwner {
private:
struct WriteIoTask {
const void *buffer;
size_t length;
};
std::queue<WriteIoTask> write_tasks;
Own<ConveyorFeeder<size_t>> write_done = nullptr;
Own<ConveyorFeeder<void>> write_ready = nullptr;
struct ReadIoTask {
void *buffer;
size_t min_length;
size_t max_length;
};
std::queue<ReadIoTask> read_tasks;
Own<ConveyorFeeder<size_t>> read_done = nullptr;
Own<ConveyorFeeder<void>> read_ready = nullptr;
Own<ConveyorFeeder<void>> on_read_disconnect = nullptr;
private:
void readStep();
void writeStep();
Own<ConveyorFeeder<void>> write_ready = nullptr;
public:
UnixIoStream(UnixEventPort &event_port, int file_descriptor, int fd_flags,
uint32_t event_mask);
void read(void *buffer, size_t min_length, size_t max_length) override;
Conveyor<size_t> readDone() override;
ErrorOr<size_t> read(void *buffer, size_t length) override;
Conveyor<void> readReady() override;
Conveyor<void> onReadDisconnected() override;
void write(const void *buffer, size_t length) override;
Conveyor<size_t> writeDone() override;
ErrorOr<size_t> write(const void *buffer, size_t length) override;
Conveyor<void> writeReady() override;
/*
void read(void *buffer, size_t min_length, size_t max_length) override;
Conveyor<size_t> readDone() override;
Conveyor<void> readReady() override;
Conveyor<void> onReadDisconnected() override;
void write(const void *buffer, size_t length) override;
Conveyor<size_t> writeDone() override;
Conveyor<void> writeReady() override;
*/
void notify(uint32_t mask) override;
};
@ -346,12 +344,13 @@ public:
return result;
}
void bind(int fd) const {
bool bind(int fd) const {
if (wildcard) {
int value = 0;
::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
}
::bind(fd, &address.generic, address_length);
int error = ::bind(fd, &address.generic, address_length);
return error < 0;
}
const struct ::sockaddr *getRaw() const { return &address.generic; }
@ -392,39 +391,52 @@ public:
class UnixNetworkAddress final : public NetworkAddress {
private:
UnixEventPort &event_port;
AsyncIoProvider &io_provider;
const std::string path;
uint16_t port_hint;
std::list<SocketAddress> addresses;
public:
UnixNetworkAddress(UnixEventPort &event_port, AsyncIoProvider &io_provider,
const std::string &path, uint16_t port_hint,
std::list<SocketAddress> &&addr)
: event_port{event_port}, io_provider{io_provider}, path{path},
port_hint{port_hint}, addresses{std::move(addr)} {}
UnixNetworkAddress(UnixEventPort &event_port, const std::string &path,
uint16_t port_hint, std::list<SocketAddress> &&addr)
: event_port{event_port}, path{path}, port_hint{port_hint},
addresses{std::move(addr)} {}
Own<Server> listen() override;
Own<IoStream> connect() override;
Conveyor<Own<IoStream>> connect() override;
std::string toString() const override;
const std::string &address() const override;
uint16_t port() const override;
};
class UnixAsyncIoProvider final : public AsyncIoProvider {
class UnixNetwork final : public Network {
private:
UnixEventPort &event_port;
public:
UnixNetwork(UnixEventPort &event_port);
Conveyor<Own<NetworkAddress>> parseAddress(const std::string &address,
uint16_t port_hint = 0) override;
};
class UnixIoProvider final : public IoProvider {
private:
UnixEventPort &event_port;
EventLoop event_loop;
WaitScope wait_scope;
UnixNetwork unix_network;
public:
UnixAsyncIoProvider(UnixEventPort &port_ref, Own<EventPort> &&port);
UnixIoProvider(UnixEventPort &port_ref, Own<EventPort> port);
Own<NetworkAddress> parseAddress(const std::string &,
uint16_t port_hint = 0) override;
Network &network() override;
Own<InputStream> wrapInputFd(int fd) override;
EventLoop &eventLoop();
WaitScope &waitScope();
};
} // namespace unix
} // namespace gin

View File

@ -1,983 +0,0 @@
#pragma once
#include "common.h"
#include "error.h"
#include "timer.h"
#include <functional>
#include <limits>
#include <list>
#include <queue>
#include <type_traits>
namespace gin {
class ConveyorNode {
protected:
Own<ConveyorNode> child;
public:
ConveyorNode();
ConveyorNode(Own<ConveyorNode> &&child);
virtual ~ConveyorNode() = default;
virtual void getResult(ErrorOrValue &err_or_val) = 0;
};
class EventLoop;
/*
* Event class inspired directly by capn'proto.
* https://github.com/capnproto/capnproto
*/
class Event {
private:
EventLoop &loop;
Event **prev = nullptr;
Event *next = nullptr;
friend class EventLoop;
public:
Event();
Event(EventLoop &loop);
virtual ~Event();
virtual void fire() = 0;
void armNext();
void armLater();
void armLast();
void disarm();
bool isArmed() const;
};
class ConveyorStorage : public Event {
protected:
ConveyorStorage *parent = nullptr;
public:
virtual ~ConveyorStorage() = default;
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
virtual void childFired() = 0;
void setParent(ConveyorStorage *parent);
};
class ConveyorBase {
protected:
Own<ConveyorNode> node;
ConveyorStorage *storage;
public:
ConveyorBase(Own<ConveyorNode> &&node_p,
ConveyorStorage *storage_p = nullptr);
virtual ~ConveyorBase() = default;
ConveyorBase(ConveyorBase &&) = default;
ConveyorBase &operator=(ConveyorBase &&) = default;
void get(ErrorOrValue &err_or_val);
};
template <typename T> class Conveyor;
template <typename T> Conveyor<T> chainedConveyorType(T *);
template <typename T> Conveyor<T> chainedConveyorType(Conveyor<T> *);
template <typename T>
using ChainedConveyors = decltype(chainedConveyorType((T *)nullptr));
template <typename Func, typename T>
using ConveyorResult = ChainedConveyors<ReturnType<Func, T>>;
struct PropagateError {
public:
Error operator()(const Error &error) const;
Error operator()(Error &&error);
};
class SinkConveyor {
private:
Own<ConveyorNode> node;
public:
SinkConveyor(Own<ConveyorNode> &&node);
SinkConveyor(SinkConveyor &&) = default;
SinkConveyor &operator=(SinkConveyor &&) = default;
};
/**
* Main interface for async operations.
*/
template <typename T> class Conveyor : public ConveyorBase {
public:
/**
* Construct a immediately fulfilled node
*/
Conveyor(FixVoid<T> value);
/**
* Construct a conveyor with a child node and the next storage point
*/
Conveyor(Own<ConveyorNode> &&node_p, ConveyorStorage *storage_p);
Conveyor(Conveyor<T> &&) = default;
Conveyor<T> &operator=(Conveyor<T> &&) = default;
/**
* This method converts values or errors from children
*/
template <typename Func, typename ErrorFunc = PropagateError>
ConveyorResult<Func, T> then(Func &&func,
ErrorFunc &&error_func = PropagateError());
/**
* This method adds a buffer node in the conveyor chains and acts as a
* scheduler interrupt point.
*/
Conveyor<T> buffer(size_t limit = std::numeric_limits<size_t>::max());
/**
* This method just takes ownership of any supplied types
*/
template <typename... Args> Conveyor<T> attach(Args &&...args);
/** @todo implement
* This method limits the total amount of passed elements
* Be careful where you place this node into the chain.
*/
Conveyor<T> limit(size_t val = 1);
/**
* Moves the conveyor chain into a thread local storage point which drops
* every element Use sink() if you want to control the lifetime of a chain
*/
template <typename ErrorFunc>
void detach(ErrorFunc &&err_func = PropagateError());
/**
* Creates a local sink which drops elements, but lifetime control remains
* in your hand.
*/
template <typename ErrorFunc>
SinkConveyor sink(ErrorFunc &&error_func = PropagateError());
/**
* If no sink() or detach() is used you have to take elements out of the
* chain yourself.
*/
ErrorOr<FixVoid<T>> take();
/** @todo implement
* Specifically pump elements through this chain
*/
void poll();
// helper
static Conveyor<T> toConveyor(Own<ConveyorNode> &&node,
ConveyorStorage *is_storage = nullptr);
// helper
static std::pair<Own<ConveyorNode>, ConveyorStorage *>
fromConveyor(Conveyor<T> &&conveyor);
};
template <typename T> class ConveyorFeeder {
public:
virtual ~ConveyorFeeder() = default;
virtual void feed(T &&data) = 0;
virtual void fail(Error &&error) = 0;
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
};
template <> class ConveyorFeeder<void> {
public:
virtual ~ConveyorFeeder() = default;
virtual void feed(Void &&value = Void{}) = 0;
virtual void fail(Error &&error) = 0;
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
};
template <typename T> struct ConveyorAndFeeder {
Own<ConveyorFeeder<T>> feeder;
Conveyor<T> conveyor;
};
template <typename T> ConveyorAndFeeder<T> newConveyorAndFeeder();
template <typename T> ConveyorAndFeeder<T> oneTimeConveyorAndFeeder();
enum class Signal : uint8_t { Terminate, User1 };
/**
* Class which acts as a correspondent between the running framework and outside
* events which may be signals from the operating system or just other threads.
* Default EventPorts are supplied by setupAsyncIo() in io.h
*/
class EventPort {
public:
virtual ~EventPort() = default;
virtual Conveyor<void> onSignal(Signal signal) = 0;
virtual void poll() = 0;
virtual void wait() = 0;
virtual void wait(const std::chrono::steady_clock::duration &) = 0;
virtual void wait(const std::chrono::steady_clock::time_point &) = 0;
virtual void wake() = 0;
};
class SinkConveyorNode;
class ConveyorSinks : public Event {
private:
friend class SinkConveyorNode;
void destroySinkConveyorNode(ConveyorNode &sink_node);
void fail(Error &&error);
std::list<Own<ConveyorNode>> sink_nodes;
std::queue<ConveyorNode *> delete_nodes;
std::function<void(Error &&error)> error_handler;
public:
ConveyorSinks() = default;
void add(Conveyor<void> &&node);
void fire() override;
};
/*
* EventLoop class inspired directly by capn'proto.
* https://github.com/capnproto/capnproto
*/
class EventLoop {
private:
friend class Event;
Event *head = nullptr;
Event **tail = &head;
Event **next_insert_point = &head;
Event **later_insert_point = &head;
bool is_runnable = false;
Own<EventPort> event_port = nullptr;
Own<ConveyorSinks> daemon_sink = nullptr;
// functions
void setRunnable(bool runnable);
friend class WaitScope;
void enterScope();
void leaveScope();
bool turnLoop();
bool turn();
public:
EventLoop();
EventLoop(Own<EventPort> &&port);
~EventLoop();
bool wait();
bool wait(const std::chrono::steady_clock::duration &);
bool wait(const std::chrono::steady_clock::time_point &);
bool poll();
EventPort *eventPort();
ConveyorSinks &daemon();
};
/*
* WaitScope class inspired directly by capn'proto.
* https://github.com/capnproto/capnproto
*/
class WaitScope {
private:
EventLoop &loop;
public:
WaitScope(EventLoop &loop);
~WaitScope();
void wait();
void wait(const std::chrono::steady_clock::duration &);
void wait(const std::chrono::steady_clock::time_point &);
void poll();
};
template <typename Func> ConveyorResult<Func, void> yieldNext(Func &&func);
template <typename Func> ConveyorResult<Func, void> yieldLater(Func &&func);
template <typename Func> ConveyorResult<Func, void> yieldLast(Func &&func);
} // namespace gin
// Secret stuff
// Aka private semi hidden classes
namespace gin {
template <typename Out, typename In> struct FixVoidCaller {
template <typename Func> static Out apply(Func &func, In &&in) {
return func(std::move(in));
}
};
template <typename Out> struct FixVoidCaller<Out, Void> {
template <typename Func> static Out apply(Func &func, Void &&in) {
(void)in;
return func();
}
};
template <typename In> struct FixVoidCaller<Void, In> {
template <typename Func> static Void apply(Func &func, In &&in) {
func(std::move(in));
return Void{};
}
};
template <> struct FixVoidCaller<Void, Void> {
template <typename Func> static Void apply(Func &func, Void &&in) {
(void)in;
func();
return Void{};
}
};
template <typename T> class AdaptConveyorNode;
template <typename T>
class AdaptConveyorFeeder : public ConveyorFeeder<UnfixVoid<T>> {
private:
AdaptConveyorNode<T> *feedee = nullptr;
public:
~AdaptConveyorFeeder();
void setFeedee(AdaptConveyorNode<T> *feedee);
void feed(T &&value) override;
void fail(Error &&error) override;
size_t space() const override;
size_t queued() const override;
};
template <typename T>
class AdaptConveyorNode : public ConveyorNode, public ConveyorStorage {
private:
AdaptConveyorFeeder<T> *feeder = nullptr;
std::queue<ErrorOr<T>> storage;
public:
~AdaptConveyorNode();
void setFeeder(AdaptConveyorFeeder<T> *feeder);
void feed(T &&value);
void fail(Error &&error);
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childFired() override {}
// Event
void fire() override;
};
template <typename T> class OneTimeConveyorNode;
template <typename T>
class OneTimeConveyorFeeder : public ConveyorFeeder<UnfixVoid<T>> {
private:
OneTimeConveyorNode<T> *feedee = nullptr;
public:
~OneTimeConveyorFeeder();
void setFeedee(OneTimeConveyorNode<T> *feedee);
void feed(T &&value) override;
void fail(Error &&error) override;
size_t space() const override;
size_t queued() const override;
};
template <typename T>
class OneTimeConveyorNode : public ConveyorNode, public ConveyorStorage {
private:
OneTimeConveyorFeeder<T> *feeder = nullptr;
bool passed = false;
Maybe<ErrorOr<T>> storage = std::nullopt;
public:
~OneTimeConveyorNode();
void setFeeder(OneTimeConveyorFeeder<T> *feeder);
void feed(T &&value);
void fail(Error &&error);
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childFired() override {}
// Event
void fire() override;
};
class QueueBufferConveyorNodeBase : public ConveyorNode,
public ConveyorStorage {
public:
QueueBufferConveyorNodeBase(Own<ConveyorNode> &&dep)
: ConveyorNode(std::move(dep)) {}
virtual ~QueueBufferConveyorNodeBase() = default;
};
template <typename T>
class QueueBufferConveyorNode : public QueueBufferConveyorNodeBase {
private:
std::queue<ErrorOr<T>> storage;
size_t max_store;
public:
QueueBufferConveyorNode(Own<ConveyorNode> &&dep, size_t max_size)
: QueueBufferConveyorNodeBase(std::move(dep)), max_store{max_size} {}
// Event
void fire() override {
if (child) {
if (!storage.empty()) {
if (storage.front().isError()) {
if (storage.front().error().isCritical()) {
child = nullptr;
}
}
}
}
if (parent) {
parent->childFired();
if (!storage.empty()) {
armLater();
}
}
}
// ConveyorNode
void getResult(ErrorOrValue &eov) override {
ErrorOr<T> &err_or_val = eov.as<T>();
err_or_val = std::move(storage.front());
storage.pop();
}
// ConveyorStorage
size_t space() const override { return max_store - storage.size(); }
size_t queued() const override { return storage.size(); }
void childFired() override {
if (child && storage.size() < max_store) {
ErrorOr<T> eov;
child->getResult(eov);
storage.push(std::move(eov));
if (!isArmed()) {
armLater();
}
}
}
};
class AttachConveyorNodeBase : public ConveyorNode {
public:
AttachConveyorNodeBase(Own<ConveyorNode> &&dep)
: ConveyorNode(std::move(dep)) {}
void getResult(ErrorOrValue &err_or_val) override;
};
template <typename... Args>
class AttachConveyorNode : public AttachConveyorNodeBase {
private:
std::tuple<Args...> attached_data;
public:
AttachConveyorNode(Own<ConveyorNode> &&dep, Args &&...args)
: AttachConveyorNodeBase(std::move(dep)), attached_data{
std::move(args...)} {}
};
class ConvertConveyorNodeBase : public ConveyorNode {
public:
ConvertConveyorNodeBase(Own<ConveyorNode> &&dep);
void getResult(ErrorOrValue &err_or_val) override;
virtual void getImpl(ErrorOrValue &err_or_val) = 0;
};
template <typename T, typename DepT, typename Func, typename ErrorFunc>
class ConvertConveyorNode : public ConvertConveyorNodeBase {
private:
Func func;
ErrorFunc error_func;
public:
ConvertConveyorNode(Own<ConveyorNode> &&dep, Func &&func,
ErrorFunc &&error_func)
: ConvertConveyorNodeBase(std::move(dep)), func{std::move(func)},
error_func{std::move(error_func)} {}
void getImpl(ErrorOrValue &err_or_val) override {
ErrorOr<DepT> dep_eov;
ErrorOr<T> &eov = err_or_val.as<T>();
if (child) {
child->getResult(dep_eov);
if (dep_eov.isValue()) {
eov = FixVoidCaller<T, DepT>::apply(func,
std::move(dep_eov.value()));
} else if (dep_eov.isError()) {
eov = error_func(std::move(dep_eov.error()));
} else {
eov = criticalError("No value set in dependency");
}
} else {
eov = criticalError("Conveyor doesn't have child");
}
}
};
class SinkConveyorNode : public ConveyorNode, public ConveyorStorage {
private:
ConveyorSinks *conveyor_sink;
public:
SinkConveyorNode(Own<ConveyorNode> &&node, ConveyorSinks &conv_sink)
: ConveyorNode(std::move(node)), conveyor_sink{&conv_sink} {}
SinkConveyorNode(Own<ConveyorNode> &&node)
: ConveyorNode(std::move(node)), conveyor_sink{nullptr} {}
// Event only queued if a critical error occured
void fire() override {
// Queued for destruction of children, because this acts as a sink and
// no other event should be here
child = nullptr;
if (conveyor_sink) {
conveyor_sink->destroySinkConveyorNode(*this);
conveyor_sink = nullptr;
}
}
// ConveyorStorage
size_t space() const override { return 1; }
size_t queued() const override { return 0; }
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override {
err_or_val.as<Void>() =
criticalError("In a sink node no result can be returned");
}
// ConveyorStorage
void childFired() override {
if (child) {
ErrorOr<Void> dep_eov;
child->getResult(dep_eov);
if (dep_eov.isError()) {
if (dep_eov.error().isCritical()) {
if (!isArmed()) {
armLast();
}
}
if (conveyor_sink) {
conveyor_sink->fail(std::move(dep_eov.error()));
}
}
}
}
};
class ImmediateConveyorNodeBase : public ConveyorNode, public ConveyorStorage {
private:
public:
};
template <typename T>
class ImmediateConveyorNode : public ImmediateConveyorNodeBase {
private:
FixVoid<T> value;
bool retrieved;
public:
ImmediateConveyorNode(FixVoid<T> &&val);
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childFired() override;
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override {
if (retrieved) {
err_or_val.as<FixVoid<T>>() = criticalError("Already taken value");
} else {
err_or_val.as<FixVoid<T>>() = std::move(value);
retrieved = true;
}
}
// Event
void fire() override;
};
template <typename T>
ImmediateConveyorNode<T>::ImmediateConveyorNode(FixVoid<T> &&val)
: value{std::move(val)}, retrieved{false} {}
template <typename T> size_t ImmediateConveyorNode<T>::space() const {
return 0;
}
template <typename T> size_t ImmediateConveyorNode<T>::queued() const {
return retrieved ? 0 : 1;
}
template <typename T> void ImmediateConveyorNode<T>::childFired() {
// Impossible
}
template <typename T> void ImmediateConveyorNode<T>::fire() {
if (parent) {
parent->childFired();
}
}
} // namespace gin
#include <cassert>
// Template inlining
namespace gin {
template <typename T> T reduceErrorOrType(T *);
template <typename T> T reduceErrorOrType(ErrorOr<T> *);
template <typename T>
using ReduceErrorOr = decltype(reduceErrorOrType((T *)nullptr));
template <typename T>
Conveyor<T>::Conveyor(FixVoid<T> value) : ConveyorBase(nullptr, nullptr) {
// Is there any way to do this?
// @todo new ConveyorBase constructor for Immediate values
auto immediate = heap<ImmediateConveyorNode<FixVoid<T>>>(std::move(value));
storage = reinterpret_cast<ConveyorStorage *>(immediate.get());
node = std::move(immediate);
}
template <typename T>
Conveyor<T>::Conveyor(Own<ConveyorNode> &&node_p, ConveyorStorage *storage_p)
: ConveyorBase(std::move(node_p), storage_p) {}
template <typename T>
template <typename Func, typename ErrorFunc>
ConveyorResult<Func, T> Conveyor<T>::then(Func &&func, ErrorFunc &&error_func) {
Own<ConveyorNode> conversion_node =
heap<ConvertConveyorNode<FixVoid<ReduceErrorOr<ReturnType<Func, T>>>,
FixVoid<T>, Func, ErrorFunc>>(
std::move(node), std::move(func), std::move(error_func));
return Conveyor<ReduceErrorOr<ReturnType<Func, T>>>::toConveyor(
std::move(conversion_node), storage);
}
template <typename T> Conveyor<T> Conveyor<T>::buffer(size_t size) {
Own<QueueBufferConveyorNode<FixVoid<T>>> storage_node =
heap<QueueBufferConveyorNode<FixVoid<T>>>(std::move(node), size);
ConveyorStorage *storage_ptr =
static_cast<ConveyorStorage *>(storage_node.get());
storage->setParent(storage_ptr);
return Conveyor<T>{std::move(storage_node), storage_ptr};
}
template <typename T>
template <typename... Args>
Conveyor<T> Conveyor<T>::attach(Args &&...args) {
Own<AttachConveyorNode<Args...>> attach_node =
heap<AttachConveyorNode<Args...>>(std::move(node), std::move(args...));
return Conveyor<T>{std::move(attach_node), storage};
}
template <>
template <typename ErrorFunc>
SinkConveyor Conveyor<void>::sink(ErrorFunc &&error_func) {
Own<SinkConveyorNode> sink_node = heap<SinkConveyorNode>(std::move(node));
ConveyorStorage *storage_ptr =
static_cast<ConveyorStorage *>(sink_node.get());
if (storage) {
storage->setParent(storage_ptr);
}
return SinkConveyor{std::move(sink_node)};
}
void detachConveyor(Conveyor<void> &&conveyor);
template <typename T>
template <typename ErrorFunc>
void Conveyor<T>::detach(ErrorFunc &&func) {
detachConveyor(std::move(then([](T &&) {}, std::move(func))));
}
template <>
template <typename ErrorFunc>
void Conveyor<void>::detach(ErrorFunc &&func) {
detachConveyor(std::move(then([]() {}, std::move(func))));
}
template <typename T>
Conveyor<T> Conveyor<T>::toConveyor(Own<ConveyorNode> &&node,
ConveyorStorage *storage) {
return Conveyor<T>{std::move(node), storage};
}
template <typename T>
std::pair<Own<ConveyorNode>, ConveyorStorage *>
Conveyor<T>::fromConveyor(Conveyor<T> &&conveyor) {
return std::make_pair(std::move(conveyor.node), conveyor.storage);
}
template <typename T> ErrorOr<FixVoid<T>> Conveyor<T>::take() {
if (storage) {
if (storage->queued() > 0) {
ErrorOr<FixVoid<T>> result;
node->getResult(result);
return ErrorOr<FixVoid<T>>{result};
} else {
return ErrorOr<FixVoid<T>>{
recoverableError("Conveyor buffer has no elements")};
}
} else {
return ErrorOr<FixVoid<T>>{criticalError("Conveyor in invalid state")};
}
}
template <typename T> ConveyorAndFeeder<T> newConveyorAndFeeder() {
Own<AdaptConveyorFeeder<FixVoid<T>>> feeder =
heap<AdaptConveyorFeeder<FixVoid<T>>>();
Own<AdaptConveyorNode<FixVoid<T>>> node =
heap<AdaptConveyorNode<FixVoid<T>>>();
feeder->setFeedee(node.get());
node->setFeeder(feeder.get());
ConveyorStorage *storage_ptr = static_cast<ConveyorStorage *>(node.get());
return ConveyorAndFeeder<T>{
std::move(feeder),
Conveyor<T>::toConveyor(std::move(node), storage_ptr)};
}
template <typename T> AdaptConveyorFeeder<T>::~AdaptConveyorFeeder() {
if (feedee) {
feedee->setFeeder(nullptr);
feedee = nullptr;
}
}
template <typename T>
void AdaptConveyorFeeder<T>::setFeedee(AdaptConveyorNode<T> *feedee_p) {
feedee = feedee_p;
}
template <typename T> void AdaptConveyorFeeder<T>::feed(T &&value) {
if (feedee) {
feedee->feed(std::move(value));
}
}
template <typename T> void AdaptConveyorFeeder<T>::fail(Error &&error) {
if (feedee) {
feedee->fail(std::move(error));
}
}
template <typename T> size_t AdaptConveyorFeeder<T>::queued() const {
if (feedee) {
return feedee->queued();
}
return 0;
}
template <typename T> size_t AdaptConveyorFeeder<T>::space() const {
if (feedee) {
return feedee->space();
}
return 0;
}
template <typename T> AdaptConveyorNode<T>::~AdaptConveyorNode() {
if (feeder) {
feeder->setFeedee(nullptr);
feeder = nullptr;
}
}
template <typename T>
void AdaptConveyorNode<T>::setFeeder(AdaptConveyorFeeder<T> *feeder_p) {
feeder = feeder_p;
}
template <typename T> void AdaptConveyorNode<T>::feed(T &&value) {
storage.push(std::move(value));
armNext();
}
template <typename T> void AdaptConveyorNode<T>::fail(Error &&error) {
storage.push(std::move(error));
armNext();
}
template <typename T> size_t AdaptConveyorNode<T>::queued() const {
return storage.size();
}
template <typename T> size_t AdaptConveyorNode<T>::space() const {
return std::numeric_limits<size_t>::max() - storage.size();
}
template <typename T>
void AdaptConveyorNode<T>::getResult(ErrorOrValue &err_or_val) {
if (!storage.empty()) {
err_or_val.as<T>() = std::move(storage.front());
storage.pop();
} else {
err_or_val.as<T>() =
criticalError("Signal for retrieval of storage sent even though no "
"data is present");
}
}
template <typename T> void AdaptConveyorNode<T>::fire() {
if (parent) {
parent->childFired();
if (storage.size() > 0) {
armLater();
}
}
}
template <typename T> OneTimeConveyorFeeder<T>::~OneTimeConveyorFeeder() {
if (feedee) {
feedee->setFeeder(nullptr);
feedee = nullptr;
}
}
template <typename T>
void OneTimeConveyorFeeder<T>::setFeedee(OneTimeConveyorNode<T> *feedee_p) {
feedee = feedee_p;
}
template <typename T> void OneTimeConveyorFeeder<T>::feed(T &&value) {
if (feedee) {
feedee->feed(std::move(value));
}
}
template <typename T> void OneTimeConveyorFeeder<T>::fail(Error &&error) {
if (feedee) {
feedee->fail(std::move(error));
}
}
template <typename T> size_t OneTimeConveyorFeeder<T>::queued() const {
if (feedee) {
return feedee->queued();
}
return 0;
}
template <typename T> size_t OneTimeConveyorFeeder<T>::space() const {
if (feedee) {
return feedee->space();
}
return 0;
}
template <typename T> OneTimeConveyorNode<T>::~OneTimeConveyorNode() {
if (feeder) {
feeder->setFeedee(nullptr);
feeder = nullptr;
}
}
template <typename T>
void OneTimeConveyorNode<T>::setFeeder(OneTimeConveyorFeeder<T> *feeder_p) {
feeder = feeder_p;
}
template <typename T> void OneTimeConveyorNode<T>::feed(T &&value) {
storage = std::move(value);
armNext();
}
template <typename T> void OneTimeConveyorNode<T>::fail(Error &&error) {
storage = std::move(error);
armNext();
}
template <typename T> size_t OneTimeConveyorNode<T>::queued() const {
return storage.has_value() ? 1 : 0;
}
template <typename T> size_t OneTimeConveyorNode<T>::space() const {
return passed ? 0 : 1;
}
template <typename T>
void OneTimeConveyorNode<T>::getResult(ErrorOrValue &err_or_val) {
if (storage.has_value()) {
err_or_val.as<T>() = std::move(storage.value());
storage = std::nullopt;
} else {
err_or_val.as<T>() =
criticalError("Signal for retrieval of storage sent even though no "
"data is present");
}
}
template <typename T> void OneTimeConveyorNode<T>::fire() {
if (parent) {
parent->childFired();
}
}
} // namespace gin

View File

@ -1,31 +0,0 @@
#include "error.h"
namespace gin {
Error::Error() : error_{0} {}
Error::Error(const std::string &msg) : error_message{msg}, error_{1} {}
Error::Error(const std::string &msg, int8_t code)
: error_message{msg}, error_{code} {}
Error::Error(const Error &error)
: error_message{error.error_message}, error_{error.error_} {}
Error::Error(Error &&error)
: error_message{std::move(error.error_message)}, error_{std::move(
error.error_)} {}
const std::string &Error::message() const { return error_message; }
bool Error::failed() const { return error_ != 0; }
bool Error::isCritical() const { return error_ < 0; }
bool Error::isRecoverable() const { return error_ > 0; }
Error criticalError(const std::string &msg) { return Error{msg, -1}; }
Error recoverableError(const std::string &msg) { return Error{msg, 1}; }
Error noError() { return Error{}; }
} // namespace gin

View File

@ -1,87 +0,0 @@
#pragma once
#include <string>
#include <variant>
#include "common.h"
namespace gin {
/**
* Utility class for generating errors. Has a base distinction between
* critical and recoverable errors. Additional code ids can be provided to the
* constructor if additional distinctions are necessary.
*/
class Error {
private:
std::string error_message;
int8_t error_;
public:
Error();
Error(const std::string &msg);
Error(const std::string &msg, int8_t code);
Error(const Error &error);
Error(Error &&error);
Error &operator=(const Error &) = default;
Error &operator=(Error &&) = default;
const std::string &message() const;
bool failed() const;
bool isCritical() const;
bool isRecoverable() const;
};
Error criticalError(const std::string &msg);
Error recoverableError(const std::string &msg);
Error noError();
/**
* Exception alternative. Since I code without exceptions this class is
* essentially a kind of exception replacement.
*/
template <typename T> class ErrorOr;
class ErrorOrValue {
public:
virtual ~ErrorOrValue() = default;
template <typename T> ErrorOr<T> &as() {
return reinterpret_cast<ErrorOr<T> &>(*this);
}
template <typename T> const ErrorOr<T> &as() const {
return reinterpret_cast<const ErrorOr<T> &>(*this);
}
};
template <typename T> class ErrorOr : public ErrorOrValue {
private:
std::variant<T, Error> value_or_error;
public:
ErrorOr() = default;
ErrorOr(const T &value) : value_or_error{value} {}
ErrorOr(T &&value) : value_or_error{std::move(value)} {}
ErrorOr(const Error &error) : value_or_error{error} {}
ErrorOr(Error &&error) : value_or_error{std::move(error)} {}
bool isValue() const { return std::holds_alternative<T>(value_or_error); }
bool isError() const {
return std::holds_alternative<Error>(value_or_error);
}
Error &error() { return std::get<Error>(value_or_error); }
const Error &error() const { return std::get<Error>(value_or_error); }
T &value() { return std::get<T>(value_or_error); }
const T &value() const { return std::get<T>(value_or_error); }
};
} // namespace gin

View File

@ -1,3 +0,0 @@
#include "io.h"
namespace gin {}

View File

@ -1,91 +0,0 @@
#pragma once
#include "async.h"
#include "common.h"
#include <string>
namespace gin {
/*
* Input stream
*/
class InputStream {
public:
virtual ~InputStream() = default;
virtual void read(void *buffer, size_t min_length, size_t max_length) = 0;
virtual Conveyor<size_t> readDone() = 0;
virtual Conveyor<void> readReady() = 0;
virtual Conveyor<void> onReadDisconnected() = 0;
};
/*
* Output stream
*/
class OutputStream {
public:
virtual ~OutputStream() = default;
virtual void write(const void *buffer, size_t length) = 0;
virtual Conveyor<size_t> writeDone() = 0;
virtual Conveyor<void> writeReady() = 0;
};
/*
* Io stream
*/
class IoStream : public InputStream, public OutputStream {
public:
virtual ~IoStream() = default;
};
class DatagramSender {
public:
virtual ~DatagramSender() = default;
};
class DatagramReceiver {
public:
virtual ~DatagramReceiver() = default;
};
class Server {
public:
virtual ~Server() = default;
virtual Conveyor<Own<IoStream>> accept() = 0;
};
class NetworkAddress {
public:
virtual ~NetworkAddress() = default;
/*
* Listen on this address
*/
virtual Own<Server> listen() = 0;
virtual Own<IoStream> connect() = 0;
virtual std::string toString() const = 0;
};
class AsyncIoProvider {
public:
virtual ~AsyncIoProvider() = default;
virtual Own<NetworkAddress> parseAddress(const std::string &,
uint16_t port_hint = 0) = 0;
virtual Own<InputStream> wrapInputFd(int fd) = 0;
};
struct AsyncIoContext {
Own<AsyncIoProvider> io;
EventPort &event_port;
WaitScope &wait_scope;
};
AsyncIoContext setupAsyncIo();
} // namespace gin

View File

@ -1,845 +0,0 @@
#pragma once
#include "buffer.h"
#include "message.h"
#include "message_dynamic.h"
#include "error.h"
#include <cassert>
#include <charconv>
#include <sstream>
#include <string_view>
#include <tuple>
#include <iostream>
namespace gin {
template <typename T> struct JsonEncodeImpl;
template <typename T> struct JsonEncodeImpl<MessagePrimitive<T>> {
static Error encode(typename MessagePrimitive<T>::Reader data,
Buffer &buffer) {
std::string stringified = std::to_string(data.get());
Error error =
buffer.push(*reinterpret_cast<const uint8_t *>(stringified.data()),
stringified.size());
if (error.failed()) {
return error;
}
return Error{};
}
};
template <> struct JsonEncodeImpl<MessagePrimitive<std::string>> {
static Error encode(typename MessagePrimitive<std::string>::Reader data,
Buffer &buffer) {
std::string str =
std::string{"\""} + std::string{data.get()} + std::string{"\""};
Error error = buffer.push(
*reinterpret_cast<const uint8_t *>(str.data()), str.size());
if (error.failed()) {
return error;
}
return Error{};
}
};
template <typename... T> struct JsonEncodeImpl<MessageList<T...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), Error>::type
encodeMembers(typename MessageList<T...>::Reader data, Buffer &buffer) {
(void)data;
(void)buffer;
return Error{};
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), Error>::type
encodeMembers(typename MessageList<T...>::Reader data, Buffer &buffer) {
{
Error error =
JsonEncodeImpl<typename ParameterPackType<i, T...>::type>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
}
if constexpr ((i + 1u) < sizeof...(T)) {
Error error = buffer.push(',');
if (error.failed()) {
return error;
}
}
{
Error error =
JsonEncodeImpl<MessageList<T...>>::encodeMembers<i + 1>(data,
buffer);
if (error.failed()) {
return error;
}
}
return noError();
}
static Error encode(typename MessageList<T...>::Reader data,
Buffer &buffer) {
Error error = buffer.push('[');
if (error.failed()) {
return error;
}
error =
JsonEncodeImpl<MessageList<T...>>::encodeMembers<0>(data, buffer);
if (error.failed()) {
return error;
}
error = buffer.push(']');
if (error.failed()) {
return error;
}
return noError();
}
};
template <typename... V, typename... K>
struct JsonEncodeImpl<MessageStruct<MessageStructMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
encodeMembers(
typename MessageStruct<MessageStructMember<V, K>...>::Reader data,
Buffer &buffer) {
(void)data;
(void)buffer;
return Error{};
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMembers(
typename MessageStruct<MessageStructMember<V, K>...>::Reader data,
Buffer &buffer) {
{
Error error = buffer.push('\"');
if (error.failed()) {
return error;
}
std::string_view view = ParameterPackType<i, K...>::type::view();
error = buffer.push(*reinterpret_cast<const uint8_t *>(view.data()),
view.size());
if (error.failed()) {
return error;
}
error = buffer.push('\"');
if (error.failed()) {
return error;
}
error = buffer.push(':');
if (error.failed()) {
return error;
}
}
{
Error error =
JsonEncodeImpl<typename ParameterPackType<i, V...>::type>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
}
if constexpr ((i + 1u) < sizeof...(V)) {
Error error = buffer.push(',');
if (error.failed()) {
return error;
}
}
{
Error error =
JsonEncodeImpl<MessageStruct<MessageStructMember<V, K>...>>::
encodeMembers<i + 1>(data, buffer);
if (error.failed()) {
return error;
}
}
return noError();
}
static Error
encode(typename MessageStruct<MessageStructMember<V, K>...>::Reader data,
Buffer &buffer) {
Error error = buffer.push('{');
if (error.failed()) {
return error;
}
error = JsonEncodeImpl<MessageStruct<MessageStructMember<V, K>...>>::
encodeMembers<0>(data, buffer);
if (error.failed()) {
return error;
}
error = buffer.push('}');
if (error.failed()) {
return error;
}
return noError();
}
};
template <typename... V, typename... K>
struct JsonEncodeImpl<MessageUnion<MessageUnionMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type encodeMember(
typename MessageUnion<MessageUnionMember<V, K>...>::Reader data,
Buffer &buffer) {
(void)data;
(void)buffer;
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMember(
typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader,
Buffer &buffer) {
/// @todo only encode if alternative is set, skip in other cases
/// use holds_alternative
if (reader.template holdsAlternative<
typename ParameterPackType<i, K...>::type>()) {
{
Error error = buffer.push('{');
if (error.failed()) {
return error;
}
}
{
Error error = buffer.push('\"');
if (error.failed()) {
return error;
}
std::string_view view =
ParameterPackType<i, K...>::type::view();
error =
buffer.push(*reinterpret_cast<const uint8_t *>(view.data()),
view.size());
if (error.failed()) {
return error;
}
error = buffer.push('\"');
if (error.failed()) {
return error;
}
error = buffer.push(':');
if (error.failed()) {
return error;
}
}
Error error =
JsonEncodeImpl<typename ParameterPackType<i, V...>::type>::
encode(reader.template get<i>(), buffer);
if (error.failed()) {
return error;
}
{
Error error = buffer.push('}');
if (error.failed()) {
return error;
}
}
return noError();
}
Error error =
JsonEncodeImpl<MessageUnion<MessageUnionMember<V, K>...>>::
encodeMember<i + 1>(reader, buffer);
if (error.failed()) {
return error;
}
return noError();
}
static Error
encode(typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader,
Buffer &buffer) {
return encodeMember<0>(reader, buffer);
}
};
/*
* For JSON decoding we need a dynamic where we can query information from
*/
template <typename T> struct JsonDecodeImpl;
template <typename T> struct JsonDecodeImpl<MessagePrimitive<T>> {
// static void decode(BufferView view, typename
// MessagePrimitive<T>::Builder){}
static Error decode(typename MessagePrimitive<T>::Builder,
DynamicMessage::DynamicReader) {
return noError();
}
};
template <> struct JsonDecodeImpl<MessagePrimitive<int64_t>> {
// static void decode(BufferView view, typename
// MessagePrimitive<T>::Builder){}
static Error decode(typename MessagePrimitive<int64_t>::Builder data,
DynamicMessage::DynamicReader reader) {
if (reader.type() != DynamicMessage::Type::Signed) {
return criticalError("Not an integer");
}
DynamicMessageSigned::Reader s_reader =
reader.as<DynamicMessageSigned>();
data.set(s_reader.get());
return noError();
}
};
template <> struct JsonDecodeImpl<MessagePrimitive<uint32_t>> {
// static void decode(BufferView view, typename
// MessagePrimitive<T>::Builder){}
static Error decode(typename MessagePrimitive<uint32_t>::Builder builder,
DynamicMessage::DynamicReader reader) {
if (reader.type() != DynamicMessage::Type::Signed) {
return criticalError("Not an integer");
}
DynamicMessageSigned::Reader s_reader =
reader.as<DynamicMessageSigned>();
int64_t val = s_reader.get();
if (val < 0) {
return criticalError("Not an unsigned integer");
}
builder.set(static_cast<uint32_t>(val));
return noError();
}
};
template <> struct JsonDecodeImpl<MessagePrimitive<int32_t>> {
// static void decode(BufferView view, typename
// MessagePrimitive<T>::Builder){}
static Error decode(typename MessagePrimitive<int32_t>::Builder data,
DynamicMessage::DynamicReader reader) {
if (reader.type() != DynamicMessage::Type::Signed) {
return criticalError("Not an integer");
}
DynamicMessageSigned::Reader s_reader =
reader.as<DynamicMessageSigned>();
int64_t val = s_reader.get();
data.set(static_cast<int32_t>(val));
return noError();
}
};
template <> struct JsonDecodeImpl<MessagePrimitive<std::string>> {
static Error decode(typename MessagePrimitive<std::string>::Builder builder,
DynamicMessage::DynamicReader reader) {
if (reader.type() != DynamicMessage::Type::String) {
return criticalError("Not a string");
}
DynamicMessageString::Reader s_reader =
reader.as<DynamicMessageString>();
builder.set(s_reader.get());
return noError();
}
};
template <typename... V, typename... K>
struct JsonDecodeImpl<MessageStruct<MessageStructMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
decodeMembers(typename MessageStruct<MessageStructMember<V, K>...>::Builder,
DynamicMessageStruct::Reader reader) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type decodeMembers(
typename MessageStruct<MessageStructMember<V, K>...>::Builder
builder,
DynamicMessageStruct::Reader reader) {
DynamicMessage::DynamicReader member_reader =
reader.get(ParameterPackType<i, K...>::type::view());
{
Error error =
JsonDecodeImpl<typename ParameterPackType<i, V...>::type>::
decode(builder.template init<i>(), member_reader);
if (error.failed()) {
return error;
}
}
{
Error error =
JsonDecodeImpl<MessageStruct<MessageStructMember<V, K>...>>::
decodeMembers<i + 1>(builder, reader);
if (error.failed()) {
return error;
}
}
return noError();
}
static Error decode(
typename MessageStruct<MessageStructMember<V, K>...>::Builder builder,
DynamicMessage::DynamicReader reader) {
if (reader.type() != DynamicMessage::Type::Struct) {
return criticalError("Not a struct");
}
Error error =
JsonDecodeImpl<MessageStruct<MessageStructMember<V, K>...>>::
decodeMembers<0>(builder, reader.as<DynamicMessageStruct>());
if (error.failed()) {
return error;
}
return noError();
}
};
class JsonCodec {
private:
bool isWhitespace(int8_t letter) {
return letter == '\t' || letter == ' ' || letter == '\r' ||
letter == '\n';
}
void skipWhitespace(Buffer &buffer) {
while (buffer.readCompositeLength() > 0 &&
isWhitespace(buffer.read())) {
buffer.readAdvance(1);
}
}
Error decodeBool(DynamicMessageBool::Builder message, Buffer &buffer) {
/// @todo unimplemented
return noError();
}
// Not yet clear if double or integer
Error decodeNumber(Own<DynamicMessage> &message, Buffer &buffer) {
assert((buffer.read() >= '0' && buffer.read() <= '9') ||
buffer.read() == '+' || buffer.read() == '-');
size_t offset = 0;
if (buffer.read() == '-') {
++offset;
} else if (buffer.read() == '+') {
return criticalError("Not a valid number with +");
}
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
bool integer = true;
if (buffer.read(offset) >= '1' && buffer.read(offset) <= '9') {
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
while (1) {
if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') {
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
continue;
}
break;
}
} else if (buffer.read(offset) == '0') {
++offset;
} else {
return criticalError("Not a JSON number");
}
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
if (buffer.read(offset) == '.') {
integer = false;
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
size_t partial_start = offset;
while (1) {
if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') {
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
continue;
}
break;
}
if (offset == partial_start) {
return criticalError("No numbers after '.'");
}
}
if (buffer.read(offset) == 'e' || buffer.read(offset) == 'E') {
integer = false;
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
if (buffer.read(offset) == '+' || buffer.read(offset) == '-') {
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
}
size_t exp_start = offset;
while (1) {
if (buffer.read(offset) >= '0' && buffer.read(offset) <= '9') {
++offset;
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
continue;
}
break;
}
if (offset == exp_start) {
return criticalError("No numbers after exponent token");
}
}
if (offset >= buffer.readCompositeLength()) {
return recoverableError("Buffer too short");
}
std::string_view number_view{reinterpret_cast<char *>(&buffer.read()),
offset};
if (integer) {
int64_t result;
auto fc_result = std::from_chars(
number_view.data(), number_view.data() + number_view.size(),
result);
if (fc_result.ec != std::errc{}) {
return criticalError("Not an integer");
}
auto int_msg = std::make_unique<DynamicMessageSigned>();
DynamicMessageSigned::Builder builder{*int_msg};
builder.set(result);
message = std::move(int_msg);
} else {
std::string number_copy{number_view};
double result;
try {
result = std::stod(number_copy);
} catch (std::exception &e) {
return criticalError("Not a double");
}
//
auto dbl_msg = std::make_unique<DynamicMessageDouble>();
DynamicMessageDouble::Builder builder{*dbl_msg};
builder.set(result);
message = std::move(dbl_msg);
}
buffer.readAdvance(offset);
skipWhitespace(buffer);
return noError();
}
Error decodeNull(Buffer &buffer) {
/// @todo unimplemented
return noError();
}
Error decodeValue(Own<DynamicMessage> &message, Buffer &buffer) {
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
switch (buffer.read()) {
case '"': {
std::string str;
Error error = decodeRawString(str, buffer);
if (error.failed()) {
return error;
}
Own<DynamicMessageString> msg_string =
std::make_unique<DynamicMessageString>();
DynamicMessageString::Builder builder{*msg_string};
builder.set(std::move(str));
message = std::move(msg_string);
} break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '+':
case '-': {
Error error = decodeNumber(message, buffer);
if (error.failed()) {
return error;
}
} break;
case 't':
case 'T':
case 'f':
case 'F': {
Own<DynamicMessageBool> msg_bool =
std::make_unique<DynamicMessageBool>();
decodeBool(DynamicMessageBool::Builder{*msg_bool}, buffer);
message = std::move(msg_bool);
} break;
case '{': {
Own<DynamicMessageStruct> msg_struct =
std::make_unique<DynamicMessageStruct>();
Error error = decodeStruct(
DynamicMessageStruct::Builder{*msg_struct}, buffer);
if (error.failed()) {
return error;
}
message = std::move(msg_struct);
} break;
case '[': {
Own<DynamicMessageList> msg_list =
std::make_unique<DynamicMessageList>();
decodeList(DynamicMessageList::Builder{*msg_list}, buffer);
message = std::move(msg_list);
} break;
case 'n':
case 'N': {
Own<DynamicMessageNull> msg_null =
std::make_unique<DynamicMessageNull>();
decodeNull(buffer);
message = std::move(msg_null);
} break;
default: {
return criticalError("Cannot identify next JSON value");
}
}
skipWhitespace(buffer);
return noError();
}
Error decodeRawString(std::string &raw, Buffer &buffer) {
assert(buffer.read() == '"');
buffer.readAdvance(1);
std::stringstream iss;
bool string_done = false;
while (!string_done) {
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
switch (buffer.read()) {
case '\\':
buffer.readAdvance(1);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
switch (buffer.read()) {
case '\\':
case '/':
case '"':
iss << buffer.read();
break;
case 'b':
iss << '\b';
break;
case 'f':
iss << '\f';
break;
case 'n':
iss << '\n';
break;
case 'r':
iss << '\r';
break;
case 't':
iss << '\t';
break;
case 'u': {
buffer.readAdvance(1);
if (buffer.readCompositeLength() < 4) {
return recoverableError(
"Broken unicode or short buffer");
}
/// @todo correct unicode handling
iss << '?'; // dummy line
iss << '?';
iss << '?';
iss << '?';
// There is alway a skip at the end so here we skip 3
// instead of 4 bytes
buffer.readAdvance(3);
} break;
}
break;
case '"':
string_done = true;
break;
default:
iss << buffer.read();
break;
}
buffer.readAdvance(1);
}
raw = iss.str();
return noError();
}
Error decodeList(DynamicMessageList::Builder builder, Buffer &buffer) {
assert(buffer.read() == '[');
buffer.readAdvance(1);
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
if (buffer.read() == ']') {
return noError();
}
Own<DynamicMessage> message = nullptr;
{ Error error = decodeValue(message, buffer); }
while (buffer.read() != ']') {
}
/// @todo unimplemented
return noError();
}
Error decodeStruct(DynamicMessageStruct::Builder message, Buffer &buffer) {
assert(buffer.read() == '{');
buffer.readAdvance(1);
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
if (buffer.read() == '}') {
buffer.readAdvance(1);
return noError();
}
while (buffer.read() != '}') {
if (buffer.read() == '"') {
std::string key_string;
{
Error error = decodeRawString(key_string, buffer);
if (error.failed()) {
return error;
}
}
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
if (buffer.read() != ':') {
return criticalError("Expecting a ':' token");
}
buffer.readAdvance(1);
Own<DynamicMessage> msg = nullptr;
{
Error error = decodeValue(msg, buffer);
if (error.failed()) {
return error;
}
}
message.init(key_string, std::move(msg));
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
/*
skipWhitespace(buffer);
if(buffer.readCompositeLength() == 0){
return recoverableError("Buffer too short");
}
/// @todo value decode
skipWhitespace(buffer);
*/
switch (buffer.read()) {
case '}':
break;
case ',':
buffer.readAdvance(1);
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
break;
default:
return criticalError("Not a JSON Object");
}
} else {
return criticalError("Not a JSON Object");
}
}
buffer.readAdvance(1);
return noError();
}
ErrorOr<Own<DynamicMessage>> decodeDynamic(Buffer &buffer) {
skipWhitespace(buffer);
if (buffer.readCompositeLength() == 0) {
return recoverableError("Buffer too short");
}
if (buffer.read() == '{') {
Own<DynamicMessageStruct> message =
std::make_unique<DynamicMessageStruct>();
Error error =
decodeStruct(DynamicMessageStruct::Builder{*message}, buffer);
if (error.failed()) {
return error;
}
skipWhitespace(buffer);
return Own<DynamicMessage>{std::move(message)};
} else if (buffer.read() == '[') {
Own<DynamicMessageList> message =
std::make_unique<DynamicMessageList>();
Error error = decodeList(*message, buffer);
if (error.failed()) {
return error;
}
skipWhitespace(buffer);
return Own<DynamicMessage>{std::move(message)};
} else {
return criticalError("Not a JSON Object");
}
}
public:
template <typename T>
Error encode(typename T::Reader reader, Buffer &buffer) {
return JsonEncodeImpl<T>::encode(reader, buffer);
}
template <typename T>
Error decode(typename T::Builder builder, Buffer &buffer) {
ErrorOr<Own<DynamicMessage>> error_or_message = decodeDynamic(buffer);
if (error_or_message.isError()) {
return error_or_message.error();
}
Own<DynamicMessage> message = std::move(error_or_message.value());
if (!message) {
return criticalError("No message object created");
}
if (message->type() == DynamicMessage::Type::Null) {
return criticalError("Can't decode to json");
}
DynamicMessage::DynamicReader reader{*message};
Error static_error = JsonDecodeImpl<T>::decode(builder, reader);
return static_error;
}
};
} // namespace gin

View File

@ -11,3 +11,6 @@ dir_path = Dir('.').abspath
env.sources += sorted(glob.glob(dir_path + "/*.cpp"))
env.headers += sorted(glob.glob(dir_path + "/*.h"))
env.tls_sources += sorted(glob.glob(dir_path + "/tls/*.cpp"))
env.tls_headers += sorted(glob.glob(dir_path + "/tls/*.h"))

View File

@ -14,11 +14,19 @@ EventLoop &currentEventLoop() {
}
} // namespace
ConveyorNode::ConveyorNode() : child{nullptr} {}
ConveyorNode::ConveyorNode() {}
ConveyorNode::ConveyorNode(Own<ConveyorNode> &&node) : child{std::move(node)} {}
ConveyorStorage::ConveyorStorage(ConveyorStorage *c) : child_storage{c} {}
void ConveyorStorage::setParent(ConveyorStorage *p) {
ConveyorStorage::~ConveyorStorage() {
if (parent) {
parent->unlinkChild();
}
}
void ConveyorStorage::unlinkChild() { child_storage = nullptr; }
void ConveyorEventStorage::setParent(ConveyorStorage *p) {
/*
* parent check isn't needed, but is used
* for the assert, because the storage should
@ -27,18 +35,23 @@ void ConveyorStorage::setParent(ConveyorStorage *p) {
*/
if (/*!parent && */ p && !isArmed() && queued() > 0) {
assert(!parent);
armNext();
if (p->space() > 0) {
armLater();
}
}
parent = p;
}
ConveyorEventStorage::ConveyorEventStorage(ConveyorStorage *c)
: ConveyorStorage{c} {}
ConveyorBase::ConveyorBase(Own<ConveyorNode> &&node_p,
ConveyorStorage *storage_p)
: node{std::move(node_p)}, storage{storage_p} {}
Error PropagateError::operator()(const Error &error) const {
Error err{error};
return err;
return error.copyError();
}
Error PropagateError::operator()(Error &&error) { return std::move(error); }
@ -143,6 +156,8 @@ void Event::disarm() {
bool Event::isArmed() const { return prev != nullptr; }
SinkConveyor::SinkConveyor() : node{nullptr} {}
SinkConveyor::SinkConveyor(Own<ConveyorNode> &&node_p)
: node{std::move(node_p)} {}
@ -263,6 +278,12 @@ void WaitScope::wait(const std::chrono::steady_clock::time_point &time_point) {
void WaitScope::poll() { loop.poll(); }
ImmediateConveyorNodeBase::ImmediateConveyorNodeBase()
: ConveyorEventStorage{nullptr} {}
MergeConveyorNodeBase::MergeConveyorNodeBase()
: ConveyorEventStorage{nullptr} {}
void ConveyorSinks::destroySinkConveyorNode(ConveyorNode &node) {
if (!isArmed()) {
armLast();
@ -275,15 +296,23 @@ void ConveyorSinks::fail(Error &&error) {
/// @todo call error_handler
}
ConveyorSinks::ConveyorSinks(EventLoop &event_loop) : Event{event_loop} {}
void ConveyorSinks::add(Conveyor<void> &&sink) {
auto nas = Conveyor<void>::fromConveyor(std::move(sink));
Own<SinkConveyorNode> sink_node =
heap<SinkConveyorNode>(std::move(nas.first), *this);
Own<SinkConveyorNode> sink_node = nullptr;
try {
sink_node =
heap<SinkConveyorNode>(nas.second, std::move(nas.first), *this);
} catch (std::bad_alloc &) {
return;
}
if (nas.second) {
nas.second->setParent(sink_node.get());
}
sink_nodes.push_back(std::move(sink_node));
sink_nodes.emplace_back(std::move(sink_node));
}
void ConveyorSinks::fire() {
@ -298,13 +327,13 @@ void ConveyorSinks::fire() {
}
ConvertConveyorNodeBase::ConvertConveyorNodeBase(Own<ConveyorNode> &&dep)
: ConveyorNode{std::move(dep)} {}
: child{std::move(dep)} {}
void ConvertConveyorNodeBase::getResult(ErrorOrValue &err_or_val) {
getImpl(err_or_val);
}
void AttachConveyorNodeBase::getResult(ErrorOrValue &err_or_val) {
void AttachConveyorNodeBase::getResult(ErrorOrValue &err_or_val) noexcept {
if (child) {
child->getResult(err_or_val);
}

883
source/kelgin/async.h Normal file
View File

@ -0,0 +1,883 @@
#pragma once
#include "common.h"
#include "error.h"
#include "timer.h"
#include <functional>
#include <limits>
#include <list>
#include <queue>
#include <type_traits>
namespace gin {
class ConveyorNode {
public:
ConveyorNode();
virtual ~ConveyorNode() = default;
virtual void getResult(ErrorOrValue &err_or_val) = 0;
};
class EventLoop;
/*
* Event class similar to capn'proto.
* https://github.com/capnproto/capnproto
*/
class Event {
private:
EventLoop &loop;
Event **prev = nullptr;
Event *next = nullptr;
friend class EventLoop;
public:
Event();
Event(EventLoop &loop);
virtual ~Event();
virtual void fire() = 0;
void armNext();
void armLater();
void armLast();
void disarm();
bool isArmed() const;
};
class ConveyorStorage {
protected:
ConveyorStorage *parent = nullptr;
ConveyorStorage *child_storage = nullptr;
public:
ConveyorStorage(ConveyorStorage *child);
virtual ~ConveyorStorage();
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
virtual void childHasFired() = 0;
virtual void parentHasFired() = 0;
virtual void setParent(ConveyorStorage *parent) = 0;
void unlinkChild();
};
class ConveyorEventStorage : public ConveyorStorage, public Event {
public:
ConveyorEventStorage(ConveyorStorage *child);
virtual ~ConveyorEventStorage() = default;
void setParent(ConveyorStorage *parent) override;
};
class ConveyorBase {
protected:
Own<ConveyorNode> node;
ConveyorStorage *storage;
public:
ConveyorBase(Own<ConveyorNode> &&node_p,
ConveyorStorage *storage_p = nullptr);
virtual ~ConveyorBase() = default;
ConveyorBase(ConveyorBase &&) = default;
ConveyorBase &operator=(ConveyorBase &&) = default;
void get(ErrorOrValue &err_or_val);
};
template <typename T> class Conveyor;
template <typename T> Conveyor<T> chainedConveyorType(T *);
// template <typename T> Conveyor<T> chainedConveyorType(Conveyor<T> *);
template <typename T> T removeErrorOrType(T *);
template <typename T> T removeErrorOrType(ErrorOr<T> *);
template <typename T>
using RemoveErrorOr = decltype(removeErrorOrType((T *)nullptr));
template <typename T>
using ChainedConveyors = decltype(chainedConveyorType((T *)nullptr));
template <typename Func, typename T>
using ConveyorResult = ChainedConveyors<RemoveErrorOr<ReturnType<Func, T>>>;
struct PropagateError {
public:
Error operator()(const Error &error) const;
Error operator()(Error &&error);
};
class SinkConveyor {
private:
Own<ConveyorNode> node;
public:
SinkConveyor();
SinkConveyor(Own<ConveyorNode> &&node);
SinkConveyor(SinkConveyor &&) = default;
SinkConveyor &operator=(SinkConveyor &&) = default;
};
template <typename T> class MergeConveyorNodeData;
template <typename T> class MergeConveyor {
private:
Lent<MergeConveyorNodeData<T>> data;
public:
MergeConveyor(Lent<MergeConveyorNodeData<T>> d);
~MergeConveyor();
void attach(Conveyor<T> conveyor);
};
/**
* Main interface for async operations.
*/
template <typename T> class Conveyor final : public ConveyorBase {
public:
/**
* Construct an immediately fulfilled node
*/
Conveyor(FixVoid<T> value);
/**
* Construct an immediately failed node
*/
Conveyor(Error &&error);
/**
* Construct a conveyor with a child node and the next storage point
*/
Conveyor(Own<ConveyorNode> node_p, ConveyorStorage *storage_p);
Conveyor(Conveyor<T> &&) = default;
Conveyor<T> &operator=(Conveyor<T> &&) = default;
/**
* This method converts values or errors from children
*/
template <typename Func, typename ErrorFunc = PropagateError>
[[nodiscard]] ConveyorResult<Func, T>
then(Func &&func, ErrorFunc &&error_func = PropagateError());
/**
* This method adds a buffer node in the conveyor chains which acts as a
* scheduler interrupt point and collects elements up to the supplied limit.
*/
[[nodiscard]] Conveyor<T>
buffer(size_t limit = std::numeric_limits<size_t>::max());
/**
* This method just takes ownership of any supplied types,
* which are destroyed when the chain gets destroyed.
* Useful for resource lifetime control.
*/
template <typename... Args>
[[nodiscard]] Conveyor<T> attach(Args &&...args);
/** @todo implement
* This method limits the total amount of passed elements
* Be careful where you place this node into the chain.
* If you meant to fork it and destroy paths you shouldn't place
* an interrupt point between the fork and this limiter
*/
[[nodiscard]] Conveyor<T> limit(size_t val = 1);
/**
*
*/
[[nodiscard]] std::pair<Conveyor<T>, MergeConveyor<T>> merge();
/**
* Moves the conveyor chain into a thread local storage point which drops
* every element. Use sink() if you want to control the lifetime of a
* conveyor chain
*/
template <typename ErrorFunc = PropagateError>
void detach(ErrorFunc &&err_func = PropagateError());
/**
* Creates a local sink which drops elements, but lifetime control remains
* in your hand.
*/
template <typename ErrorFunc = PropagateError>
[[nodiscard]] SinkConveyor sink(ErrorFunc &&error_func = PropagateError());
/**
* If no sink() or detach() is used you have to take elements out of the
* chain yourself.
*/
ErrorOr<FixVoid<T>> take();
/** @todo implement
* Specifically pump elements through this chain
*/
void poll();
// helper
static Conveyor<T> toConveyor(Own<ConveyorNode> node,
ConveyorStorage *is_storage = nullptr);
// helper
static std::pair<Own<ConveyorNode>, ConveyorStorage *>
fromConveyor(Conveyor<T> conveyor);
};
template <typename Func> ConveyorResult<Func, void> execLater(Func &&func);
/*
* Join Conveyors into a single one
*/
template <typename... Args>
Conveyor<std::tuple<Args...>>
joinConveyors(std::tuple<Conveyor<Args>...> &conveyors);
template <typename T> class ConveyorFeeder {
public:
virtual ~ConveyorFeeder() = default;
virtual void feed(T &&data) = 0;
virtual void fail(Error &&error) = 0;
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
};
template <> class ConveyorFeeder<void> {
public:
virtual ~ConveyorFeeder() = default;
virtual void feed(Void &&value = Void{}) = 0;
virtual void fail(Error &&error) = 0;
virtual size_t space() const = 0;
virtual size_t queued() const = 0;
};
template <typename T> struct ConveyorAndFeeder {
Own<ConveyorFeeder<T>> feeder;
Conveyor<T> conveyor;
};
template <typename T> ConveyorAndFeeder<T> newConveyorAndFeeder();
template <typename T> ConveyorAndFeeder<T> oneTimeConveyorAndFeeder();
enum class Signal : uint8_t { Terminate, User1 };
/**
* Class which acts as a correspondent between the running framework and outside
* events which may be signals from the operating system or just other threads.
* Default EventPorts are supplied by setupAsyncIo() in io.h
*/
class EventPort {
public:
virtual ~EventPort() = default;
virtual Conveyor<void> onSignal(Signal signal) = 0;
virtual void poll() = 0;
virtual void wait() = 0;
virtual void wait(const std::chrono::steady_clock::duration &) = 0;
virtual void wait(const std::chrono::steady_clock::time_point &) = 0;
virtual void wake() = 0;
};
class SinkConveyorNode;
class ConveyorSinks final : public Event {
private:
/*
class Helper final : public Event {
private:
void destroySinkConveyorNode(ConveyorNode& sink);
void fail(Error&& error);
std::vector<Own<ConveyorNode>> sink_nodes;
std::queue<ConveyorNode*> delete_nodes;
std::function<void(Error&& error)> error_handler;
public:
ConveyorSinks() = default;
ConveyorSinks(EventLoop& event_loop);
void add(Conveyor<void> node);
void fire() override {}
};
gin::Own<Helper> helper;
*/
friend class SinkConveyorNode;
void destroySinkConveyorNode(ConveyorNode &sink_node);
void fail(Error &&error);
std::list<Own<ConveyorNode>> sink_nodes;
std::queue<ConveyorNode *> delete_nodes;
std::function<void(Error &&error)> error_handler;
public:
// ConveyorSinks();
// ConveyorSinks(EventLoop& event_loop);
ConveyorSinks() = default;
ConveyorSinks(EventLoop &event_loop);
void add(Conveyor<void> &&node);
void fire() override;
};
/*
* EventLoop class similar to capn'proto.
* https://github.com/capnproto/capnproto
*/
class EventLoop {
private:
friend class Event;
Event *head = nullptr;
Event **tail = &head;
Event **next_insert_point = &head;
Event **later_insert_point = &head;
bool is_runnable = false;
Own<EventPort> event_port = nullptr;
Own<ConveyorSinks> daemon_sink = nullptr;
// functions
void setRunnable(bool runnable);
friend class WaitScope;
void enterScope();
void leaveScope();
bool turnLoop();
bool turn();
public:
EventLoop();
EventLoop(Own<EventPort> &&port);
~EventLoop();
EventLoop(EventLoop &&) = default;
EventLoop &operator=(EventLoop &&) = default;
bool wait();
bool wait(const std::chrono::steady_clock::duration &);
bool wait(const std::chrono::steady_clock::time_point &);
bool poll();
EventPort *eventPort();
ConveyorSinks &daemon();
};
/*
* WaitScope class similar to capn'proto.
* https://github.com/capnproto/capnproto
*/
class WaitScope {
private:
EventLoop &loop;
public:
WaitScope(EventLoop &loop);
~WaitScope();
void wait();
void wait(const std::chrono::steady_clock::duration &);
void wait(const std::chrono::steady_clock::time_point &);
void poll();
};
template <typename Func> ConveyorResult<Func, void> yieldNext(Func &&func);
template <typename Func> ConveyorResult<Func, void> yieldLater(Func &&func);
template <typename Func> ConveyorResult<Func, void> yieldLast(Func &&func);
} // namespace gin
// Secret stuff
// Aka private semi hidden classes
namespace gin {
template <typename Out, typename In> struct FixVoidCaller {
template <typename Func> static Out apply(Func &func, In &&in) {
return func(std::move(in));
}
};
template <typename Out> struct FixVoidCaller<Out, Void> {
template <typename Func> static Out apply(Func &func, Void &&in) {
(void)in;
return func();
}
};
template <typename In> struct FixVoidCaller<Void, In> {
template <typename Func> static Void apply(Func &func, In &&in) {
func(std::move(in));
return Void{};
}
};
template <> struct FixVoidCaller<Void, Void> {
template <typename Func> static Void apply(Func &func, Void &&in) {
(void)in;
func();
return Void{};
}
};
template <typename T> class AdaptConveyorNode;
template <typename T>
class AdaptConveyorFeeder final : public ConveyorFeeder<UnfixVoid<T>> {
private:
AdaptConveyorNode<T> *feedee = nullptr;
public:
~AdaptConveyorFeeder();
void setFeedee(AdaptConveyorNode<T> *feedee);
void feed(T &&value) override;
void fail(Error &&error) override;
size_t space() const override;
size_t queued() const override;
};
template <typename T>
class AdaptConveyorNode final : public ConveyorNode,
public ConveyorEventStorage {
private:
AdaptConveyorFeeder<T> *feeder = nullptr;
std::queue<ErrorOr<UnfixVoid<T>>> storage;
public:
AdaptConveyorNode();
~AdaptConveyorNode();
void setFeeder(AdaptConveyorFeeder<T> *feeder);
void feed(T &&value);
void fail(Error &&error);
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childHasFired() override;
void parentHasFired() override;
// Event
void fire() override;
};
template <typename T> class OneTimeConveyorNode;
template <typename T>
class OneTimeConveyorFeeder final : public ConveyorFeeder<UnfixVoid<T>> {
private:
OneTimeConveyorNode<T> *feedee = nullptr;
public:
~OneTimeConveyorFeeder();
void setFeedee(OneTimeConveyorNode<T> *feedee);
void feed(T &&value) override;
void fail(Error &&error) override;
size_t space() const override;
size_t queued() const override;
};
template <typename T>
class OneTimeConveyorNode final : public ConveyorNode,
public ConveyorStorage,
public Event {
protected:
Own<ConveyorNode> child;
private:
OneTimeConveyorFeeder<T> *feeder = nullptr;
bool passed = false;
Maybe<ErrorOr<T>> storage = std::nullopt;
public:
~OneTimeConveyorNode();
void setFeeder(OneTimeConveyorFeeder<T> *feeder);
void feed(T &&value);
void fail(Error &&error);
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childHasFired() override {}
void parentHasFired() override;
// Event
void fire() override;
};
class QueueBufferConveyorNodeBase : public ConveyorNode,
public ConveyorEventStorage {
protected:
Own<ConveyorNode> child;
public:
QueueBufferConveyorNodeBase(ConveyorStorage *child_store,
Own<ConveyorNode> dep)
: ConveyorEventStorage{child_store}, child(std::move(dep)) {}
virtual ~QueueBufferConveyorNodeBase() = default;
};
template <typename T>
class QueueBufferConveyorNode final : public QueueBufferConveyorNodeBase {
private:
std::queue<ErrorOr<T>> storage;
size_t max_store;
public:
QueueBufferConveyorNode(ConveyorStorage *child_store, Own<ConveyorNode> dep,
size_t max_size)
: QueueBufferConveyorNodeBase{child_store, std::move(dep)},
max_store{max_size} {}
// Event
void fire() override;
// ConveyorNode
void getResult(ErrorOrValue &eov) noexcept override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childHasFired() override;
void parentHasFired() override;
};
class AttachConveyorNodeBase : public ConveyorNode {
protected:
Own<ConveyorNode> child;
public:
AttachConveyorNodeBase(Own<ConveyorNode> &&dep) : child(std::move(dep)) {}
virtual ~AttachConveyorNodeBase() = default;
void getResult(ErrorOrValue &err_or_val) noexcept override;
};
template <typename... Args>
class AttachConveyorNode final : public AttachConveyorNodeBase {
private:
std::tuple<Args...> attached_data;
public:
AttachConveyorNode(Own<ConveyorNode> &&dep, Args &&...args)
: AttachConveyorNodeBase(std::move(dep)), attached_data{
std::move(args...)} {}
};
class ConvertConveyorNodeBase : public ConveyorNode {
protected:
Own<ConveyorNode> child;
public:
ConvertConveyorNodeBase(Own<ConveyorNode> &&dep);
virtual ~ConvertConveyorNodeBase() = default;
void getResult(ErrorOrValue &err_or_val) override;
virtual void getImpl(ErrorOrValue &err_or_val) = 0;
};
template <typename T, typename DepT, typename Func, typename ErrorFunc>
class ConvertConveyorNode final : public ConvertConveyorNodeBase {
private:
Func func;
ErrorFunc error_func;
static_assert(std::is_same<DepT, RemoveErrorOr<DepT>>::value,
"Should never be of type ErrorOr");
public:
ConvertConveyorNode(Own<ConveyorNode> &&dep, Func &&func,
ErrorFunc &&error_func)
: ConvertConveyorNodeBase(std::move(dep)), func{std::move(func)},
error_func{std::move(error_func)} {}
void getImpl(ErrorOrValue &err_or_val) noexcept override {
ErrorOr<UnfixVoid<DepT>> dep_eov;
ErrorOr<UnfixVoid<RemoveErrorOr<T>>> &eov =
err_or_val.as<UnfixVoid<RemoveErrorOr<T>>>();
if (child) {
child->getResult(dep_eov);
if (dep_eov.isValue()) {
try {
eov = FixVoidCaller<T, DepT>::apply(
func, std::move(dep_eov.value()));
} catch (const std::bad_alloc &) {
eov = criticalError("Out of memory");
} catch (const std::exception &) {
eov = criticalError(
"Exception in chain occured. Return ErrorOr<T> if you "
"want to handle errors which are recoverable");
}
} else if (dep_eov.isError()) {
eov = error_func(std::move(dep_eov.error()));
} else {
eov = criticalError("No value set in dependency");
}
} else {
eov = criticalError("Conveyor doesn't have child");
}
}
};
class SinkConveyorNode final : public ConveyorNode,
public ConveyorEventStorage {
private:
Own<ConveyorNode> child;
ConveyorSinks *conveyor_sink;
public:
SinkConveyorNode(ConveyorStorage *child_store, Own<ConveyorNode> node,
ConveyorSinks &conv_sink)
: ConveyorEventStorage{child_store}, child{std::move(node)},
conveyor_sink{&conv_sink} {}
SinkConveyorNode(ConveyorStorage *child_store, Own<ConveyorNode> node)
: ConveyorEventStorage{child_store}, child{std::move(node)},
conveyor_sink{nullptr} {}
// Event only queued if a critical error occured
void fire() override {
// Queued for destruction of children, because this acts as a sink and
// no other event should be here
child = nullptr;
if (conveyor_sink) {
conveyor_sink->destroySinkConveyorNode(*this);
conveyor_sink = nullptr;
}
}
// ConveyorStorage
size_t space() const override { return 1; }
size_t queued() const override { return 0; }
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) noexcept override {
err_or_val.as<Void>() =
criticalError("In a sink node no result can be returned");
}
// ConveyorStorage
void childHasFired() override {
if (child) {
ErrorOr<void> dep_eov;
child->getResult(dep_eov);
if (dep_eov.isError()) {
if (dep_eov.error().isCritical()) {
if (!isArmed()) {
armLast();
}
}
if (conveyor_sink) {
conveyor_sink->fail(std::move(dep_eov.error()));
}
}
}
}
/*
* No parent needs to be fired since we always have space
*/
void parentHasFired() override {}
};
class ImmediateConveyorNodeBase : public ConveyorNode,
public ConveyorEventStorage {
private:
public:
ImmediateConveyorNodeBase();
virtual ~ImmediateConveyorNodeBase() = default;
};
template <typename T>
class ImmediateConveyorNode final : public ImmediateConveyorNodeBase {
private:
ErrorOr<FixVoid<T>> value;
uint8_t retrieved;
public:
ImmediateConveyorNode(FixVoid<T> &&val);
ImmediateConveyorNode(Error &&error);
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childHasFired() override;
void parentHasFired() override;
// ConveyorNode
void getResult(ErrorOrValue &err_or_val) noexcept override {
if (retrieved > 0) {
err_or_val.as<FixVoid<T>>() =
makeError("Already taken value", Error::Code::Exhausted);
} else {
err_or_val.as<FixVoid<T>>() = std::move(value);
}
if (queued() > 0) {
++retrieved;
}
}
// Event
void fire() override;
};
/*
* Collects every incoming value and throws it in one lane
*/
class MergeConveyorNodeBase : public ConveyorNode, public ConveyorEventStorage {
public:
MergeConveyorNodeBase();
virtual ~MergeConveyorNodeBase() = default;
};
template <typename T> class MergeConveyorNode : public MergeConveyorNodeBase {
private:
class Appendage final : public ConveyorStorage {
public:
Own<ConveyorNode> child;
MergeConveyorNode *merger;
Maybe<ErrorOr<FixVoid<T>>> error_or_value;
public:
Appendage(ConveyorStorage *child_store, Own<ConveyorNode> n,
MergeConveyorNode &m)
: ConveyorStorage{child_store}, child{std::move(n)}, merger{&m},
error_or_value{std::nullopt} {}
bool childStorageHasElementQueued() const {
if (child_storage) {
return child_storage->queued() > 0;
}
return false;
}
void getAppendageResult(ErrorOrValue &eov);
size_t space() const override;
size_t queued() const override;
void childHasFired() override;
void parentHasFired() override;
void setParent(ConveyorStorage *par) override;
};
friend class MergeConveyorNodeData<T>;
friend class Appendage;
Our<MergeConveyorNodeData<T>> data;
size_t next_appendage = 0;
public:
MergeConveyorNode(Our<MergeConveyorNodeData<T>> data);
~MergeConveyorNode();
// Event
void getResult(ErrorOrValue &err_or_val) noexcept override;
void fire() override;
// ConveyorStorage
size_t space() const override;
size_t queued() const override;
void childHasFired() override;
void parentHasFired() override;
};
template <typename T> class MergeConveyorNodeData {
public:
std::vector<Own<typename MergeConveyorNode<T>::Appendage>> appendages;
MergeConveyorNode<T> *merger = nullptr;
public:
void attach(Conveyor<T> conv);
void governingNodeDestroyed();
};
/*
class JoinConveyorNodeBase : public ConveyorNode, public ConveyorEventStorage {
private:
public:
};
template <typename... Args>
class JoinConveyorNode final : public JoinConveyorNodeBase {
private:
template<typename T>
class Appendage : public ConveyorEventStorage {
private:
Maybe<T> data = std::nullopt;
public:
size_t space() const override;
size_t queued() const override;
void fire() override;
void getResult(ErrorOrValue& eov) override;
};
std::tuple<Appendage<Args>...> appendages;
public:
};
*/
} // namespace gin
#include "async.tmpl.h"

647
source/kelgin/async.tmpl.h Normal file
View File

@ -0,0 +1,647 @@
#pragma once
#include "common.h"
#include <cassert>
// Template inlining
#include <iostream>
namespace gin {
template <typename Func> ConveyorResult<Func, void> execLater(Func &&func) {
Conveyor<void> conveyor{FixVoid<void>{}};
return conveyor.then(std::move(func));
}
template <typename T>
Conveyor<T>::Conveyor(FixVoid<T> value) : ConveyorBase(nullptr, nullptr) {
// Is there any way to do this?
// @todo new ConveyorBase constructor for Immediate values
Own<ImmediateConveyorNode<FixVoid<T>>> immediate =
heap<ImmediateConveyorNode<FixVoid<T>>>(std::move(value));
if (!immediate) {
return;
}
storage = static_cast<ConveyorStorage *>(immediate.get());
node = std::move(immediate);
}
template <typename T>
Conveyor<T>::Conveyor(Error &&error) : ConveyorBase(nullptr, nullptr) {
Own<ImmediateConveyorNode<FixVoid<T>>> immediate =
heap<ImmediateConveyorNode<FixVoid<T>>>(std::move(error));
if (!immediate) {
return;
}
storage = static_cast<ConveyorStorage *>(immediate.get());
node = std::move(immediate);
}
template <typename T>
Conveyor<T>::Conveyor(Own<ConveyorNode> node_p, ConveyorStorage *storage_p)
: ConveyorBase{std::move(node_p), storage_p} {}
template <typename T>
template <typename Func, typename ErrorFunc>
ConveyorResult<Func, T> Conveyor<T>::then(Func &&func, ErrorFunc &&error_func) {
Own<ConveyorNode> conversion_node =
heap<ConvertConveyorNode<FixVoid<ReturnType<Func, T>>, FixVoid<T>, Func,
ErrorFunc>>(std::move(node), std::move(func),
std::move(error_func));
return Conveyor<RemoveErrorOr<ReturnType<Func, T>>>::toConveyor(
std::move(conversion_node), storage);
}
template <typename T> Conveyor<T> Conveyor<T>::buffer(size_t size) {
Own<QueueBufferConveyorNode<FixVoid<T>>> storage_node =
heap<QueueBufferConveyorNode<FixVoid<T>>>(storage, std::move(node),
size);
ConveyorStorage *storage_ptr =
static_cast<ConveyorStorage *>(storage_node.get());
GIN_ASSERT(storage) { return Conveyor<T>{nullptr, nullptr}; }
storage->setParent(storage_ptr);
return Conveyor<T>{std::move(storage_node), storage_ptr};
}
template <typename T>
template <typename... Args>
Conveyor<T> Conveyor<T>::attach(Args &&...args) {
Own<AttachConveyorNode<Args...>> attach_node =
heap<AttachConveyorNode<Args...>>(std::move(node), std::move(args...));
return Conveyor<T>{std::move(attach_node), storage};
}
template <typename T>
std::pair<Conveyor<T>, MergeConveyor<T>> Conveyor<T>::merge() {
Our<MergeConveyorNodeData<T>> data = share<MergeConveyorNodeData<T>>();
Own<MergeConveyorNode<T>> merge_node = heap<MergeConveyorNode<T>>(data);
data->attach(Conveyor<T>::toConveyor(std::move(node), storage));
MergeConveyor<T> node_ref{data};
ConveyorStorage *merge_storage =
static_cast<ConveyorStorage *>(merge_node.get());
return std::make_pair(Conveyor<T>{std::move(merge_node), merge_storage},
std::move(node_ref));
}
template <>
template <typename ErrorFunc>
SinkConveyor Conveyor<void>::sink(ErrorFunc &&error_func) {
Own<SinkConveyorNode> sink_node =
heap<SinkConveyorNode>(storage, std::move(node));
ConveyorStorage *storage_ptr =
static_cast<ConveyorStorage *>(sink_node.get());
GIN_ASSERT(storage) { return SinkConveyor{}; }
storage->setParent(storage_ptr);
return SinkConveyor{std::move(sink_node)};
}
void detachConveyor(Conveyor<void> &&conveyor);
template <typename T>
template <typename ErrorFunc>
void Conveyor<T>::detach(ErrorFunc &&func) {
detachConveyor(std::move(then([](T &&) {}, std::move(func))));
}
template <>
template <typename ErrorFunc>
void Conveyor<void>::detach(ErrorFunc &&func) {
detachConveyor(std::move(then([]() {}, std::move(func))));
}
template <typename T>
Conveyor<T> Conveyor<T>::toConveyor(Own<ConveyorNode> node,
ConveyorStorage *storage) {
return Conveyor<T>{std::move(node), storage};
}
template <typename T>
std::pair<Own<ConveyorNode>, ConveyorStorage *>
Conveyor<T>::fromConveyor(Conveyor<T> conveyor) {
return std::make_pair(std::move(conveyor.node), conveyor.storage);
}
template <typename T> ErrorOr<FixVoid<T>> Conveyor<T>::take() {
if (storage) {
if (storage->queued() > 0) {
ErrorOr<FixVoid<T>> result;
node->getResult(result);
return result;
} else {
return ErrorOr<FixVoid<T>>{
recoverableError("Conveyor buffer has no elements")};
}
} else {
return ErrorOr<FixVoid<T>>{criticalError("Conveyor in invalid state")};
}
}
template <typename T> ConveyorAndFeeder<T> newConveyorAndFeeder() {
Own<AdaptConveyorFeeder<FixVoid<T>>> feeder =
heap<AdaptConveyorFeeder<FixVoid<T>>>();
Own<AdaptConveyorNode<FixVoid<T>>> node =
heap<AdaptConveyorNode<FixVoid<T>>>();
feeder->setFeedee(node.get());
node->setFeeder(feeder.get());
ConveyorStorage *storage_ptr = static_cast<ConveyorStorage *>(node.get());
return ConveyorAndFeeder<T>{
std::move(feeder),
Conveyor<T>::toConveyor(std::move(node), storage_ptr)};
}
// QueueBuffer
template <typename T> void QueueBufferConveyorNode<T>::fire() {
if (child) {
if (!storage.empty()) {
if (storage.front().isError()) {
if (storage.front().error().isCritical()) {
child = nullptr;
child_storage = nullptr;
}
}
}
}
bool has_space_before_fire = space() > 0;
if (parent) {
parent->childHasFired();
if (!storage.empty() && parent->space() > 0) {
armLater();
}
}
if (child_storage && !has_space_before_fire) {
child_storage->parentHasFired();
}
}
template <typename T>
void QueueBufferConveyorNode<T>::getResult(ErrorOrValue &eov) noexcept {
ErrorOr<T> &err_or_val = eov.as<T>();
err_or_val = std::move(storage.front());
storage.pop();
}
template <typename T> size_t QueueBufferConveyorNode<T>::space() const {
return max_store - storage.size();
}
template <typename T> size_t QueueBufferConveyorNode<T>::queued() const {
return storage.size();
}
template <typename T> void QueueBufferConveyorNode<T>::childHasFired() {
if (child && storage.size() < max_store) {
ErrorOr<T> eov;
child->getResult(eov);
if (eov.isError()) {
if (eov.error().isCritical()) {
child_storage = nullptr;
}
}
storage.push(std::move(eov));
if (!isArmed()) {
armLater();
}
}
}
template <typename T> void QueueBufferConveyorNode<T>::parentHasFired() {
GIN_ASSERT(parent) { return; }
if (parent->space() == 0) {
return;
}
if (queued() > 0) {
armLater();
}
}
template <typename T>
ImmediateConveyorNode<T>::ImmediateConveyorNode(FixVoid<T> &&val)
: value{std::move(val)}, retrieved{0} {}
template <typename T>
ImmediateConveyorNode<T>::ImmediateConveyorNode(Error &&error)
: value{std::move(error)}, retrieved{0} {}
template <typename T> size_t ImmediateConveyorNode<T>::space() const {
return 0;
}
template <typename T> size_t ImmediateConveyorNode<T>::queued() const {
return retrieved > 1 ? 0 : 1;
}
template <typename T> void ImmediateConveyorNode<T>::childHasFired() {
// Impossible case
assert(false);
}
template <typename T> void ImmediateConveyorNode<T>::parentHasFired() {
GIN_ASSERT(parent) { return; }
assert(parent->space() > 0);
if (queued() > 0) {
armNext();
}
}
template <typename T> void ImmediateConveyorNode<T>::fire() {
if (parent) {
parent->childHasFired();
if (queued() > 0 && parent->space() > 0) {
armLast();
}
}
}
template <typename T>
MergeConveyor<T>::MergeConveyor(Lent<MergeConveyorNodeData<T>> d)
: data{std::move(d)} {}
template <typename T> MergeConveyor<T>::~MergeConveyor() {}
template <typename T> void MergeConveyor<T>::attach(Conveyor<T> conveyor) {
auto sp = data.lock();
GIN_ASSERT(sp) { return; }
sp->attach(std::move(conveyor));
}
template <typename T>
MergeConveyorNode<T>::MergeConveyorNode(Our<MergeConveyorNodeData<T>> d)
: data{d} {
GIN_ASSERT(data) { return; }
data->merger = this;
}
template <typename T> MergeConveyorNode<T>::~MergeConveyorNode() {}
template <typename T>
void MergeConveyorNode<T>::getResult(ErrorOrValue &eov) noexcept {
ErrorOr<FixVoid<T>> &err_or_val = eov.as<FixVoid<T>>();
GIN_ASSERT(data) { return; }
/// @todo search appendages for result
auto &appendages = data->appendages;
next_appendage = std::min(appendages.size(), next_appendage);
for (size_t i = next_appendage; i < appendages.size(); ++i) {
if (appendages[i]->queued() > 0) {
err_or_val = std::move(appendages[i]->error_or_value.value());
appendages[i]->error_or_value = std::nullopt;
next_appendage = i + 1;
return;
}
}
for (size_t i = 0; i < next_appendage; ++i) {
if (appendages[i]->queued() > 0) {
err_or_val = std::move(appendages[i]->error_or_value.value());
appendages[i]->error_or_value = std::nullopt;
next_appendage = i + 1;
return;
}
}
err_or_val = criticalError("No value in Merge Appendages");
}
template <typename T> void MergeConveyorNode<T>::fire() {
GIN_ASSERT(queued() > 0) { return; }
if (parent) {
parent->childHasFired();
if (queued() > 0 && parent->space() > 0) {
armLater();
}
}
}
template <typename T> size_t MergeConveyorNode<T>::space() const { return 0; }
template <typename T> size_t MergeConveyorNode<T>::queued() const {
GIN_ASSERT(data) { return 0; }
size_t queue_count = 0;
for (auto &iter : data->appendages) {
queue_count += iter->queued();
}
return queue_count;
}
template <typename T> void MergeConveyorNode<T>::childHasFired() {
/// This can never happen
assert(false);
}
template <typename T> void MergeConveyorNode<T>::parentHasFired() {
GIN_ASSERT(parent) { return; }
if (queued() > 0) {
if (parent->space() > 0) {
armLater();
}
}
}
template <typename T> size_t MergeConveyorNode<T>::Appendage::space() const {
GIN_ASSERT(merger) { return 0; }
if (error_or_value.has_value()) {
return 0;
}
return 1;
}
template <typename T> size_t MergeConveyorNode<T>::Appendage::queued() const {
GIN_ASSERT(merger) { return 0; }
if (error_or_value.has_value()) {
return 1;
}
return 0;
}
template <typename T>
void MergeConveyorNode<T>::Appendage::getAppendageResult(ErrorOrValue &eov) {
ErrorOr<FixVoid<T>> &err_or_val = eov.as<FixVoid<T>>();
GIN_ASSERT(queued() > 0) {
err_or_val = criticalError("No element queued in Merge Appendage Node");
return;
}
err_or_val = std::move(error_or_value.value());
error_or_value = std::nullopt;
}
template <typename T> void MergeConveyorNode<T>::Appendage::childHasFired() {
GIN_ASSERT(!error_or_value.has_value()) { return; }
ErrorOr<FixVoid<T>> eov;
child->getResult(eov);
error_or_value = std::move(eov);
if (!merger->isArmed()) {
merger->armLater();
}
}
template <typename T> void MergeConveyorNode<T>::Appendage::parentHasFired() {
if (child_storage) {
child_storage->parentHasFired();
}
}
template <typename T>
void MergeConveyorNode<T>::Appendage::setParent(ConveyorStorage *par) {
GIN_ASSERT(merger) { return; }
GIN_ASSERT(child) { return; }
parent = par;
}
template <typename T>
void MergeConveyorNodeData<T>::attach(Conveyor<T> conveyor) {
auto nas = Conveyor<T>::fromConveyor(std::move(conveyor));
auto merge_node_appendage = heap<typename MergeConveyorNode<T>::Appendage>(
nas.second, std::move(nas.first), *merger);
if (nas.second) {
nas.second->setParent(merge_node_appendage.get());
}
appendages.push_back(std::move(merge_node_appendage));
}
template <typename T> void MergeConveyorNodeData<T>::governingNodeDestroyed() {
appendages.clear();
merger = nullptr;
}
template <typename T> AdaptConveyorFeeder<T>::~AdaptConveyorFeeder() {
if (feedee) {
feedee->setFeeder(nullptr);
feedee = nullptr;
}
}
template <typename T>
void AdaptConveyorFeeder<T>::setFeedee(AdaptConveyorNode<T> *feedee_p) {
feedee = feedee_p;
}
template <typename T> void AdaptConveyorFeeder<T>::feed(T &&value) {
if (feedee) {
feedee->feed(std::move(value));
}
}
template <typename T> void AdaptConveyorFeeder<T>::fail(Error &&error) {
if (feedee) {
feedee->fail(std::move(error));
}
}
template <typename T> size_t AdaptConveyorFeeder<T>::queued() const {
if (feedee) {
return feedee->queued();
}
return 0;
}
template <typename T> size_t AdaptConveyorFeeder<T>::space() const {
if (feedee) {
return feedee->space();
}
return 0;
}
template <typename T>
AdaptConveyorNode<T>::AdaptConveyorNode() : ConveyorEventStorage{nullptr} {}
template <typename T> AdaptConveyorNode<T>::~AdaptConveyorNode() {
if (feeder) {
feeder->setFeedee(nullptr);
feeder = nullptr;
}
}
template <typename T>
void AdaptConveyorNode<T>::setFeeder(AdaptConveyorFeeder<T> *feeder_p) {
feeder = feeder_p;
}
template <typename T> void AdaptConveyorNode<T>::feed(T &&value) {
storage.push(std::move(value));
armNext();
}
template <typename T> void AdaptConveyorNode<T>::fail(Error &&error) {
storage.push(std::move(error));
armNext();
}
template <typename T> size_t AdaptConveyorNode<T>::queued() const {
return storage.size();
}
template <typename T> size_t AdaptConveyorNode<T>::space() const {
return std::numeric_limits<size_t>::max() - storage.size();
}
template <typename T>
void AdaptConveyorNode<T>::getResult(ErrorOrValue &err_or_val) {
if (!storage.empty()) {
err_or_val.as<T>() = std::move(storage.front());
storage.pop();
} else {
err_or_val.as<T>() =
criticalError("Signal for retrieval of storage sent even though no "
"data is present");
}
}
template <typename T> void AdaptConveyorNode<T>::childHasFired() {
// Adapt node has no children
assert(false);
}
template <typename T> void AdaptConveyorNode<T>::parentHasFired() {
GIN_ASSERT(parent) { return; }
if (parent->space() == 0) {
return;
}
}
template <typename T> void AdaptConveyorNode<T>::fire() {
if (parent) {
parent->childHasFired();
if (storage.size() > 0) {
armLater();
}
}
}
template <typename T> OneTimeConveyorFeeder<T>::~OneTimeConveyorFeeder() {
if (feedee) {
feedee->setFeeder(nullptr);
feedee = nullptr;
}
}
template <typename T>
void OneTimeConveyorFeeder<T>::setFeedee(OneTimeConveyorNode<T> *feedee_p) {
feedee = feedee_p;
}
template <typename T> void OneTimeConveyorFeeder<T>::feed(T &&value) {
if (feedee) {
feedee->feed(std::move(value));
}
}
template <typename T> void OneTimeConveyorFeeder<T>::fail(Error &&error) {
if (feedee) {
feedee->fail(std::move(error));
}
}
template <typename T> size_t OneTimeConveyorFeeder<T>::queued() const {
if (feedee) {
return feedee->queued();
}
return 0;
}
template <typename T> size_t OneTimeConveyorFeeder<T>::space() const {
if (feedee) {
return feedee->space();
}
return 0;
}
template <typename T> OneTimeConveyorNode<T>::~OneTimeConveyorNode() {
if (feeder) {
feeder->setFeedee(nullptr);
feeder = nullptr;
}
}
template <typename T>
void OneTimeConveyorNode<T>::setFeeder(OneTimeConveyorFeeder<T> *feeder_p) {
feeder = feeder_p;
}
template <typename T> void OneTimeConveyorNode<T>::feed(T &&value) {
storage = std::move(value);
armNext();
}
template <typename T> void OneTimeConveyorNode<T>::fail(Error &&error) {
storage = std::move(error);
armNext();
}
template <typename T> size_t OneTimeConveyorNode<T>::queued() const {
return storage.has_value() ? 1 : 0;
}
template <typename T> size_t OneTimeConveyorNode<T>::space() const {
return passed ? 0 : 1;
}
template <typename T>
void OneTimeConveyorNode<T>::getResult(ErrorOrValue &err_or_val) {
if (storage.has_value()) {
err_or_val.as<T>() = std::move(storage.value());
storage = std::nullopt;
} else {
err_or_val.as<T>() =
criticalError("Signal for retrieval of storage sent even though no "
"data is present");
}
}
template <typename T> void OneTimeConveyorNode<T>::fire() {
if (parent) {
parent->childHasFired();
}
}
} // namespace gin

View File

@ -80,6 +80,117 @@ std::string Buffer::toHex() const {
return oss.str();
}
BufferView::BufferView(Buffer &buffer)
: buffer{buffer}, read_offset{0}, write_offset{0} {}
size_t BufferView::readPosition() const {
return read_offset + buffer.readPosition();
}
size_t BufferView::readCompositeLength() const {
assert(read_offset <= buffer.readCompositeLength());
if (read_offset > buffer.readCompositeLength()) {
return 0;
}
return buffer.readCompositeLength() - read_offset;
}
size_t BufferView::readSegmentLength(size_t offset) const {
size_t off = offset + read_offset;
assert(off <= buffer.readCompositeLength());
if (off > buffer.readCompositeLength()) {
return 0;
}
return buffer.readSegmentLength(off);
}
void BufferView::readAdvance(size_t bytes) {
size_t offset = bytes + read_offset;
assert(offset <= buffer.readCompositeLength());
if (offset > buffer.readCompositeLength()) {
read_offset += buffer.readCompositeLength();
return;
}
read_offset += bytes;
}
uint8_t &BufferView::read(size_t i) {
size_t pos = i + read_offset;
assert(pos < buffer.readCompositeLength());
return buffer.read(pos);
}
const uint8_t &BufferView::read(size_t i) const {
size_t pos = i + read_offset;
assert(pos < buffer.readCompositeLength());
return buffer.read(pos);
}
size_t BufferView::writePosition() const {
return write_offset + buffer.writePosition();
}
size_t BufferView::writeCompositeLength() const {
assert(write_offset <= buffer.writeCompositeLength());
if (write_offset > buffer.writeCompositeLength()) {
return 0;
}
return buffer.writeCompositeLength() - write_offset;
}
size_t BufferView::writeSegmentLength(size_t offset) const {
size_t off = offset + write_offset;
assert(off <= buffer.writeCompositeLength());
if (off > buffer.writeCompositeLength()) {
return 0;
}
return buffer.writeSegmentLength(off);
}
void BufferView::writeAdvance(size_t bytes) {
size_t offset = bytes + write_offset;
assert(offset <= buffer.writeCompositeLength());
if (offset > buffer.writeCompositeLength()) {
write_offset += buffer.writeCompositeLength();
return;
}
write_offset += bytes;
}
uint8_t &BufferView::write(size_t i) {
size_t pos = i + write_offset;
assert(pos < buffer.writeCompositeLength());
return buffer.write(pos);
}
const uint8_t &BufferView::write(size_t i) const {
size_t pos = i + write_offset;
assert(pos < buffer.writeCompositeLength());
return buffer.write(pos);
}
Error BufferView::writeRequireLength(size_t bytes) {
return buffer.writeRequireLength(bytes + write_offset);
}
size_t BufferView::readOffset() const { return read_offset; }
size_t BufferView::writeOffset() const { return write_offset; }
RingBuffer::RingBuffer() : read_position{0}, write_position{0} {
buffer.resize(RING_BUFFER_MAX_SIZE);
}
@ -106,19 +217,42 @@ size_t RingBuffer::readCompositeLength() const {
* If write is ahead then it's the simple distance again. If read is ahead it's
* until the end of the buffer/segment
*/
size_t RingBuffer::readSegmentLength() const {
return writePosition() < readPosition()
? (buffer.size() - readPosition())
: (write_reached_read
? (buffer.size() - readPosition())
: writePosition() -
readPosition()); //(writePosition() -
// readPosition()) :
//(write_reached_read ? () : 0 );
size_t RingBuffer::readSegmentLength(size_t offset) const {
size_t read_composite = readCompositeLength();
assert(offset <= read_composite);
offset = std::min(offset, read_composite);
size_t remaining = read_composite - offset;
size_t read_offset = readPosition() + offset;
read_offset = read_offset >= buffer.size() ? read_offset - buffer.size()
: read_offset;
// case 1 write is located before read and reached read
// then offset can be used normally
// case 2 write is located at read, but read reached write
// then it is set to zero by readCompositeLength()
// case 3 write is located after read
// since std::min you can use simple subtraction
if (writePosition() < read_offset) {
return buffer.size() - read_offset;
}
if (writePosition() == read_offset) {
if (remaining > 0) {
return buffer.size() - read_offset;
} else {
return 0;
}
}
return writePosition() - read_offset;
}
void RingBuffer::readAdvance(size_t bytes) {
assert(bytes <= readCompositeLength());
size_t read_composite = readCompositeLength();
assert(bytes <= read_composite);
bytes = std::min(bytes, read_composite);
size_t advanced = read_position + bytes;
read_position = advanced >= buffer.size() ? advanced - buffer.size()
: advanced;
@ -149,10 +283,24 @@ size_t RingBuffer::writeCompositeLength() const {
: buffer.size() - (writePosition() - readPosition()));
}
size_t RingBuffer::writeSegmentLength() const {
return readPosition() > writePosition()
? (readPosition() - writePosition())
: (write_reached_read ? 0 : (buffer.size() - writePosition()));
size_t RingBuffer::writeSegmentLength(size_t offset) const {
size_t write_composite = writeCompositeLength();
assert(offset <= write_composite);
offset = std::min(offset, write_composite);
size_t write_offset = writePosition() + offset;
write_offset = write_offset >= buffer.size() ? write_offset - buffer.size()
: write_offset;
if (read_position > write_offset) {
return read_position - write_offset;
}
if (write_reached_read) {
return 0;
}
return buffer.size() - write_offset;
}
void RingBuffer::writeAdvance(size_t bytes) {
@ -214,8 +362,14 @@ size_t ArrayBuffer::readCompositeLength() const {
return write_position - read_position;
}
size_t ArrayBuffer::readSegmentLength() const {
return write_position - read_position;
size_t ArrayBuffer::readSegmentLength(size_t offset) const {
size_t read_composite = readCompositeLength();
assert(offset <= read_composite);
offset = std::min(read_composite, offset);
size_t read_offset = read_position + offset;
return write_position - read_offset;
}
void ArrayBuffer::readAdvance(size_t bytes) {
@ -242,9 +396,15 @@ size_t ArrayBuffer::writeCompositeLength() const {
return buffer.size() - write_position;
}
size_t ArrayBuffer::writeSegmentLength() const {
size_t ArrayBuffer::writeSegmentLength(size_t offset) const {
assert(write_position <= buffer.size());
return buffer.size() - write_position;
size_t write_composite = writeCompositeLength();
assert(offset <= write_composite);
offset = std::min(write_composite, offset);
size_t write_offset = write_position + offset;
return buffer.size() - write_offset;
}
void ArrayBuffer::writeAdvance(size_t bytes) {

View File

@ -20,7 +20,7 @@ protected:
public:
virtual size_t readPosition() const = 0;
virtual size_t readCompositeLength() const = 0;
virtual size_t readSegmentLength() const = 0;
virtual size_t readSegmentLength(size_t offset = 0) const = 0;
virtual void readAdvance(size_t bytes) = 0;
virtual uint8_t &read(size_t i = 0) = 0;
@ -28,7 +28,7 @@ public:
virtual size_t writePosition() const = 0;
virtual size_t writeCompositeLength() const = 0;
virtual size_t writeSegmentLength() const = 0;
virtual size_t writeSegmentLength(size_t offset = 0) const = 0;
virtual void writeAdvance(size_t bytes) = 0;
virtual uint8_t &write(size_t i = 0) = 0;
@ -50,6 +50,42 @@ public:
std::string toString() const;
std::string toHex() const;
};
/*
* A viewer class for buffers.
* Working on the reference buffer invalidates the buffer view
*/
class BufferView : public Buffer {
private:
Buffer &buffer;
size_t read_offset;
size_t write_offset;
public:
BufferView(Buffer &);
size_t readPosition() const override;
size_t readCompositeLength() const override;
size_t readSegmentLength(size_t offset = 0) const override;
void readAdvance(size_t bytes) override;
uint8_t &read(size_t i = 0) override;
const uint8_t &read(size_t i = 0) const override;
size_t writePosition() const override;
size_t writeCompositeLength() const override;
size_t writeSegmentLength(size_t offset = 0) const override;
void writeAdvance(size_t bytes) override;
uint8_t &write(size_t i = 0) override;
const uint8_t &write(size_t i = 0) const override;
Error writeRequireLength(size_t bytes) override;
size_t readOffset() const;
size_t writeOffset() const;
};
/*
* Buffer size meant for default allocation size of the ringbuffer since
* this class currently doesn't support proper resizing
@ -76,7 +112,7 @@ public:
size_t readPosition() const override;
size_t readCompositeLength() const override;
size_t readSegmentLength() const override;
size_t readSegmentLength(size_t offset = 0) const override;
void readAdvance(size_t bytes) override;
uint8_t &read(size_t i = 0) override;
@ -84,7 +120,7 @@ public:
size_t writePosition() const override;
size_t writeCompositeLength() const override;
size_t writeSegmentLength() const override;
size_t writeSegmentLength(size_t offset = 0) const override;
void writeAdvance(size_t bytes) override;
uint8_t &write(size_t i = 0) override;
@ -108,7 +144,7 @@ public:
size_t readPosition() const override;
size_t readCompositeLength() const override;
size_t readSegmentLength() const override;
size_t readSegmentLength(size_t offset = 0) const override;
void readAdvance(size_t bytes) override;
uint8_t &read(size_t i = 0) override;
@ -116,7 +152,7 @@ public:
size_t writePosition() const override;
size_t writeCompositeLength() const override;
size_t writeSegmentLength() const override;
size_t writeSegmentLength(size_t offset = 0) const override;
void writeAdvance(size_t bytes) override;
uint8_t &write(size_t i = 0) override;
@ -137,7 +173,7 @@ public:
size_t readPosition() const override;
size_t readCompositeLength() const override;
size_t readSegmentLength() const override;
size_t readSegmentLength(size_t offset = 0) const override;
void readAdvance(size_t bytes) override;
uint8_t &read(size_t i = 0) override;
@ -145,7 +181,7 @@ public:
size_t writePosition() const override;
size_t writeCompositeLength() const override;
size_t writeSegmentLength() const override;
size_t writeSegmentLength(size_t offset = 0) const override;
void writeAdvance(size_t bytes) override;
uint8_t &write(size_t i = 0) override;

View File

@ -15,6 +15,22 @@ namespace gin {
classname(const classname &) = delete; \
classname &operator=(const classname &) = delete
#define GIN_FORBID_MOVE(classname) \
classname(classname &&) = delete; \
classname &operator=(classname &&) = delete
#define GIN_DEFAULT_COPY(classname) \
classname(const classname &) = default; \
classname &operator=(const classname &) = default
#define GIN_DEFAULT_MOVE(classname) \
classname(classname &&) = default; \
classname &operator=(classname &&) = default
#define GIN_ASSERT(expression) \
assert(expression); \
if (!(expression))
template <typename T> using Maybe = std::optional<T>;
template <typename T> using Own = std::unique_ptr<T>;
@ -24,7 +40,7 @@ template <typename T> using Our = std::shared_ptr<T>;
template <typename T> using Lent = std::weak_ptr<T>;
template <typename T, class... Args> Own<T> heap(Args &&...args) {
return std::make_unique<T>(std::forward<Args>(args)...);
return Own<T>(new T(std::forward<Args>(args)...));
}
template <typename T, class... Args> Our<T> share(Args &&...args) {
@ -40,6 +56,9 @@ template <typename Func> struct ReturnTypeHelper<Func, void> {
typedef decltype(instance<Func>()()) Type;
};
template <typename Func, typename T>
using ReturnType = typename ReturnTypeHelper<Func, T>::Type;
struct Void {};
template <typename T> struct VoidFix { typedef T Type; };
@ -50,7 +69,4 @@ template <typename T> struct VoidUnfix { typedef T Type; };
template <> struct VoidUnfix<Void> { typedef void Type; };
template <typename T> using UnfixVoid = typename VoidUnfix<T>::Type;
template <typename Func, typename T>
using ReturnType = typename ReturnTypeHelper<Func, T>::Type;
} // namespace gin
} // namespace gin

73
source/kelgin/error.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "error.h"
namespace gin {
Error::Error() : error_{static_cast<Error::Code>(0)} {}
Error::Error(const std::string_view &msg, Error::Code code)
: error_message{msg}, error_{static_cast<Error::Code>(code)} {}
Error::Error(std::string &&msg, Error::Code code)
: error_message{std::move(msg)}, error_{static_cast<Error::Code>(code)} {}
Error::Error(Error &&error)
: error_message{std::move(error.error_message)}, error_{std::move(
error.error_)} {}
const std::string_view Error::message() const {
return std::visit(
[this](auto &&arg) -> const std::string_view {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, std::string>) {
return std::string_view{arg};
} else if constexpr (std::is_same_v<T, std::string_view>) {
return arg;
} else {
return "Error in class Error. Good luck :)";
}
},
error_message);
}
bool Error::failed() const {
return static_cast<std::underlying_type_t<Error::Code>>(error_) != 0;
}
bool Error::isCritical() const {
return static_cast<std::underlying_type_t<Error::Code>>(error_) < 0;
}
bool Error::isRecoverable() const {
return static_cast<std::underlying_type_t<Error::Code>>(error_) > 0;
}
Error Error::copyError() const {
Error error;
error.error_ = error_;
try {
error.error_message = error_message;
} catch (const std::bad_alloc &) {
error.error_message =
std::string_view{"Error while copying Error string. Out of memory"};
}
return error;
}
Error::Code Error::code() const { return static_cast<Error::Code>(error_); }
Error makeError(const std::string_view &generic, Error::Code code) {
return Error{generic, code};
}
Error criticalError(const std::string_view &generic, Error::Code c) {
return makeError(generic, c);
}
Error recoverableError(const std::string_view &generic, Error::Code c) {
return makeError(generic, c);
}
Error noError() { return Error{}; }
} // namespace gin

141
source/kelgin/error.h Normal file
View File

@ -0,0 +1,141 @@
#pragma once
#include <string>
#include <string_view>
#include <variant>
#include "common.h"
namespace gin {
/**
* Utility class for generating errors. Has a base distinction between
* critical and recoverable errors. Additional code ids can be provided to the
* constructor if additional distinctions are necessary.
*/
class Error {
public:
enum class Code : int16_t {
GenericCritical = -1,
GenericRecoverable = 1,
Disconnected = -99,
Exhausted = -98
};
private:
std::variant<std::string_view, std::string> error_message;
Code error_;
public:
Error();
Error(const std::string_view &msg, Error::Code code);
Error(std::string &&msg, Error::Code code);
Error(Error &&error);
GIN_FORBID_COPY(Error);
Error &operator=(Error &&) = default;
const std::string_view message() const;
bool failed() const;
bool isCritical() const;
bool isRecoverable() const;
Error copyError() const;
Code code() const;
};
Error makeError(const std::string_view &generic, Error::Code c);
template <typename Formatter>
Error makeError(const Formatter &formatter, Error::Code code,
const std::string_view &generic) {
try {
std::string error_msg = formatter();
return Error{std::move(error_msg), code};
} catch (std::bad_alloc &) {
return Error{generic, code};
}
}
Error criticalError(const std::string_view &generic,
Error::Code c = Error::Code::GenericCritical);
template <typename Formatter>
Error criticalError(const Formatter &formatter, const std::string_view &generic,
Error::Code c = Error::Code::GenericCritical) {
return makeError(formatter, c, generic);
}
Error recoverableError(const std::string_view &generic,
Error::Code c = Error::Code::GenericRecoverable);
template <typename Formatter>
Error recoverableError(const Formatter &formatter,
const std::string_view &generic,
Error::Code c = Error::Code::GenericRecoverable) {
return makeError(formatter, c, generic);
}
Error noError();
/**
* Exception alternative. Since I code without exceptions this class is
* essentially a kind of exception replacement.
*/
template <typename T> class ErrorOr;
class ErrorOrValue {
public:
virtual ~ErrorOrValue() = default;
template <typename T> ErrorOr<UnfixVoid<T>> &as() {
return dynamic_cast<ErrorOr<UnfixVoid<T>> &>(*this);
}
template <typename T> const ErrorOr<UnfixVoid<T>> &as() const {
return dynamic_cast<const ErrorOr<UnfixVoid<T>> &>(*this);
}
};
template <typename T> class ErrorOr final : public ErrorOrValue {
private:
std::variant<Error, FixVoid<T>> value_or_error;
static_assert(!std::is_same_v<T, Void>, "Don't use internal private types");
public:
ErrorOr() = default;
ErrorOr(const FixVoid<T> &value) : value_or_error{value} {}
ErrorOr(FixVoid<T> &&value) : value_or_error{std::move(value)} {}
ErrorOr(const Error &error) : value_or_error{error} {}
ErrorOr(Error &&error) : value_or_error{std::move(error)} {}
bool isValue() const {
return std::holds_alternative<FixVoid<T>>(value_or_error);
}
bool isError() const {
return std::holds_alternative<Error>(value_or_error);
}
Error &error() { return std::get<Error>(value_or_error); }
const Error &error() const { return std::get<Error>(value_or_error); }
FixVoid<T> &value() { return std::get<FixVoid<T>>(value_or_error); }
const FixVoid<T> &value() const {
return std::get<FixVoid<T>>(value_or_error);
}
};
template <typename T> class ErrorOr<ErrorOr<T>> {
private:
ErrorOr() = delete;
};
} // namespace gin

61
source/kelgin/io.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "io.h"
#include <cassert>
namespace gin {
AsyncIoStream::AsyncIoStream(Own<IoStream> str)
: stream{std::move(str)}, read_ready{stream->readReady()
.then([this]() {
read_stepper.readStep(*stream);
})
.sink()},
write_ready{stream->writeReady()
.then([this]() { write_stepper.writeStep(*stream); })
.sink()},
read_disconnected{stream->onReadDisconnected()
.then([this]() {
if (read_stepper.on_read_disconnect) {
read_stepper.on_read_disconnect->feed();
}
})
.sink()} {}
void AsyncIoStream::read(void *buffer, size_t min_length, size_t max_length) {
GIN_ASSERT(buffer && max_length >= min_length && min_length > 0) { return; }
GIN_ASSERT(!read_stepper.read_task.has_value()) { return; }
read_stepper.read_task =
ReadTaskAndStepHelper::ReadIoTask{buffer, min_length, max_length, 0};
read_stepper.readStep(*stream);
}
Conveyor<size_t> AsyncIoStream::readDone() {
auto caf = newConveyorAndFeeder<size_t>();
read_stepper.read_done = std::move(caf.feeder);
return std::move(caf.conveyor);
}
Conveyor<void> AsyncIoStream::onReadDisconnected() {
auto caf = newConveyorAndFeeder<void>();
read_stepper.on_read_disconnect = std::move(caf.feeder);
return std::move(caf.conveyor);
}
void AsyncIoStream::write(const void *buffer, size_t length) {
GIN_ASSERT(buffer && length > 0) { return; }
GIN_ASSERT(!write_stepper.write_task.has_value()) { return; }
write_stepper.write_task =
WriteTaskAndStepHelper::WriteIoTask{buffer, length, 0};
write_stepper.writeStep(*stream);
}
Conveyor<size_t> AsyncIoStream::writeDone() {
auto caf = newConveyorAndFeeder<size_t>();
write_stepper.write_done = std::move(caf.feeder);
return std::move(caf.conveyor);
}
} // namespace gin

135
source/kelgin/io.h Normal file
View File

@ -0,0 +1,135 @@
#pragma once
#include "async.h"
#include "common.h"
#include "io_helpers.h"
#include <string>
namespace gin {
/*
* Input stream
*/
class InputStream {
public:
virtual ~InputStream() = default;
virtual ErrorOr<size_t> read(void *buffer, size_t length) = 0;
virtual Conveyor<void> readReady() = 0;
virtual Conveyor<void> onReadDisconnected() = 0;
};
/*
* Output stream
*/
class OutputStream {
public:
virtual ~OutputStream() = default;
virtual ErrorOr<size_t> write(const void *buffer, size_t length) = 0;
virtual Conveyor<void> writeReady() = 0;
};
/*
* Io stream
*/
class IoStream : public InputStream, public OutputStream {
public:
virtual ~IoStream() = default;
};
class AsyncInputStream {
public:
virtual ~AsyncInputStream() = default;
virtual void read(void *buffer, size_t min_length, size_t max_length) = 0;
virtual Conveyor<size_t> readDone() = 0;
virtual Conveyor<void> onReadDisconnected() = 0;
};
class AsyncOutputStream {
public:
virtual ~AsyncOutputStream() = default;
virtual void write(const void *buffer, size_t length) = 0;
virtual Conveyor<size_t> writeDone() = 0;
};
class AsyncIoStream : public AsyncInputStream, public AsyncOutputStream {
private:
Own<IoStream> stream;
SinkConveyor read_ready;
SinkConveyor write_ready;
SinkConveyor read_disconnected;
ReadTaskAndStepHelper read_stepper;
WriteTaskAndStepHelper write_stepper;
public:
AsyncIoStream(Own<IoStream> str);
void read(void *buffer, size_t min_length, size_t max_length) override;
Conveyor<size_t> readDone() override;
Conveyor<void> onReadDisconnected() override;
void write(const void *buffer, size_t length) override;
Conveyor<size_t> writeDone() override;
};
class Server {
public:
virtual ~Server() = default;
virtual Conveyor<Own<IoStream>> accept() = 0;
};
class NetworkAddress {
public:
virtual ~NetworkAddress() = default;
/*
* Listen on this address
*/
virtual Own<Server> listen() = 0;
virtual Conveyor<Own<IoStream>> connect() = 0;
virtual std::string toString() const = 0;
virtual const std::string &address() const = 0;
virtual uint16_t port() const = 0;
};
class Network {
public:
virtual ~Network() = default;
virtual Conveyor<Own<NetworkAddress>>
parseAddress(const std::string &addr, uint16_t port_hint = 0) = 0;
};
class IoProvider {
public:
virtual ~IoProvider() = default;
virtual Own<InputStream> wrapInputFd(int fd) = 0;
virtual Network &network() = 0;
};
struct AsyncIoContext {
Own<IoProvider> io;
EventLoop &event_loop;
EventPort &event_port;
};
ErrorOr<AsyncIoContext> setupAsyncIo();
} // namespace gin

View File

@ -0,0 +1,85 @@
#include "io_helpers.h"
#include "io.h"
#include <cassert>
namespace gin {
void ReadTaskAndStepHelper::readStep(InputStream &reader) {
while (read_task.has_value()) {
ReadIoTask &task = *read_task;
ErrorOr<size_t> n_err = reader.read(task.buffer, task.max_length);
if (n_err.isError()) {
const Error &error = n_err.error();
if (error.isCritical()) {
if (read_done) {
read_done->fail(error.copyError());
}
read_task = std::nullopt;
}
break;
} else if (n_err.isValue()) {
size_t n = n_err.value();
if (static_cast<size_t>(n) >= task.min_length &&
static_cast<size_t>(n) <= task.max_length) {
if (read_done) {
read_done->feed(n + task.already_read);
}
read_task = std::nullopt;
} else {
task.buffer = static_cast<uint8_t *>(task.buffer) + n;
task.min_length -= static_cast<size_t>(n);
task.max_length -= static_cast<size_t>(n);
task.already_read += n;
}
} else {
if (read_done) {
read_done->fail(criticalError("Read failed"));
}
read_task = std::nullopt;
}
}
}
void WriteTaskAndStepHelper::writeStep(OutputStream &writer) {
while (write_task.has_value()) {
WriteIoTask &task = *write_task;
ErrorOr<size_t> n_err = writer.write(task.buffer, task.length);
if (n_err.isValue()) {
size_t n = n_err.value();
assert(n <= task.length);
if (n == task.length) {
if (write_done) {
write_done->feed(n + task.already_written);
}
write_task = std::nullopt;
} else {
task.buffer = static_cast<const uint8_t *>(task.buffer) + n;
task.length -= n;
task.already_written += n;
}
} else if (n_err.isError()) {
const Error &error = n_err.error();
if (error.isCritical()) {
if (write_done) {
write_done->fail(error.copyError());
}
write_task = std::nullopt;
}
break;
} else {
if (write_done) {
write_done->fail(criticalError("Write failed"));
}
write_task = std::nullopt;
}
}
}
} // namespace gin

View File

@ -0,0 +1,53 @@
#pragma once
#include "async.h"
#include "common.h"
#include <cstdint>
#include <optional>
namespace gin {
/*
* Helper classes for the specific driver implementations
*/
/*
* Since I don't want to repeat these implementations for tls on unix systems
* and gnutls doesn't let me write or read into buffers I have to have this kind
* of strange abstraction. This may also be reusable for windows/macOS though.
*/
class InputStream;
class ReadTaskAndStepHelper {
public:
struct ReadIoTask {
void *buffer;
size_t min_length;
size_t max_length;
size_t already_read = 0;
};
std::optional<ReadIoTask> read_task;
Own<ConveyorFeeder<size_t>> read_done = nullptr;
Own<ConveyorFeeder<void>> on_read_disconnect = nullptr;
public:
void readStep(InputStream &reader);
};
class OutputStream;
class WriteTaskAndStepHelper {
public:
struct WriteIoTask {
const void *buffer;
size_t length;
size_t already_written = 0;
};
std::optional<WriteIoTask> write_task;
Own<ConveyorFeeder<size_t>> write_done = nullptr;
public:
void writeStep(OutputStream &writer);
};
} // namespace gin

View File

@ -0,0 +1,25 @@
#pragma once
#include "async.h"
#include "io.h"
namespace gin {
template <typename Codec, typename Incoming, typename Outgoing>
class StreamingIoPeer {
private:
Codec codec;
Own<AsyncIoStream> io_stream;
Own<ConveyorFeeder<Incoming>> incoming_feeder = nullptr;
public:
StreamingIoPeer(Own<AsyncIoStream> stream);
void send(Outgoing outgoing, Own<MessageBuilder> builder);
Conveyor<Incoming> startReadPump();
};
} // namespace gin

560
source/kelgin/message.h Normal file
View File

@ -0,0 +1,560 @@
#pragma once
#include <cstdint>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
#include <cassert>
#include "common.h"
#include "message_container.h"
#include "schema.h"
#include "string_literal.h"
namespace gin {
class MessageBase {
protected:
bool set_explicitly = false;
public:
template <class T> T &as() {
static_assert(std::is_base_of<MessageBase, T>());
return dynamic_cast<T &>(*this);
}
template <class T> const T &as() const {
static_assert(std::is_base_of<MessageBase, T>());
return dynamic_cast<const T &>(*this);
}
};
/*
* Representing all message types
* Description which type to use happens through the use of the schema classes
* in schema.h The message classes are wrapper classes which store the data
* according to the specified container class.
*
* The reader and builder classe exist to create some clarity while implementing
* parsers. Also minor guarantess are provided if the message is used as a
* template parameter.
*/
/*
* Struct Message
*/
template <class... V, StringLiteral... Keys, class Container>
class Message<schema::Struct<schema::NamedMember<V, Keys>...>, Container> final
: public MessageBase {
private:
using SchemaType = schema::Struct<schema::NamedMember<V, Keys>...>;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same the schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageType &message;
public:
Builder(MessageType &msg) : message{msg} {}
Reader asReader() { return Reader{message}; }
/*
* Initialize a member by index
*/
template <size_t i>
typename std::enable_if<
!SchemaIsArray<
typename MessageParameterPackType<i, V...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init() {
return typename Container::template ElementType<i>::Builder{
message.container.template get<i>()};
}
/*
* Initialize a member by their name
* This is the preferred method for schema::Struct messages
*/
template <StringLiteral Literal>
typename std::enable_if<
!SchemaIsArray<typename MessageParameterPackType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value,
V...>::Type>::Value,
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal,
Keys...>::Value>::Builder>::type
init() {
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return init<i>();
}
template <size_t i>
typename std::enable_if<
SchemaIsArray<
typename MessageParameterPackType<i, V...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init(size_t size) {
auto array_builder =
typename Container::template ElementType<i>::Builder{
message.container.template get<i>(), size};
return array_builder;
}
/*
* Version for array schema type
*/
template <StringLiteral Literal>
typename std::enable_if<
SchemaIsArray<typename MessageParameterPackType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value,
V...>::Type>::Value,
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal,
Keys...>::Value>::Builder>::type
init(size_t size) {
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return init<i>(size);
}
};
class Reader {
private:
MessageType &message;
public:
Reader(MessageType &msg) : message{msg} {}
Builder asBuilder() { return Builder{message}; }
/*
* Get member by index
*/
template <size_t i>
typename Container::template ElementType<i>::Reader get() {
return typename Container::template ElementType<i>::Reader{
message.container.template get<i>()};
}
/*
* Get member by name
* This is the preferred method for schema::Struct messages
*/
template <StringLiteral Literal>
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value>::Reader
get() {
// The index of the first match
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return get<i>();
}
};
Builder build() { return Builder{*this}; }
Reader read() { return Reader{*this}; }
};
/*
* Union message class. Wrapper object
*/
template <class... V, StringLiteral... Keys, class Container>
class Message<schema::Union<schema::NamedMember<V, Keys>...>, Container> final
: public MessageBase {
private:
using SchemaType = schema::Union<schema::NamedMember<V, Keys>...>;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same the schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageType &message;
public:
Builder(MessageType &msg) : message{msg} {}
Reader asReader() { return Reader{message}; }
template <size_t i>
typename std::enable_if<
!SchemaIsArray<
typename MessageParameterPackType<i, V...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init() {
return typename Container::template ElementType<i>::Builder{
message.container.template get<i>()};
}
template <StringLiteral Literal>
typename std::enable_if<
!SchemaIsArray<typename MessageParameterPackType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value,
V...>::Type>::Value,
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal,
Keys...>::Value>::Builder>::type
init() {
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return init<i>();
}
/*
* If Schema is Array
*/
template <size_t i>
typename std::enable_if<
SchemaIsArray<
typename MessageParameterPackType<i, V...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init(size_t size) {
return typename Container::template ElementType<i>::Builder{
message.container.template get<i>(), size};
}
template <StringLiteral Literal>
typename std::enable_if<
SchemaIsArray<typename MessageParameterPackType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value,
V...>::Type>::Value,
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal,
Keys...>::Value>::Builder>::type
init(size_t size) {
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return init<i>(size);
}
};
class Reader {
private:
MessageType &message;
public:
Reader(MessageType &msg) : message{msg} {}
Builder asBuilder() { return Builder{message}; }
template <size_t i>
typename Container::template ElementType<i>::Reader get() {
return typename Container::template ElementType<i>::Reader{
message.container.template get<i>()};
}
template <StringLiteral Literal>
typename Container::template ElementType<
MessageParameterKeyPackIndex<Literal, Keys...>::Value>::Reader
get() {
// The index of the first match
constexpr size_t i =
MessageParameterKeyPackIndex<Literal, Keys...>::Value;
return get<i>();
}
template <StringLiteral Literal>
constexpr size_t toIndex() const noexcept {
return MessageParameterKeyPackIndex<Literal, Keys...>::Value;
}
size_t index() const noexcept { return message.container.index(); }
template <StringLiteral Literal> bool hasAlternative() const {
return index() == toIndex<Literal>();
}
};
};
/*
* Array message class. Wrapper around an array schema element
*/
template <class T, class Container>
class Message<schema::Array<T>, Container> final : public MessageBase {
private:
using SchemaType = schema::Array<T>;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same Schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageType &message;
public:
Builder(MessageType &msg, size_t size) : message{msg} {
if (size > 0) {
message.container.resize(size);
}
}
Reader asReader() { return Reader{message}; }
typename Container::ElementType::Builder init(size_t i) {
return typename Container::ElementType::Builder{
message.container.get(i)};
}
size_t size() const { return message.container.size(); }
};
class Reader {
private:
MessageType &message;
public:
Reader(MessageType &msg) : message{msg} {}
Builder asBuilder() { return Builder{message, 0}; }
typename Container::ElementType::Reader get(size_t i) {
return typename Container::ElementType::Reader{
message.container.get(i)};
}
size_t size() const { return message.container.size(); }
};
};
/*
* Tuple message class. Wrapper around a tuple schema
*/
template <class... T, class Container>
class Message<schema::Tuple<T...>, Container> final : public MessageBase {
private:
using SchemaType = schema::Tuple<T...>;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same the schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
MessageType &message;
public:
Builder(MessageType &msg) : message{msg} {}
Reader asReader() { return Reader{message}; }
template <size_t i>
typename std::enable_if<
!SchemaIsArray<
typename MessageParameterPackType<i, T...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init() {
return typename Container::template ElementType<i>::Builder{
message.container.template get<i>()};
}
template <size_t i>
typename std::enable_if<
SchemaIsArray<
typename MessageParameterPackType<i, T...>::Type>::Value,
typename Container::template ElementType<i>::Builder>::type
init(size_t size) {
return typename Container::template ElementType<i>::Builder{
message.container.template get<i>(), size};
}
};
class Reader {
private:
MessageType &message;
public:
Reader(MessageType &msg) : message{msg} {}
Builder asBuilder() { return Builder{message}; }
template <size_t i>
typename Container::template ElementType<i>::Reader get() {
return typename Container::template ElementType<i>::Reader{
message.container.template get<i>()};
}
};
};
/*
* Primitive type (float, double, uint8_t, uint16_t, uint32_t, uint64_t, int8_t,
* int16_t, int32_t, int64_t) message class
*/
template <class T, size_t N, class Container>
class Message<schema::Primitive<T, N>, Container> final : public MessageBase {
private:
using SchemaType = schema::Primitive<T, N>;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same the schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageType &message;
public:
Builder(MessageType &msg) : message{msg} {}
Reader asReader() { return Reader{message}; }
void set(const typename Container::ValueType &value) {
message.container.set(value);
}
};
class Reader {
private:
MessageType &message;
public:
Reader(Message &msg) : message{msg} {}
Builder asBuilder() { return Builder{message}; }
const typename Container::ValueType &get() const {
return message.container.get();
}
};
};
template <class Container>
class Message<schema::String, Container> final : public MessageBase {
private:
using SchemaType = schema::String;
using MessageType = Message<SchemaType, Container>;
Container container;
static_assert(std::is_same_v<SchemaType, typename Container::SchemaType>,
"Container should have same the schema as Message");
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageType &message;
public:
Builder(MessageType &msg) : message{msg} {}
Reader asReader() { return Reader{message}; }
void set(std::string &&str) { message.container.set(std::move(str)); }
void set(const std::string_view str) { message.container.set(str); }
void set(const char *str) { set(std::string_view{str}); }
};
class Reader {
private:
MessageType &message;
public:
Reader(MessageType &msg) : message{msg} {}
Builder asBuilder() { return Builder{message}; }
const std::string_view get() const { return message.container.get(); }
};
};
template <class Schema, class Container = MessageContainer<Schema>>
class HeapMessageRoot {
private:
Own<Message<Schema, Container>> root;
public:
HeapMessageRoot(Own<Message<Schema, Container>> r) : root{std::move(r)} {}
typename Message<Schema, Container>::Builder build() {
assert(root);
return typename Message<Schema, Container>::Builder{*root};
}
typename Message<Schema, Container>::Reader read() {
assert(root);
return typename Message<Schema, Container>::Reader{*root};
}
};
template <class T, class Container>
class HeapMessageRoot<schema::Array<T>, Container> {
public:
using Schema = schema::Array<T>;
private:
Own<Message<Schema, Container>> root;
public:
HeapMessageRoot(Own<Message<Schema, Container>> r) : root{std::move(r)} {}
typename Message<Schema, Container>::Builder build(size_t size) {
assert(root);
return typename Message<Schema, Container>::Builder{*root, size};
}
typename Message<Schema, Container>::Reader read() {
assert(root);
return typename Message<Schema, Container>::Reader{*root};
}
};
/*
* Minor helper for creating a message root
*/
template <class Schema, class Container = MessageContainer<Schema>>
inline HeapMessageRoot<Schema, Container> heapMessageRoot() {
Own<Message<Schema, Container>> root = heap<Message<Schema, Container>>();
return HeapMessageRoot<Schema, Container>{std::move(root)};
}
} // namespace gin

View File

@ -0,0 +1,241 @@
#pragma once
#include "schema.h"
namespace gin {
template <class T> class MessageContainer;
template <class T, class Container = MessageContainer<T>> class Message;
template <size_t N, class... T> struct MessageParameterPackType;
template <class TN, class... T> struct MessageParameterPackType<0, TN, T...> {
using Type = TN;
};
template <size_t N, class TN, class... T>
struct MessageParameterPackType<N, TN, T...> {
static_assert(sizeof...(T) > 0, "Exhausted parameters");
using Type = typename MessageParameterPackType<N - 1, T...>::Type;
};
template <class T, class... TL> struct MessageParameterPackIndex;
template <class T, class... TL> struct MessageParameterPackIndex<T, T, TL...> {
static constexpr size_t Value = 0u;
};
template <class T, class TL0, class... TL>
struct MessageParameterPackIndex<T, TL0, TL...> {
static constexpr size_t Value =
1u + MessageParameterPackIndex<T, TL...>::Value;
};
/*
* Nightmare inducing compiler problems found here. Somehow non-type
* StringLiterals cannot be resolved as non-type primitive template values can.
* This is the workaround
*/
template <StringLiteral V, StringLiteral Key0, StringLiteral... Keys>
struct MessageParameterKeyPackIndexHelper {
static constexpr size_t Value =
(V == Key0)
? (0u)
: (1u + MessageParameterKeyPackIndexHelper<V, Keys...>::Value);
};
template <StringLiteral V, StringLiteral Key0>
struct MessageParameterKeyPackIndexHelper<V, Key0> {
static constexpr size_t Value = (V == Key0) ? (0u) : (1u);
};
template <StringLiteral V, StringLiteral... Keys>
struct MessageParameterKeyPackIndex {
static constexpr size_t Value =
MessageParameterKeyPackIndexHelper<V, Keys...>::Value;
static_assert(Value < sizeof...(Keys),
"Provided StringLiteral doesn't exist in searched list");
};
template <class T> struct SchemaIsArray {
constexpr static bool Value = false;
};
template <class T> struct SchemaIsArray<schema::Array<T>> {
constexpr static bool Value = true;
};
template <class... V, StringLiteral... Keys>
class MessageContainer<schema::Struct<schema::NamedMember<V, Keys>...>> {
private:
using ValueType = std::tuple<Message<V, MessageContainer<V>>...>;
ValueType values;
public:
using SchemaType = schema::Struct<schema::NamedMember<V, Keys>...>;
template <size_t i>
using ElementType = typename MessageParameterPackType<
i, Message<V, MessageContainer<V>>...>::Type;
template <size_t i> ElementType<i> &get() { return std::get<i>(values); }
};
/*
* Union storage
*/
template <class... V, StringLiteral... Keys>
class MessageContainer<schema::Union<schema::NamedMember<V, Keys>...>> {
private:
using ValueType = std::variant<Message<V, MessageContainer<V>>...>;
ValueType value;
public:
using SchemaType = schema::Union<schema::NamedMember<V, Keys>...>;
template <size_t i>
using ElementType = typename MessageParameterPackType<
i, Message<V, MessageContainer<V>>...>::Type;
template <size_t i> ElementType<i> &get() {
if (i != value.index()) {
using MessageIV = typename MessageParameterPackType<i, V...>::Type;
value = Message<MessageIV, MessageContainer<MessageIV>>{};
}
return std::get<i>(value);
}
size_t index() const noexcept { return value.index(); }
};
/*
* Array storage
*/
template <class T> class MessageContainer<schema::Array<T>> {
private:
using ValueType = std::vector<Message<T, MessageContainer<T>>>;
ValueType values;
public:
using SchemaType = schema::Array<T>;
using ElementType = Message<T, MessageContainer<T>>;
Message<T, MessageContainer<T>> &get(size_t index) {
return values.at(index);
}
void resize(size_t size) { values.resize(size); }
size_t size() const { return values.size(); }
};
/*
* Tuple storage
*/
template <class... T> class MessageContainer<schema::Tuple<T...>> {
private:
using ValueType = std::tuple<Message<T, MessageContainer<T>>...>;
ValueType values;
public:
using SchemaType = schema::Tuple<T...>;
template <size_t i>
using ElementType = typename MessageParameterPackType<
i, Message<T, MessageContainer<T>>...>::Type;
template <size_t i> ElementType<i> &get() { return std::get<i>(values); }
};
/*
* Helper for the basic message container, so the class doesn't have to be
* specialized 10 times.
*/
template <class T> struct PrimitiveTypeHelper;
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::SignedInteger, 1>> {
using Type = int8_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::SignedInteger, 2>> {
using Type = int16_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::SignedInteger, 4>> {
using Type = int32_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::SignedInteger, 8>> {
using Type = int64_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::UnsignedInteger, 1>> {
using Type = uint8_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::UnsignedInteger, 2>> {
using Type = uint16_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::UnsignedInteger, 4>> {
using Type = uint32_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::UnsignedInteger, 8>> {
using Type = uint64_t;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::FloatingPoint, 4>> {
using Type = float;
};
template <>
struct PrimitiveTypeHelper<schema::Primitive<schema::FloatingPoint, 8>> {
using Type = double;
};
template <class T, size_t N> class MessageContainer<schema::Primitive<T, N>> {
public:
using SchemaType = schema::Primitive<T, N>;
using ValueType =
typename PrimitiveTypeHelper<schema::Primitive<T, N>>::Type;
private:
ValueType value;
public:
MessageContainer() : value{0} {}
void set(const ValueType &v) { value = v; }
const ValueType &get() const { return value; }
};
template <> class MessageContainer<schema::String> {
public:
using SchemaType = schema::String;
using ValueType = std::string;
using ValueViewType = std::string_view;
private:
ValueType value;
public:
void set(ValueType &&v) { value = std::move(v); }
void set(const ValueType &v) { value = v; }
void set(const ValueViewType v) { value = std::string{v}; }
const ValueType &get() const { return value; }
};
} // namespace gin

585
source/kelgin/proto_kel.h Normal file
View File

@ -0,0 +1,585 @@
#pragma once
#include "buffer.h"
#include "message.h"
#include "stream_endian.h"
#include <iostream>
namespace gin {
/// @todo replace types with these
/*
* I'm not really sure if anyone will use a union which is
* bigger than uint32_t max. At least I hope noone would do this
*/
using msg_union_id_t = uint32_t;
using msg_array_length_t = uint64_t;
using msg_packet_length_t = uint64_t;
class ProtoKelCodec {
private:
struct ReadContext {
Buffer &buffer;
size_t offset = 0;
};
template <typename T> friend struct ProtoKelEncodeImpl;
template <typename T> friend struct ProtoKelDecodeImpl;
public:
struct Limits {
msg_packet_length_t packet_size;
Limits() : packet_size{4096} {}
Limits(msg_packet_length_t ps) : packet_size{ps} {}
};
struct Version {
size_t major;
size_t minor;
size_t security;
};
const Version version() const { return Version{0, 0, 0}; }
template <class Schema, class Container = MessageContainer<Schema>>
Error encode(typename Message<Schema, Container>::Reader reader,
Buffer &buffer);
template <class Schema, class Container = MessageContainer<Schema>>
Error decode(typename Message<Schema, Container>::Builder builder,
Buffer &buffer, const Limits &limits = Limits{});
};
template <class T> struct ProtoKelEncodeImpl;
template <class T, size_t N, class Container>
struct ProtoKelEncodeImpl<Message<schema::Primitive<T, N>, Container>> {
static Error
encode(typename Message<schema::Primitive<T, N>, Container>::Reader data,
Buffer &buffer) {
Error error = StreamValue<typename PrimitiveTypeHelper<
schema::Primitive<T, N>>::Type>::encode(data.get(), buffer);
return error;
}
static size_t
size(typename Message<schema::Primitive<T, N>, Container>::Reader) {
return StreamValue<typename PrimitiveTypeHelper<
schema::Primitive<T, N>>::Type>::size();
}
};
template <class Container>
struct ProtoKelEncodeImpl<Message<schema::String, Container>> {
static Error
encode(typename Message<schema::String, Container>::Reader data,
Buffer &buffer) {
std::string_view view = data.get();
size_t size = view.size();
Error error = buffer.writeRequireLength(sizeof(size) + size);
if (error.failed()) {
return error;
}
error = StreamValue<size_t>::encode(size, buffer);
if (error.failed()) {
return error;
}
for (size_t i = 0; i < view.size(); ++i) {
buffer.write(i) = view[i];
}
buffer.writeAdvance(view.size());
return noError();
}
static size_t
size(typename Message<schema::String, Container>::Reader reader) {
return sizeof(size_t) + reader.get().size();
}
};
template <class... T, class Container>
struct ProtoKelEncodeImpl<Message<schema::Tuple<T...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), Error>::type
encodeMembers(typename Message<schema::Tuple<T...>, Container>::Reader,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if<(i < sizeof...(T)), Error>::type
encodeMembers(typename Message<schema::Tuple<T...>, Container>::Reader data,
Buffer &buffer) {
Error error =
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
return encodeMembers<i + 1>(data, buffer);
}
static Error
encode(typename Message<schema::Tuple<T...>, Container>::Reader data,
Buffer &buffer) {
return encodeMembers<0>(data, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), size_t>::type
sizeMembers(typename Message<schema::Tuple<T...>, Container>::Reader) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), size_t>::type sizeMembers(
typename Message<schema::Tuple<T...>, Container>::Reader reader) {
return ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
size(reader.template get<i>()) +
sizeMembers<i + 1>(reader);
}
static size_t
size(typename Message<schema::Tuple<T...>, Container>::Reader reader) {
return sizeMembers<0>(reader);
}
};
template <typename... V, StringLiteral... K, class Container>
struct ProtoKelEncodeImpl<
Message<schema::Struct<schema::NamedMember<V, K>...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
encodeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMembers(
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader data,
Buffer &buffer) {
Error error =
ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
return encodeMembers<i + 1>(data, buffer);
}
static Error
encode(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader data,
Buffer &buffer) {
return encodeMembers<0>(data, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), size_t>::type
sizeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), size_t>::type sizeMembers(
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader reader) {
return ProtoKelEncodeImpl<typename Container::template ElementType<i>>::
size(reader.template get<i>()) +
sizeMembers<i + 1>(reader);
}
static size_t
size(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Reader reader) {
return sizeMembers<0>(reader);
}
};
template <typename... V, StringLiteral... K, class Container>
struct ProtoKelEncodeImpl<
Message<schema::Union<schema::NamedMember<V, K>...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
encodeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMembers(
typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader reader,
Buffer &buffer) {
if (reader.index() == i) {
Error error = StreamValue<msg_union_id_t>::encode(i, buffer);
if (error.failed()) {
return error;
}
return ProtoKelEncodeImpl<typename Container::template ElementType<
i>>::encode(reader.template get<i>(), buffer);
}
return encodeMembers<i + 1>(reader, buffer);
}
static Error
encode(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader reader,
Buffer &buffer) {
return encodeMembers<0>(reader, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), size_t>::type
sizeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), size_t>::type sizeMembers(
typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader reader) {
if (reader.index() == i) {
return ProtoKelEncodeImpl<typename Container::template ElementType<
i>>::size(reader.template get<i>());
}
return sizeMembers<i + 1>(reader);
}
/*
* Size of union id + member size
*/
static size_t
size(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Reader reader) {
return sizeof(msg_union_id_t) + sizeMembers<0>(reader);
}
};
template <class T, class Container>
struct ProtoKelEncodeImpl<Message<schema::Array<T>, Container>> {
static Error
encode(typename Message<schema::Array<T>, Container>::Reader data,
Buffer &buffer) {
msg_array_length_t array_length = data.size();
{
Error error =
StreamValue<msg_array_length_t>::encode(array_length, buffer);
if (error.failed()) {
return error;
}
}
for (size_t i = 0; i < array_length; ++i) {
Error error =
ProtoKelEncodeImpl<typename Container::ElementType>::encode(
data.get(i), buffer);
if (error.failed()) {
return error;
}
}
return noError();
}
/*
*
*/
static size_t
size(typename Message<schema::Array<T>, Container>::Reader data) {
size_t members = sizeof(msg_array_length_t);
for (size_t i = 0; i < data.size(); ++i) {
members +=
ProtoKelEncodeImpl<typename Container::ElementType>::size(
data.get(i));
}
return members;
}
};
/*
* Decode Implementations
*/
template <typename T> struct ProtoKelDecodeImpl;
template <class T, size_t N, class Container>
struct ProtoKelDecodeImpl<Message<schema::Primitive<T, N>, Container>> {
static Error
decode(typename Message<schema::Primitive<T, N>, Container>::Builder data,
Buffer &buffer) {
typename PrimitiveTypeHelper<schema::Primitive<T, N>>::Type val = 0;
Error error = StreamValue<typename PrimitiveTypeHelper<
schema::Primitive<T, N>>::Type>::decode(val, buffer);
data.set(val);
return error;
}
};
template <class Container>
struct ProtoKelDecodeImpl<Message<schema::String, Container>> {
static Error
decode(typename Message<schema::String, Container>::Builder data,
Buffer &buffer) {
size_t size = 0;
if (sizeof(size) > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
Error error = StreamValue<size_t>::decode(size, buffer);
if (error.failed()) {
return error;
}
if (size > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
std::string value;
value.resize(size);
if (size > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
for (size_t i = 0; i < value.size(); ++i) {
value[i] = buffer.read(i);
}
buffer.readAdvance(value.size());
data.set(std::move(value));
return noError();
}
};
template <class... T, class Container>
struct ProtoKelDecodeImpl<Message<schema::Tuple<T...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), Error>::type
decodeMembers(typename Message<schema::Tuple<T...>, Container>::Builder,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), Error>::type decodeMembers(
typename Message<schema::Tuple<T...>, Container>::Builder builder,
Buffer &buffer) {
Error error =
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
return decodeMembers<i + 1>(builder, buffer);
}
static Error
decode(typename Message<schema::Tuple<T...>, Container>::Builder builder,
Buffer &buffer) {
return decodeMembers<0>(builder, buffer);
}
};
template <class... V, StringLiteral... K, class Container>
struct ProtoKelDecodeImpl<
Message<schema::Struct<schema::NamedMember<V, K>...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
decodeMembers(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Builder,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type decodeMembers(
typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Builder builder,
Buffer &buffer) {
Error error =
ProtoKelDecodeImpl<typename Container::template ElementType<i>>::
decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
return decodeMembers<i + 1>(builder, buffer);
}
static Error
decode(typename Message<schema::Struct<schema::NamedMember<V, K>...>,
Container>::Builder builder,
Buffer &buffer) {
return decodeMembers<0>(builder, buffer);
}
};
template <class... V, StringLiteral... K, class Container>
struct ProtoKelDecodeImpl<
Message<schema::Union<schema::NamedMember<V, K>...>, Container>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
decodeMembers(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Builder,
Buffer &, msg_union_id_t) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type decodeMembers(
typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Builder builder,
Buffer &buffer, msg_union_id_t id) {
if (id == i) {
Error error =
ProtoKelDecodeImpl<typename Container::template ElementType<
i>>::decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
}
return decodeMembers<i + 1>(builder, buffer, id);
}
static Error
decode(typename Message<schema::Union<schema::NamedMember<V, K>...>,
Container>::Builder builder,
Buffer &buffer) {
msg_union_id_t id = 0;
Error error = StreamValue<msg_union_id_t>::decode(id, buffer);
if (error.failed()) {
return error;
}
if (id >= sizeof...(V)) {
return criticalError("Union doesn't have this many id's");
}
return decodeMembers<0>(builder, buffer, id);
}
};
template <class T, class Container>
struct ProtoKelDecodeImpl<Message<schema::Array<T>, Container>> {
static Error
decode(typename Message<schema::Array<T>, Container>::Builder data,
Buffer &buffer) {
msg_array_length_t array_length = 0;
{
Error error =
StreamValue<msg_array_length_t>::decode(array_length, buffer);
if (error.failed()) {
return error;
}
}
for (size_t i = 0; i < array_length; ++i) {
Error error =
ProtoKelDecodeImpl<typename Container::ElementType>::decode(
data.init(i), buffer);
if (error.failed()) {
return error;
}
}
return noError();
}
};
template <class Schema, class Container>
Error ProtoKelCodec::encode(typename Message<Schema, Container>::Reader reader,
Buffer &buffer) {
BufferView view{buffer};
msg_packet_length_t packet_length =
ProtoKelEncodeImpl<Message<Schema, Container>>::size(reader);
// Check the size of the packet for the first
// message length description
Error error =
view.writeRequireLength(packet_length + sizeof(msg_packet_length_t));
if (error.failed()) {
return error;
}
{
Error error =
StreamValue<msg_packet_length_t>::encode(packet_length, view);
if (error.failed()) {
return error;
}
}
{
Error error = ProtoKelEncodeImpl<Message<Schema, Container>>::encode(
reader, view);
if (error.failed()) {
return error;
}
}
buffer.writeAdvance(view.writeOffset());
return noError();
}
template <class Schema, class Container>
Error ProtoKelCodec::decode(
typename Message<Schema, Container>::Builder builder, Buffer &buffer,
const Limits &limits) {
BufferView view{buffer};
msg_packet_length_t packet_length = 0;
{
Error error =
StreamValue<msg_packet_length_t>::decode(packet_length, view);
if (error.failed()) {
return error;
}
}
if (packet_length > limits.packet_size) {
return criticalError(
[packet_length]() {
return std::string{"Packet size too big: "} +
std::to_string(packet_length);
},
"Packet size too big");
}
{
Error error = ProtoKelDecodeImpl<Message<Schema, Container>>::decode(
builder, view);
if (error.failed()) {
return error;
}
}
{
if (ProtoKelEncodeImpl<Message<Schema, Container>>::size(
builder.asReader()) != packet_length) {
return criticalError("Bad packet format");
}
}
buffer.readAdvance(view.readOffset());
return noError();
}
} // namespace gin

51
source/kelgin/schema.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include "string_literal.h"
namespace gin {
namespace schema {
template <class T, StringLiteral Literal> struct NamedMember {};
template <class... T> struct Struct;
template <class... V, StringLiteral... K>
struct Struct<NamedMember<V, K>...> {};
template <class... T> struct Union;
template <class... V, StringLiteral... K> struct Union<NamedMember<V, K>...> {};
template <class T> struct Array {};
template <class... T> struct Tuple {};
struct String {};
struct SignedInteger {};
struct UnsignedInteger {};
struct FloatingPoint {};
template <class T, size_t N> struct Primitive {
static_assert(((std::is_same_v<T, SignedInteger> ||
std::is_same_v<T, UnsignedInteger>)&&(N == 1 || N == 2 ||
N == 4 || N == 8)) ||
(std::is_same_v<T, FloatingPoint> && (N == 4 || N == 8)),
"Primitive Type is not supported");
};
using Int8 = Primitive<SignedInteger, 1>;
using Int16 = Primitive<SignedInteger, 2>;
using Int32 = Primitive<SignedInteger, 4>;
using Int64 = Primitive<SignedInteger, 8>;
using UInt8 = Primitive<UnsignedInteger, 1>;
using UInt16 = Primitive<UnsignedInteger, 2>;
using UInt32 = Primitive<UnsignedInteger, 4>;
using UInt64 = Primitive<UnsignedInteger, 8>;
using Float32 = Primitive<FloatingPoint, 4>;
using Float64 = Primitive<FloatingPoint, 8>;
} // namespace schema
} // namespace gin

View File

@ -6,6 +6,8 @@
#include <cstdint>
#include <cstring>
#include <iostream>
namespace gin {
/**
* Helper class to encode/decode any primtive type into/from litte endian.
@ -23,7 +25,7 @@ public:
}
inline static Error encode(const T &val, Buffer &buffer) {
uint8_t &raw = reinterpret_cast<uint8_t &>(val);
const uint8_t &raw = reinterpret_cast<const uint8_t &>(val);
return buffer.push(raw, sizeof(T));
}
@ -40,7 +42,7 @@ public:
uint16_t raw = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
raw |= buffer.read(i) << (i * 8);
raw |= (static_cast<uint16_t>(buffer.read(i)) << (i * 8));
}
memcpy(&val, &raw, sizeof(T));
buffer.readAdvance(sizeof(T));
@ -78,7 +80,7 @@ public:
uint32_t raw = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
raw |= buffer.read(i) << (i * 8);
raw |= (static_cast<uint32_t>(buffer.read(i)) << (i * 8));
}
memcpy(&val, &raw, sizeof(T));
buffer.readAdvance(sizeof(T));
@ -116,8 +118,9 @@ public:
uint64_t raw = 0;
for (size_t i = 0; i < sizeof(T); ++i) {
raw |= buffer.read(i) << (i * 8);
raw |= (static_cast<uint64_t>(buffer.read(i)) << (i * 8));
}
memcpy(&val, &raw, sizeof(T));
buffer.readAdvance(sizeof(T));
@ -146,4 +149,4 @@ public:
template <typename T> using StreamValue = ShiftStreamValue<T>;
} // namespace gin
} // namespace gin

View File

@ -0,0 +1,40 @@
#pragma once
#include <array>
#include <string_view>
namespace gin {
/**
* Helper object which creates a templated string from the provided string
* literal. It guarantees compile time uniqueness and thus allows using strings
* in template parameters.
*/
template <class CharT, size_t N> class StringLiteral {
public:
constexpr StringLiteral(const CharT (&input)[N]) noexcept {
for (size_t i = 0; i < N; ++i) {
data[i] = input[i];
}
}
std::array<CharT, N> data{};
constexpr std::string_view view() const noexcept {
return std::string_view{data.data()};
}
constexpr bool
operator==(const StringLiteral<CharT, N> &) const noexcept = default;
template <class CharTR, size_t NR>
constexpr bool
operator==(const StringLiteral<CharTR, NR> &) const noexcept {
return false;
}
};
template <typename T, T... Chars>
constexpr gin::StringLiteral<T, sizeof...(Chars)> operator""_key() {
return gin::StringLiteral<T, sizeof...(Chars) + 1u>{Chars..., '\0'};
}
} // namespace gin

266
source/kelgin/tls/tls.cpp Normal file
View File

@ -0,0 +1,266 @@
#include "tls.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include "io_helpers.h"
#include <cassert>
#include <iostream>
namespace gin {
class Tls::Impl {
public:
gnutls_certificate_credentials_t xcred;
public:
Impl() {
gnutls_global_init();
gnutls_certificate_allocate_credentials(&xcred);
gnutls_certificate_set_x509_system_trust(xcred);
}
~Impl() {
gnutls_certificate_free_credentials(xcred);
gnutls_global_deinit();
}
};
static ssize_t kelgin_tls_push_func(gnutls_transport_ptr_t p, const void *data,
size_t size);
static ssize_t kelgin_tls_pull_func(gnutls_transport_ptr_t p, void *data, size_t size);
Tls::Tls() : impl{heap<Tls::Impl>()} {}
Tls::~Tls() {}
Tls::Impl &Tls::getImpl() { return *impl; }
class TlsIoStream final : public IoStream {
private:
Own<IoStream> internal;
gnutls_session_t session_handle;
public:
TlsIoStream(Own<IoStream> internal_) : internal{std::move(internal_)} {}
~TlsIoStream() { gnutls_bye(session_handle, GNUTLS_SHUT_RDWR); }
ErrorOr<size_t> read(void *buffer, size_t length) override {
ssize_t size = gnutls_record_recv(session_handle, buffer, length);
if (size < 0) {
if(gnutls_error_is_fatal(size) == 0){
return recoverableError([size](){return std::string{"Read recoverable Error "}+std::string{gnutls_strerror(size)};}, "Error read r");
}else{
return criticalError([size](){return std::string{"Read critical Error "}+std::string{gnutls_strerror(size)};}, "Error read c");
}
}else if(size == 0){
return criticalError("Disconnected");
}
return static_cast<size_t>(length);
}
Conveyor<void> readReady() override { return internal->readReady(); }
Conveyor<void> onReadDisconnected() override {
return internal->onReadDisconnected();
}
ErrorOr<size_t> write(const void *buffer, size_t length) override {
ssize_t size = gnutls_record_send(session_handle, buffer, length);
if(size < 0){
if(gnutls_error_is_fatal(size) == 0){
return recoverableError([size](){return std::string{"Write recoverable Error "}+std::string{gnutls_strerror(size)} + " " + std::to_string(size);}, "Error write r");
}else{
return criticalError([size](){return std::string{"Write critical Error "}+std::string{gnutls_strerror(size)} + " " + std::to_string(size);}, "Error write c");
}
}
return static_cast<size_t>(size);
}
Conveyor<void> writeReady() override { return internal->writeReady(); }
gnutls_session_t &session() { return session_handle; }
};
TlsServer::TlsServer(Own<Server> srv) : internal{std::move(srv)} {}
Conveyor<Own<IoStream>> TlsServer::accept() {
GIN_ASSERT(internal) { return Conveyor<Own<IoStream>>{nullptr, nullptr}; }
return internal->accept().then([](Own<IoStream> stream) -> Own<IoStream> {
/// @todo handshake
return heap<TlsIoStream>(std::move(stream));
});
}
namespace {
/*
* Small helper for setting up the nonblocking connection handshake
*/
struct TlsClientStreamHelper {
public:
Own<ConveyorFeeder<Own<IoStream>>> feeder;
SinkConveyor connection_sink;
SinkConveyor stream_reader;
SinkConveyor stream_writer;
Own<TlsIoStream> stream = nullptr;
public:
TlsClientStreamHelper(Own<ConveyorFeeder<Own<IoStream>>> f):
feeder{std::move(f)}
{}
void setupTurn(){
GIN_ASSERT(stream){
return;
}
stream_reader = stream->readReady().then([this](){
turn();
}).sink();
stream_writer = stream->writeReady().then([this](){
turn();
}).sink();
}
void turn(){
if(stream){
// Guarantee that the receiving end is already setup
GIN_ASSERT(feeder){
return;
}
auto &session = stream->session();
int ret;
do {
ret = gnutls_handshake(session);
} while ( (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) && gnutls_error_is_fatal(ret) == 0);
if(gnutls_error_is_fatal(ret)){
feeder->fail(criticalError("Couldn't create Tls connection"));
stream = nullptr;
}else if(ret == GNUTLS_E_SUCCESS){
feeder->feed(std::move(stream));
}
}
}
};
}
TlsNetworkAddress::TlsNetworkAddress(Own<NetworkAddress> net_addr, const std::string& host_name_, Tls &tls_)
: internal{std::move(net_addr)}, host_name{host_name_}, tls{tls_} {}
Own<Server> TlsNetworkAddress::listen() {
GIN_ASSERT(internal) { return nullptr; }
return heap<TlsServer>(internal->listen());
}
Conveyor<Own<IoStream>> TlsNetworkAddress::connect() {
GIN_ASSERT(internal) { return Conveyor<Own<IoStream>>{nullptr, nullptr}; }
// Helper setups
auto caf = newConveyorAndFeeder<Own<IoStream>>();
Own<TlsClientStreamHelper> helper = heap<TlsClientStreamHelper>(std::move(caf.feeder));
TlsClientStreamHelper* hlp_ptr = helper.get();
// Conveyor entangled structure
auto prim_conv = internal->connect().then([this, hlp_ptr](
Own<IoStream> stream) -> ErrorOr<void> {
IoStream* inner_stream = stream.get();
auto tls_stream = heap<TlsIoStream>(std::move(stream));
auto &session = tls_stream->session();
gnutls_init(&session, GNUTLS_CLIENT);
const std::string &addr = this->address();
gnutls_server_name_set(session, GNUTLS_NAME_DNS, addr.c_str(),
addr.size());
gnutls_set_default_priority(session);
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
tls.getImpl().xcred);
gnutls_session_set_verify_cert(session, addr.c_str(), 0);
gnutls_transport_set_ptr(session, reinterpret_cast<gnutls_transport_ptr_t>(inner_stream));
gnutls_transport_set_push_function(session, kelgin_tls_push_func);
gnutls_transport_set_pull_function(session, kelgin_tls_pull_func);
// gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
hlp_ptr->stream = std::move(tls_stream);
hlp_ptr->setupTurn();
hlp_ptr->turn();
return Void{};
});
helper->connection_sink = prim_conv.sink();
return caf.conveyor.attach(std::move(helper));
}
static ssize_t kelgin_tls_push_func(gnutls_transport_ptr_t p, const void *data,
size_t size) {
IoStream *stream = reinterpret_cast<IoStream *>(p);
if (!stream) {
return -1;
}
ErrorOr<size_t> length = stream->write(data, size);
if (length.isError() || !length.isValue()) {
return -1;
}
return static_cast<ssize_t>(length.value());
}
static ssize_t kelgin_tls_pull_func(gnutls_transport_ptr_t p, void *data, size_t size) {
IoStream *stream = reinterpret_cast<IoStream *>(p);
if (!stream) {
return -1;
}
ErrorOr<size_t> length = stream->read(data, size);
if (length.isError() || !length.isValue()) {
return -1;
}
return static_cast<ssize_t>(length.value());
}
const std::string &TlsNetworkAddress::address() const {
assert(internal);
return internal->address();
}
uint16_t TlsNetworkAddress::port() const {
assert(internal);
return internal->port(); }
std::string TlsNetworkAddress::toString() const { return internal->toString(); }
TlsNetwork::TlsNetwork(Network &network) : internal{network} {}
Conveyor<Own<NetworkAddress>> TlsNetwork::parseAddress(const std::string &addr,
uint16_t port) {
return internal.parseAddress(addr, port)
.then(
[this, addr, port](Own<NetworkAddress> net) -> Own<NetworkAddress> {
assert(net);
return heap<TlsNetworkAddress>(std::move(net), addr, tls);
});
}
std::optional<Own<TlsNetwork>> setupTlsNetwork(Network &network) {
return std::nullopt;
}
} // namespace gin

69
source/kelgin/tls/tls.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include "../common.h"
#include "../io.h"
#include <optional>
#include <variant>
namespace gin {
class Tls {
private:
class Impl;
Own<Impl> impl;
public:
Tls();
~Tls();
class Options {
public:
};
Impl &getImpl();
};
class TlsServer final : public Server {
private:
Own<Server> internal;
public:
TlsServer(Own<Server> srv);
Conveyor<Own<IoStream>> accept() override;
};
class TlsNetworkAddress final : public NetworkAddress {
private:
Own<NetworkAddress> internal;
std::string host_name;
Tls &tls;
public:
TlsNetworkAddress(Own<NetworkAddress> net_addr, const std::string& host_name_, Tls &tls_);
Own<Server> listen() override;
Conveyor<Own<IoStream>> connect() override;
std::string toString() const override;
const std::string &address() const override;
uint16_t port() const override;
};
class TlsNetwork final : public Network {
private:
Tls tls;
Network &internal;
public:
TlsNetwork(Network &network);
Conveyor<Own<NetworkAddress>> parseAddress(const std::string &addr,
uint16_t port = 0) override;
};
std::optional<Own<TlsNetwork>> setupTlsNetwork(Network &network);
} // namespace gin

View File

@ -1,405 +0,0 @@
#pragma once
#include <cstdint>
#include <tuple>
#include <variant>
#include "common.h"
#include "string_literal.h"
namespace gin {
class Message {
protected:
bool set_explicitly = false;
public:
template <typename T> T &as() {
static_assert(std::is_base_of<Message, T>());
return reinterpret_cast<T &>(*this);
}
template <typename T> const T &as() const {
static_assert(std::is_base_of<Message, T>());
return reinterpret_cast<const T &>(*this);
}
};
/*
* Representing all primitive types
*/
template <typename T> class MessagePrimitive : public Message {
private:
T value;
friend class Builder;
friend class Reader;
public:
MessagePrimitive() = default;
class Reader;
class Builder {
private:
MessagePrimitive<T> &message;
public:
Builder(MessagePrimitive<T> &message) : message{message} {}
constexpr void set(const T &value) {
message.value = value;
message.set_explicitly = true;
}
Reader asReader() { return Reader{message}; }
};
class Reader {
private:
MessagePrimitive<T> &message;
public:
Reader(MessagePrimitive<T> &message) : message{message} {}
const T &get() const { return message.value; }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() { return Builder{message}; }
};
};
template <> class MessagePrimitive<std::string> : public Message {
private:
std::string value;
friend class Builder;
friend class Reader;
public:
MessagePrimitive() = default;
class Reader;
class Builder {
private:
MessagePrimitive<std::string> &message;
public:
Builder(MessagePrimitive<std::string> &message) : message{message} {}
void set(std::string_view value) {
message.value = std::string{value};
message.set_explicitly = true;
}
/*
void set(std::string &&value) {
message.value = std::move(value);
message.set_explicitly = true;
}
*/
Reader asReader() { return Reader{message}; }
};
class Reader {
private:
MessagePrimitive<std::string> &message;
public:
Reader(MessagePrimitive<std::string> &message) : message{message} {}
std::string_view get() const { return std::string_view{message.value}; }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() { return Builder{message}; }
};
};
template <typename... Args> class MessageList : public Message {
private:
using tuple_type = std::tuple<Args...>;
tuple_type elements;
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageList<Args...> &message;
public:
Builder(MessageList<Args...> &message) : message{message} {
message.set_explicitly = true;
}
template <size_t i>
constexpr typename std::tuple_element_t<i, tuple_type>::Builder init() {
std::tuple_element_t<i, tuple_type> &msg_ref =
std::get<i>(message.elements);
return
typename std::tuple_element_t<i, tuple_type>::Builder{msg_ref};
}
Reader asReader() { return Reader{message}; }
};
class Reader {
private:
MessageList<Args...> &message;
public:
Reader(MessageList<Args...> &message) : message{message} {}
template <size_t i>
constexpr typename std::tuple_element_t<i, tuple_type>::Reader get() {
return std::get<i>(message.elements);
}
size_t size() const { return std::tuple_size<tuple_type>::value; }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() { return Reader{message}; }
};
};
template <typename T, typename K> struct MessageStructMember;
template <typename T, typename C, C... Chars>
struct MessageStructMember<T, StringLiteral<C, Chars...>> {
T value;
};
/*
* Helper structs which retrieve
* the index of a parameter pack based on the index
* or
* a type based on the index
* Pass N as the index for the desired type
* or
* a type to get the first occurence of a type index
*/
template <size_t N, typename... T> struct ParameterPackType;
template <typename T, typename... TL> struct ParameterPackType<0, T, TL...> {
using type = T;
};
template <size_t N, typename T, typename... TL>
struct ParameterPackType<N, T, TL...> {
using type = typename ParameterPackType<N - 1, TL...>::type;
};
template <typename T, typename... TL> struct ParameterPackIndex;
template <typename T, typename... TL> struct ParameterPackIndex<T, T, TL...> {
static constexpr size_t value = 0u;
};
template <typename T, typename TL0, typename... TL>
struct ParameterPackIndex<T, TL0, TL...> {
static constexpr size_t value = 1u + ParameterPackIndex<T, TL...>::value;
};
template <typename... T> class MessageStruct;
/*
* Since no value is retrieved from the keys, I only need a value tuple
*/
template <typename... V, typename... K>
class MessageStruct<MessageStructMember<V, K>...> : public Message {
private:
using value_type = std::tuple<V...>;
value_type values;
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageStruct<MessageStructMember<V, K>...> &message;
public:
Builder(MessageStruct<MessageStructMember<V, K>...> &message)
: message{message} {
message.set_explicitly = true;
}
template <size_t i>
constexpr typename std::tuple_element_t<i, value_type>::Builder init() {
std::tuple_element_t<i, value_type> &msg_ref =
std::get<i>(message.values);
return
typename std::tuple_element_t<i, value_type>::Builder{msg_ref};
}
template <typename T>
constexpr
typename std::tuple_element_t<ParameterPackIndex<T, K...>::value,
value_type>::Builder
init() {
std::tuple_element_t<ParameterPackIndex<T, K...>::value, value_type>
&msg_ref = std::get<ParameterPackIndex<T, K...>::value>(
message.values);
return typename std::tuple_element_t<
ParameterPackIndex<T, K...>::value, value_type>::Builder{
msg_ref};
}
Reader asReader() { return Reader{message}; }
};
class Reader {
private:
MessageStruct<MessageStructMember<V, K>...> &message;
public:
Reader(MessageStruct<MessageStructMember<V, K>...> &message)
: message{message} {}
template <size_t i>
constexpr typename std::tuple_element_t<i, value_type>::Reader get() {
std::tuple_element_t<i, value_type> &msg_ref =
std::get<i>(message.values);
return
typename std::tuple_element_t<i, value_type>::Reader{msg_ref};
}
template <typename T>
constexpr
typename std::tuple_element_t<ParameterPackIndex<T, K...>::value,
value_type>::Reader
get() {
std::tuple_element_t<ParameterPackIndex<T, K...>::value, value_type>
&msg_ref = std::get<ParameterPackIndex<T, K...>::value>(
message.values);
return typename std::tuple_element_t<
ParameterPackIndex<T, K...>::value, value_type>::Reader{
msg_ref};
}
constexpr size_t size() { return std::tuple_size<value_type>::value; }
Builder asBuilder() { return Builder{message}; }
};
};
template <typename T, typename K> struct MessageUnionMember;
template <typename T, typename C, C... Chars>
struct MessageUnionMember<T, StringLiteral<C, Chars...>> {
T value;
};
template <typename... T> class MessageUnion;
/// @todo copied from MessageStruct, but the acces is different, since
/// only one value can be set at the same time.
template <typename... V, typename... K>
class MessageUnion<MessageUnionMember<V, K>...> : public Message {
private:
using value_type = std::variant<MessageUnionMember<V, K>...>;
value_type values;
friend class Builder;
friend class Reader;
public:
class Reader;
class Builder {
private:
MessageUnion<MessageUnionMember<V, K>...> &message;
public:
Builder(MessageUnion<MessageUnionMember<V, K>...> &message)
: message{message} {
message.set_explicitly = true;
}
template <size_t i>
constexpr typename ParameterPackType<i, V...>::type::Builder init() {
message.values =
typename std::variant_alternative_t<i, value_type>{};
typename ParameterPackType<i, V...>::type &msg_ref =
std::get<i>(message.values).value;
return typename ParameterPackType<i, V...>::type::Builder{msg_ref};
}
template <typename T>
constexpr typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type::Builder
init() {
message.values = typename std::variant_alternative_t<
ParameterPackIndex<T, K...>::value, value_type>{};
typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type &msg_ref =
std::get<ParameterPackIndex<T, K...>::value>(message.values)
.value;
return
typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type::Builder{msg_ref};
}
Reader asReader() { return Reader{message}; }
};
class Reader {
private:
MessageUnion<MessageUnionMember<V, K>...> &message;
public:
Reader(MessageUnion<MessageUnionMember<V, K>...> &message)
: message{message} {}
template <size_t i>
constexpr typename ParameterPackType<i, V...>::type::Reader get() {
typename ParameterPackType<i, V...>::type &msg_ref =
std::get<i>(message.values).value;
return typename ParameterPackType<i, V...>::type::Reader{msg_ref};
}
template <typename T>
constexpr typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type::Reader
get() {
typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type &msg_ref =
std::get<ParameterPackIndex<T, K...>::value>(message.values)
.value;
return
typename ParameterPackType<ParameterPackIndex<T, K...>::value,
V...>::type::Reader{msg_ref};
}
template <typename T> constexpr bool holdsAlternative() {
return std::holds_alternative<std::variant_alternative_t<
ParameterPackIndex<T, K...>::value, value_type>>(
message.values);
}
size_t index() const { return message.values.index(); }
constexpr size_t size() { return std::variant_size<value_type>::value; }
Builder asBuilder() { return Builder{message}; }
};
};
class MessageReader {
public:
virtual ~MessageReader() = default;
};
class MessageBuilder {
private:
Own<Message> root_message = nullptr;
public:
virtual ~MessageBuilder() = default;
template <typename MessageRoot> typename MessageRoot::Builder initRoot() {
root_message = std::make_unique<MessageRoot>();
MessageRoot &msg_ref = root_message->as<MessageRoot>();
return typename MessageRoot::Builder{msg_ref};
}
};
inline MessageBuilder heapMessageBuilder() { return MessageBuilder{}; }
} // namespace gin

View File

@ -1,22 +0,0 @@
#include "message_dynamic.h"
namespace gin {
template <>
DynamicMessage::Type DynamicMessagePrimitive<uint64_t>::type() const {
return DynamicMessage::Type::Unsigned;
};
template <>
DynamicMessage::Type DynamicMessagePrimitive<int64_t>::type() const {
return DynamicMessage::Type::Signed;
};
template <>
DynamicMessage::Type DynamicMessagePrimitive<std::string>::type() const {
return DynamicMessage::Type::String;
};
template <> DynamicMessage::Type DynamicMessagePrimitive<bool>::type() const {
return DynamicMessage::Type::Bool;
};
template <> DynamicMessage::Type DynamicMessagePrimitive<double>::type() const {
return DynamicMessage::Type::Double;
};
} // namespace gin

View File

@ -1,268 +0,0 @@
#pragma once
#include <cstdint>
#include <deque>
#include <map>
#include <string>
#include "common.h"
/// @todo Move implementation to cpp file
namespace gin {
/*
* Base class for each message data structure
*/
class DynamicMessage {
protected:
/*
* The encoder and decoders use this as a hint if this was set by a default
* value or not
*/
bool set_explicitly = false;
public:
/*
* Later use for dynamic access
*/
enum class Type : uint16_t {
Null,
Struct,
List,
Array,
Union,
String,
Signed,
Unsigned,
Bool,
Double
};
virtual ~DynamicMessage() = default;
virtual Type type() const = 0;
template <typename T> T &as() {
static_assert(std::is_base_of<DynamicMessage, T>());
return reinterpret_cast<T &>(*this);
}
template <typename T> const T &as() const {
static_assert(std::is_base_of<DynamicMessage, T>());
return reinterpret_cast<const T &>(*this);
}
class DynamicReader;
class DynamicBuilder {
private:
DynamicMessage &message;
public:
DynamicBuilder(DynamicMessage &message) : message{message} {}
DynamicReader asReader() const { return DynamicReader{message}; }
DynamicMessage::Type type() const { return message.type(); }
template <typename T> typename T::Builder as() {
static_assert(std::is_base_of<DynamicMessage, T>());
return typename T::Builder{reinterpret_cast<T &>(message)};
}
};
class DynamicReader {
private:
DynamicMessage &message;
public:
DynamicReader(DynamicMessage &message) : message{message} {}
DynamicBuilder asBuilder() const { return DynamicBuilder{message}; }
DynamicMessage::Type type() const { return message.type(); }
template <typename T> typename T::Reader as() {
static_assert(std::is_base_of<DynamicMessage, T>());
return typename T::Reader{reinterpret_cast<T &>(message)};
}
};
};
class DynamicMessageNull : public DynamicMessage {
public:
DynamicMessage::Type type() const override {
return DynamicMessage::Type::Null;
}
};
static DynamicMessageNull dynamicNullMessage;
template <typename T> class DynamicMessagePrimitive : public DynamicMessage {
private:
T value;
friend class Builder;
friend class Reader;
public:
DynamicMessagePrimitive() = default;
DynamicMessage::Type type() const override;
class Reader;
class Builder {
private:
DynamicMessagePrimitive<T> &message;
public:
Builder(DynamicMessagePrimitive<T> &message) : message{message} {}
constexpr void set(const T &value) {
message.value = value;
message.set_explicitly = true;
}
constexpr void set(T &&value) {
message.value = std::move(value);
message.set_explicitly = true;
}
Reader asReader() const { return Reader{message}; }
};
class Reader {
private:
DynamicMessagePrimitive<T> &message;
public:
Reader(DynamicMessagePrimitive<T> &message) : message{message} {}
constexpr const T &get() { return message.value; }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() const { return Builder{message}; }
};
};
using DynamicMessageString = DynamicMessagePrimitive<std::string>;
using DynamicMessageSigned = DynamicMessagePrimitive<int64_t>;
using DynamicMessageUnsigned = DynamicMessagePrimitive<uint64_t>;
using DynamicMessageBool = DynamicMessagePrimitive<bool>;
using DynamicMessageDouble = DynamicMessagePrimitive<double>;
class DynamicMessageStruct : public DynamicMessage {
private:
std::map<std::string, Own<DynamicMessage>, std::less<>> messages;
friend class Builder;
friend class Reader;
public:
DynamicMessage::Type type() const override {
return DynamicMessage::Type::Struct;
}
class Reader;
class Builder {
private:
DynamicMessageStruct &message;
public:
Builder(DynamicMessageStruct &message) : message{message} {
message.set_explicitly = true;
}
template <typename T> typename T::Builder init(const std::string &key) {
Own<T> msg = std::make_unique<T>();
typename T::Builder builder{*msg};
/*auto insert = */ message.messages.insert(
std::make_pair(key, std::move(msg)));
return builder;
}
DynamicMessage::DynamicBuilder init(const std::string &key,
Own<DynamicMessage> &&msg) {
DynamicMessage::DynamicBuilder builder{*msg};
message.messages.insert(std::make_pair(key, std::move(msg)));
return builder;
}
Reader asReader() const { return Reader{message}; }
};
class Reader {
private:
DynamicMessageStruct &message;
public:
Reader(DynamicMessageStruct &message) : message{message} {}
DynamicMessage::DynamicReader get(std::string_view key) {
auto find = message.messages.find(key);
if (find != message.messages.end()) {
return DynamicMessage::DynamicReader{*(find->second)};
} else {
return DynamicMessage::DynamicReader{dynamicNullMessage};
}
}
size_t size() const { return message.messages.size(); }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() const { return Builder{message}; }
};
};
class DynamicMessageList : public DynamicMessage {
private:
std::deque<Own<DynamicMessage>> messages;
friend class Builder;
friend class Reader;
public:
DynamicMessage::Type type() const override {
return DynamicMessage::Type::List;
}
class Reader;
class Builder {
private:
DynamicMessageList &message;
public:
Builder(DynamicMessageList &message) : message{message} {
message.set_explicitly = true;
}
template <typename T> typename T::Builder push() {
static_assert(std::is_base_of<DynamicMessage, T>());
Own<T> msg = std::make_unique<T>();
typename T::Builder builder{*msg};
message.messages.push_back(std::move(msg));
return builder;
}
Reader asReader() const { return Reader{message}; }
};
class Reader {
private:
DynamicMessageList &message;
public:
Reader(DynamicMessageList &message) : message{message} {}
DynamicMessage::DynamicReader get(size_t element) {
if (element < message.messages.size()) {
return DynamicMessage::DynamicReader{
*(message.messages[element])};
} else {
return DynamicMessage::DynamicReader{dynamicNullMessage};
}
}
size_t size() const { return message.messages.size(); }
bool isSetExplicitly() const { return message.set_explicitly; }
Builder asBuilder() const { return Builder{message}; }
};
};
} // namespace gin

View File

@ -1,428 +0,0 @@
#pragma once
#include "buffer.h"
#include "message.h"
#include "stream_endian.h"
#include <iostream>
namespace gin {
/// @todo replace types with these
/*
* I'm not really sure if anyone will use a union which is
* bigger than uint32_t max. At least I hope noone would do this
*/
using msg_union_id_t = uint32_t;
using msg_packet_length_t = uint64_t;
template <typename T> struct ProtoKelEncodeImpl;
template <typename T> struct ProtoKelEncodeImpl<MessagePrimitive<T>> {
static Error encode(typename MessagePrimitive<T>::Reader data,
Buffer &buffer) {
Error error = StreamValue<T>::encode(data.get(), buffer);
return error;
}
static size_t size(typename MessagePrimitive<T>::Reader) {
return StreamValue<T>::size();
}
};
template <> struct ProtoKelEncodeImpl<MessagePrimitive<std::string>> {
static Error encode(typename MessagePrimitive<std::string>::Reader data,
Buffer &buffer) {
std::string_view view = data.get();
size_t size = view.size();
Error error = buffer.writeRequireLength(sizeof(size) + size);
if (error.failed()) {
return error;
}
error = StreamValue<size_t>::encode(size, buffer);
if (error.failed()) {
return error;
}
for (size_t i = 0; i < view.size(); ++i) {
buffer.write(i) = view[i];
}
buffer.writeAdvance(view.size());
return noError();
}
static size_t size(typename MessagePrimitive<std::string>::Reader reader) {
return sizeof(size_t) + reader.get().size();
}
};
template <typename... T> struct ProtoKelEncodeImpl<MessageList<T...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), Error>::type
encodeMembers(typename MessageList<T...>::Reader, Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), Error>::type
encodeMembers(typename MessageList<T...>::Reader data, Buffer &buffer) {
Error error =
ProtoKelEncodeImpl<typename ParameterPackType<i, T...>::type>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
return encodeMembers<i + 1>(data, buffer);
}
static Error encode(typename MessageList<T...>::Reader data,
Buffer &buffer) {
return encodeMembers<0>(data, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), size_t>::type
sizeMembers(typename MessageList<T...>::Reader data) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), size_t>::type
sizeMembers(typename MessageList<T...>::Reader reader) {
return ProtoKelEncodeImpl<typename ParameterPackType<i, T...>::type>::
size(reader.template get<i>()) +
sizeMembers<i + 1>(reader);
}
static size_t size(typename MessageList<T...>::Reader reader) {
return sizeMembers<0>(reader);
}
};
template <typename... V, typename... K>
struct ProtoKelEncodeImpl<MessageStruct<MessageStructMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
encodeMembers(typename MessageStruct<MessageStructMember<V, K>...>::Reader,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMembers(
typename MessageStruct<MessageStructMember<V, K>...>::Reader data,
Buffer &buffer) {
Error error =
ProtoKelEncodeImpl<typename ParameterPackType<i, V...>::type>::
encode(data.template get<i>(), buffer);
if (error.failed()) {
return error;
}
return encodeMembers<i + 1>(data, buffer);
}
static Error
encode(typename MessageStruct<MessageStructMember<V, K>...>::Reader data,
Buffer &buffer) {
return encodeMembers<0>(data, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), size_t>::type
sizeMembers(typename MessageStruct<MessageStructMember<V, K>...>::Reader) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), size_t>::type
sizeMembers(typename MessageStruct<MessageStructMember<V, K>...>::Reader
reader) {
return ProtoKelEncodeImpl<typename ParameterPackType<i, V...>::type>::
size(reader.template get<i>()) +
sizeMembers<i + 1>(reader);
}
static size_t
size(typename MessageStruct<MessageStructMember<V, K>...>::Reader reader) {
return sizeMembers<0>(reader);
}
};
template <typename... V, typename... K>
struct ProtoKelEncodeImpl<MessageUnion<MessageUnionMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
encodeMembers(typename MessageUnion<MessageUnionMember<V, K>...>::Reader,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type encodeMembers(
typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader,
Buffer &buffer) {
if (reader.template holdsAlternative<
typename ParameterPackType<i, K...>::type>()) {
Error error = StreamValue<msg_union_id_t>::encode(i, buffer);
if (error.failed()) {
return error;
}
return ProtoKelEncodeImpl<typename ParameterPackType<
i, V...>::type>::encode(reader.template get<i>(), buffer);
}
return encodeMembers<i + 1>(reader, buffer);
}
static Error
encode(typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader,
Buffer &buffer) {
return encodeMembers<0>(reader, buffer);
}
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), size_t>::type
sizeMembers(typename MessageUnion<MessageUnionMember<V, K>...>::Reader) {
return 0;
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), size_t>::type sizeMembers(
typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader) {
if (reader.template holdsAlternative<
typename ParameterPackType<i, K...>::type>()) {
return ProtoKelEncodeImpl<typename ParameterPackType<
i, V...>::type>::size(reader.template get<i>());
}
return sizeMembers<i + 1>(reader);
}
/*
* Size of union id + member size
*/
static size_t
size(typename MessageUnion<MessageUnionMember<V, K>...>::Reader reader) {
return sizeof(uint32_t) + sizeMembers<0>(reader);
}
};
/*
* Decode Implementations
*/
template <typename T> struct ProtoKelDecodeImpl;
template <typename T> struct ProtoKelDecodeImpl<MessagePrimitive<T>> {
static Error decode(typename MessagePrimitive<T>::Builder data,
Buffer &buffer) {
T val = 0;
Error error = StreamValue<T>::decode(val, buffer);
data.set(val);
return error;
}
};
template <> struct ProtoKelDecodeImpl<MessagePrimitive<std::string>> {
static Error decode(typename MessagePrimitive<std::string>::Builder data,
Buffer &buffer) {
size_t size = 0;
if (sizeof(size) > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
Error error = StreamValue<size_t>::decode(size, buffer);
if (error.failed()) {
return error;
}
if (size > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
std::string value;
value.resize(size);
if (size > buffer.readCompositeLength()) {
return recoverableError("Buffer too small");
}
for (size_t i = 0; i < value.size(); ++i) {
value[i] = buffer.read(i);
}
buffer.readAdvance(value.size());
data.set(std::move(value));
return noError();
}
};
template <typename... T> struct ProtoKelDecodeImpl<MessageList<T...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(T), Error>::type
decodeMembers(typename MessageList<T...>::Builder, Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(T), Error>::type
decodeMembers(typename MessageList<T...>::Builder builder,
Buffer &buffer) {
Error error =
ProtoKelDecodeImpl<typename ParameterPackType<i, T...>::type>::
decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
return decodeMembers<i + 1>(builder, buffer);
}
static Error decode(typename MessageList<T...>::Builder builder,
Buffer &buffer) {
return decodeMembers<0>(builder, buffer);
}
};
template <typename... V, typename... K>
struct ProtoKelDecodeImpl<MessageStruct<MessageStructMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
decodeMembers(typename MessageStruct<MessageStructMember<V, K>...>::Builder,
Buffer &) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type decodeMembers(
typename MessageStruct<MessageStructMember<V, K>...>::Builder
builder,
Buffer &buffer) {
Error error =
ProtoKelDecodeImpl<typename ParameterPackType<i, V...>::type>::
decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
return decodeMembers<i + 1>(builder, buffer);
}
static Error decode(
typename MessageStruct<MessageStructMember<V, K>...>::Builder builder,
Buffer &buffer) {
return decodeMembers<0>(builder, buffer);
}
};
template <typename... V, typename... K>
struct ProtoKelDecodeImpl<MessageUnion<MessageUnionMember<V, K>...>> {
template <size_t i = 0>
static typename std::enable_if<i == sizeof...(V), Error>::type
decodeMembers(typename MessageUnion<MessageUnionMember<V, K>...>::Builder,
Buffer &, msg_union_id_t) {
return noError();
}
template <size_t i = 0>
static typename std::enable_if <
i<sizeof...(V), Error>::type decodeMembers(
typename MessageUnion<MessageUnionMember<V, K>...>::Builder builder,
Buffer &buffer, msg_union_id_t id) {
if (id == i) {
Error error =
ProtoKelDecodeImpl<typename ParameterPackType<i, V...>::type>::
decode(builder.template init<i>(), buffer);
if (error.failed()) {
return error;
}
}
return decodeMembers<i + 1>(builder, buffer, id);
}
static Error
decode(typename MessageUnion<MessageUnionMember<V, K>...>::Builder builder,
Buffer &buffer) {
msg_union_id_t id = 0;
Error error = StreamValue<msg_union_id_t>::decode(id, buffer);
if (error.failed()) {
return error;
}
if (id >= sizeof...(V)) {
return criticalError("Union doesn't have this many id's");
}
return decodeMembers<0>(builder, buffer, id);
}
};
class ProtoKelCodec {
public:
struct Version {
size_t major;
size_t minor;
size_t security;
};
const Version version() const { return Version{0, 0, 0}; }
template <typename T>
Error encode(typename T::Reader reader, Buffer &buffer) {
msg_packet_length_t packet_length = ProtoKelEncodeImpl<T>::size(reader);
// Check the size of the packet for the first
// message length description
Error error = buffer.writeRequireLength(packet_length +
sizeof(msg_packet_length_t));
if (error.failed()) {
return error;
}
{
Error error =
StreamValue<msg_packet_length_t>::encode(packet_length, buffer);
if (error.failed()) {
return error;
}
}
{
Error error = ProtoKelEncodeImpl<T>::encode(reader, buffer);
if (error.failed()) {
return error;
}
}
return noError();
};
template <typename T>
Error decode(typename T::Builder builder, Buffer &buffer) {
msg_packet_length_t packet_length = 0;
{
Error error =
StreamValue<msg_packet_length_t>::decode(packet_length, buffer);
if (error.failed()) {
return error;
}
}
{
Error error = ProtoKelDecodeImpl<T>::decode(builder, buffer);
if (error.failed()) {
return error;
}
}
{
if (ProtoKelEncodeImpl<T>::size(builder.asReader()) !=
packet_length) {
return criticalError("Bad packet format");
}
}
return noError();
}
};
} // namespace gin

View File

@ -1,25 +0,0 @@
#pragma once
#include <array>
#include <string_view>
namespace gin {
/**
* Helper object which creates a templated string from the provided string
* literal. It guarantees compile time uniqueness and thus allows using strings
* in template parameters.
*/
template <typename T, T... Chars> class StringLiteral {
public:
static constexpr std::array<T, sizeof...(Chars) + 1u> data = {Chars...,
'\0'};
static constexpr std::string_view view() {
return std::string_view{data.data()};
}
};
} // namespace gin
template <typename T, T... Chars>
constexpr gin::StringLiteral<T, Chars...> operator""_t() {
return {};
}

View File

@ -1,6 +1,6 @@
#include "suite/suite.h"
#include "source/async.h"
#include "source/kelgin/async.h"
namespace {
GIN_TEST("Async Immediate"){
@ -19,7 +19,7 @@ GIN_TEST("Async Immediate"){
ErrorOr<bool> error_or_number = is_number.take();
GIN_EXPECT(!error_or_number.isError(), "Return is an error: " + error_or_number.error().message());
GIN_EXPECT(!error_or_number.isError(), error_or_number.error().message());
GIN_EXPECT(error_or_number.isValue(), "Return is not a value");
GIN_EXPECT(error_or_number.value(), "Value is not 5");
}
@ -36,7 +36,7 @@ GIN_TEST("Async Adapt"){
ErrorOr<size_t> foo = feeder_conveyor.conveyor.take();
GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message());
GIN_EXPECT(!foo.isError(), foo.error().message());
GIN_EXPECT(foo.isValue(), "Return is not a value");
GIN_EXPECT(foo.value() == 5, "Values not 5, but " + std::to_string(foo.value()));
}
@ -54,7 +54,7 @@ GIN_TEST("Async Adapt Multiple"){
ErrorOr<size_t> foo = feeder_conveyor.conveyor.take();
GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message());
GIN_EXPECT(!foo.isError(), foo.error().message());
GIN_EXPECT(foo.isValue(), "Return is not a value");
GIN_EXPECT(foo.value() == 5, "Values not 5, but " + std::to_string(foo.value()));
@ -62,7 +62,7 @@ GIN_TEST("Async Adapt Multiple"){
ErrorOr<size_t> bar = feeder_conveyor.conveyor.take();
GIN_EXPECT(!bar.isError(), "Return is an error: " + bar.error().message());
GIN_EXPECT(!foo.isError(), bar.error().message());
GIN_EXPECT(bar.isValue(), "Return is not a value");
GIN_EXPECT(bar.value() == 10, "Values not 10, but " + std::to_string(bar.value()));
@ -72,11 +72,11 @@ GIN_TEST("Async Adapt Multiple"){
ErrorOr<size_t> a = feeder_conveyor.conveyor.take();
ErrorOr<size_t> b = feeder_conveyor.conveyor.take();
GIN_EXPECT(!a.isError(), "Return is an error: " + a.error().message());
GIN_EXPECT(!foo.isError(), a.error().message());
GIN_EXPECT(a.isValue(), "Return is not a value");
GIN_EXPECT(a.value() == 2, "Values not 2, but " + std::to_string(a.value()));
GIN_EXPECT(!b.isError(), "Return is an error: " + b.error().message());
GIN_EXPECT(!foo.isError(), b.error().message());
GIN_EXPECT(b.isValue(), "Return is not a value");
GIN_EXPECT(b.value() == 4234, "Values not 4234, but " + std::to_string(b.value()));
}
@ -97,7 +97,7 @@ GIN_TEST("Async Conversion"){
ErrorOr<std::string> foo = string_conveyor.take();
GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message());
GIN_EXPECT(!foo.isError(), foo.error().message());
GIN_EXPECT(foo.isValue(), "Return is not a value");
GIN_EXPECT(foo.value() == std::to_string(10), "Values is not 10, but " + foo.value());
}
@ -122,7 +122,7 @@ GIN_TEST("Async Conversion Multistep"){
ErrorOr<bool> foo = conveyor.take();
GIN_EXPECT(!foo.isError(), "Return is an error: " + foo.error().message());
GIN_EXPECT(!foo.isError(), foo.error().message());
GIN_EXPECT(foo.isValue(), "Return is not a value");
GIN_EXPECT(foo.value(), "Values is not true");
}
@ -165,20 +165,66 @@ GIN_TEST("Async Scheduling"){
ErrorOr<std::string> foo_10 = string_conveyor.take();
GIN_EXPECT(!foo_10.isError(), "Return is an error: " + foo_10.error().message());
GIN_EXPECT(!foo_10.isError(), foo_10.error().message());
GIN_EXPECT(foo_10.isValue(), "Return is not a value");
GIN_EXPECT(foo_10.value() == (std::string{"pre"} + std::to_string(11) + std::string{"post"}), "Values is not pre11post, but " + foo_10.value());
ErrorOr<std::string> foo_20 = string_conveyor.take();
GIN_EXPECT(!foo_20.isError(), "Return is an error: " + foo_20.error().message());
GIN_EXPECT(!foo_20.isError(), foo_20.error().message());
GIN_EXPECT(foo_20.isValue(), "Return is not a value");
GIN_EXPECT(foo_20.value() == (std::string{"pre"} + std::to_string(22) + std::string{"post"}), "Values is not pre22post, but " + foo_20.value());
ErrorOr<std::string> foo_30 = string_conveyor.take();
GIN_EXPECT(!foo_30.isError(), "Return is an error: " + foo_30.error().message());
GIN_EXPECT(!foo_30.isError(), foo_30.error().message());
GIN_EXPECT(foo_30.isValue(), "Return is not a value");
GIN_EXPECT(foo_30.value() == (std::string{"pre"} + std::to_string(33) + std::string{"post"}), "Values is not pre33post, but " + foo_30.value());
}
}
GIN_TEST("Async Detach"){
using namespace gin;
EventLoop event_loop;
WaitScope wait_scope{event_loop};
int num = 0;
Conveyor<int>{10}.then([&num](int bar){
num = bar;
}).detach();
wait_scope.poll();
GIN_EXPECT(num == 10, std::string{"Bad value: Expected 10, but got "} + std::to_string(num));
}
GIN_TEST("Async Merge"){
using namespace gin;
EventLoop event_loop;
WaitScope wait_scope{event_loop};
auto cam = Conveyor<int>{10}.merge();
cam.second.attach(Conveyor<int>{11});
cam.second.attach(Conveyor<int>{14});
size_t elements_passed = 0;
bool wrong_value = false;
auto sink = cam.first.then([&elements_passed, &wrong_value](int foo){
if(foo == 10 || foo == 11 || 14){
++elements_passed;
}else{
wrong_value = true;
}
}).sink();
wait_scope.poll();
GIN_EXPECT(!wrong_value, std::string{"Expected values 10 or 11"});
GIN_EXPECT(elements_passed == 3, std::string{"Expected 2 passed elements, got only "} + std::to_string(elements_passed));
}
}

View File

@ -1,177 +0,0 @@
#include "suite/suite.h"
#include <cstdint>
#include <string>
#include "buffer.h"
#include "source/message.h"
#include "source/json.h"
using gin::MessageList;
using gin::MessageStruct;
using gin::MessageStructMember;
using gin::MessagePrimitive;
using gin::heapMessageBuilder;
using gin::JsonCodec;
using gin::Error;
using gin::RingBuffer;
namespace {
typedef MessageList<MessagePrimitive<uint32_t>, MessagePrimitive<std::string> > TestList;
GIN_TEST("JSON List Encoding"){
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestList>();
auto uint = root.init<0>();
uint.set(12);
auto string = root.init<1>();
string.set("free");
RingBuffer temp_buffer;
JsonCodec codec;
codec.encode<TestList>(root.asReader(), temp_buffer);
std::string tmp_string = temp_buffer.toString();
GIN_EXPECT(tmp_string == "[12,\"free\"]", std::string{"Bad encoding:\n"} + tmp_string);
}
typedef MessageStruct<
MessageStructMember<MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
MessageStructMember<MessagePrimitive<std::string>, decltype("test_string"_t)>,
MessageStructMember<MessagePrimitive<std::string>, decltype("test_name"_t)>
> TestStruct;
GIN_TEST("JSON Struct Encoding"){
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestStruct>();
auto uint = root.init<decltype("test_uint"_t)>();
uint.set(23);
std::string test_string = "foo";
auto string = root.init<decltype("test_string"_t)>();
string.set(test_string);
auto string_name = root.init<decltype("test_name"_t)>();
string_name.set("test_name"_t.view());
JsonCodec codec;
RingBuffer temp_buffer;
codec.encode<TestStruct>(root.asReader(), temp_buffer);
std::string expected_result{"{\"test_uint\":23,\"test_string\":\"foo\",\"test_name\":\"test_name\"}"};
std::string tmp_string = temp_buffer.toString();
GIN_EXPECT(tmp_string == expected_result, std::string{"Bad encoding:\n"} + tmp_string);
}
typedef gin::MessageUnion<
gin::MessageUnionMember<gin::MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
gin::MessageUnionMember<gin::MessagePrimitive<std::string>, decltype("test_string"_t)>
> TestUnion;
GIN_TEST("JSON Union Encoding"){
using namespace gin;
{
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestUnion>();
auto test_uint = root.init<decltype("test_uint"_t)>();
test_uint.set(23);
RingBuffer buffer;
JsonCodec codec;
Error error = codec.encode<TestUnion>(root.asReader(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
std::string expected_result{"{\"test_uint\":23}"};
std::string tmp_string = buffer.toString();
GIN_EXPECT(tmp_string == expected_result, std::string{"Bad encoding:\n"} + tmp_string);
}
{
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestUnion>();
auto test_string = root.init<decltype("test_string"_t)>();
test_string.set("foo");
RingBuffer buffer;
JsonCodec codec;
Error error = codec.encode<TestUnion>(root.asReader(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
std::string expected_result{"{\"test_string\":\"foo\"}"};
std::string tmp_string = buffer.toString();
GIN_EXPECT(tmp_string == expected_result, std::string{"Bad encoding:\n"} + tmp_string);
}
}
GIN_TEST("JSON Struct Decoding"){
std::string json_string = R"(
{
"test_string" :"banana" ,
"test_uint" : 5,
"test_name" : "keldu"
})";
auto builder = heapMessageBuilder();
TestStruct::Builder root = builder.initRoot<TestStruct>();
JsonCodec codec;
RingBuffer temp_buffer;
temp_buffer.push(*reinterpret_cast<const uint8_t*>(json_string.data()), json_string.size());
Error error = codec.decode<TestStruct>(root, temp_buffer);
GIN_EXPECT( !error.failed(), error.message() );
auto reader = root.asReader();
GIN_EXPECT( reader.get<decltype("test_string"_t)>().get() == "banana", "Test String has wrong value" );
GIN_EXPECT( reader.get<decltype("test_uint"_t)>().get() == 5, "Test Unsigned has wrong value" );
GIN_EXPECT( reader.get<decltype("test_name"_t)>().get() == "keldu", "Test Name has wrong value" );
}
typedef MessageStruct<
MessageStructMember<MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
MessageStructMember<TestStruct, decltype("test_struct"_t)>,
MessageStructMember<MessagePrimitive<std::string>, decltype("test_name"_t)>
> TestStructDepth;
GIN_TEST("JSON Struct Decoding Two layer"){
std::string json_string = R"(
{
"test_struct" :{
"test_string" : "banana",
"test_uint": 40,
"test_name":"HaDiKo"
},
"test_uint": 5,
"test_name" : "keldu"
})";
auto builder = heapMessageBuilder();
TestStructDepth::Builder root = builder.initRoot<TestStructDepth>();
JsonCodec codec;
RingBuffer temp_buffer;
temp_buffer.push(*reinterpret_cast<const uint8_t*>(json_string.data()), json_string.size());
Error error = codec.decode<TestStructDepth>(root, temp_buffer);
GIN_EXPECT( !error.failed(), error.message() );
auto reader = root.asReader();
auto inner_reader = reader.get<decltype("test_struct"_t)>();
GIN_EXPECT( inner_reader.get<decltype("test_string"_t)>().get() == "banana", "Test String has wrong value" );
GIN_EXPECT( inner_reader.get<decltype("test_uint"_t)>().get() == 40, "Test Unsigned has wrong value" );
GIN_EXPECT( inner_reader.get<decltype("test_name"_t)>().get() == "HaDiKo", "Test Name has wrong value" );
GIN_EXPECT( reader.get<decltype("test_uint"_t)>().get() == 5, "Test Unsigned has wrong value" );
GIN_EXPECT( reader.get<decltype("test_name"_t)>().get() == "keldu", "Test Name has wrong value" );
}
}

View File

@ -3,51 +3,51 @@
#include <cstdint>
#include <string>
#include "source/message.h"
using gin::MessageList;
using gin::MessageStruct;
using gin::MessageStructMember;
using gin::MessagePrimitive;
using gin::heapMessageBuilder;
#include "source/kelgin/message.h"
#include "source/kelgin/schema.h"
namespace {
typedef MessageList<MessagePrimitive<uint32_t>, MessagePrimitive<std::string> > TestList;
namespace schema {
using namespace gin::schema;
}
GIN_TEST("MessageList"){
using TestTuple = schema::Tuple<schema::UInt32, schema::String>;
GIN_TEST("Message Tuple"){
std::string test_string_1 = "banana";
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestList>();
auto uint = root.init<0>();
auto root = gin::heapMessageRoot<TestTuple>();
auto builder = root.build();
auto uint = builder.init<0>();
uint.set(10);
auto string = root.init<1>();
string.set(test_string_1);
auto string = builder.init<1>();
string.set(std::string_view{test_string_1});
auto root_reader = root.asReader();
auto uint_reader = root_reader.get<0>();
auto string_reader = root_reader.get<1>();
auto reader = root.read();
auto uint_reader = reader.get<0>();
auto string_reader = reader.get<1>();
GIN_EXPECT( uint_reader.get() == 10 && string_reader.get() == test_string_1, "wrong values");
}
typedef MessageList<MessageList<MessagePrimitive<uint32_t>, MessagePrimitive<std::string>>, MessagePrimitive<std::string> > NestedTestList;
using NestedTestTuple = schema::Tuple<schema::Tuple<schema::UInt32, schema::String>, schema::String>;
GIN_TEST("MessageList nested"){
GIN_TEST("Message Tuple nested"){
std::string test_string_1 = "banana";
std::string test_string_2 = "bat";
auto builder = heapMessageBuilder();
auto root = builder.initRoot<NestedTestList>();
auto inner_list = root.init<0>();
auto root = gin::heapMessageRoot<NestedTestTuple>();
auto builder = root.build();
auto inner_list = builder.init<0>();
auto uint = inner_list.init<0>();
uint.set(20);
auto inner_string = inner_list.init<1>();
inner_string.set(test_string_2);
auto string = root.init<1>();
auto string = builder.init<1>();
string.set(test_string_1);
auto root_reader = root.asReader();
auto root_reader = root.read();
auto inner_reader = root_reader.get<0>();
auto uint_reader = inner_reader.get<0>();
auto inner_string_reader = inner_reader.get<1>();
@ -56,30 +56,71 @@ GIN_TEST("MessageList nested"){
GIN_EXPECT(uint_reader.get() == 20 && inner_string_reader.get() == test_string_2 && string_reader.get() == test_string_1, "wrong values");
}
typedef MessageStruct<
MessageStructMember<MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
MessageStructMember<MessagePrimitive<std::string>, decltype("test_string"_t)>,
MessageStructMember<MessagePrimitive<std::string>, decltype("test_name"_t)>
> TestStruct;
using TestStruct = schema::Struct<
schema::NamedMember<schema::UInt32, "test_uint">,
schema::NamedMember<schema::String, "test_string">,
schema::NamedMember<schema::String, "test_name">
>;
GIN_TEST("MessageStruct"){
GIN_TEST("Message Struct"){
std::string test_string = "foo";
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestStruct>();
auto uint = root.init<decltype("test_uint"_t)>();
auto root = gin::heapMessageRoot<TestStruct>();
auto builder = root.build();
auto uint = builder.init<"test_uint">();
uint.set(23);
auto string = root.init<decltype("test_string"_t)>();
auto string = builder.init<"test_string">();
string.set(test_string);
auto string_name = root.init<decltype("test_name"_t)>();
string_name.set(&"test_name"_t.data[0]);
auto string_name = builder.init<"test_name">();
string_name.set("test_name");
auto reader = root.asReader();
auto uint_reader = reader.get<decltype("test_uint"_t)>();
auto string_reader = reader.get<decltype("test_string"_t)>();
auto name_reader = reader.get<decltype("test_name"_t)>();
auto reader = root.read();
auto uint_reader = reader.get<"test_uint">();
auto string_reader = reader.get<"test_string">();
auto name_reader = reader.get<"test_name">();
/*
* Set string to another value to guarantee no changes
*/
test_string = "foo2";
GIN_EXPECT(uint_reader.get() == 23 && string_reader.get() != test_string && string_reader.get() == "foo" && name_reader.get() == "test_name", "wrong values");
GIN_EXPECT(uint_reader.get() == 23 && string_reader.get() != test_string && string_reader.get() == "foo" && name_reader.get() == "test_name", "Wrong values");
}
using TestArray = schema::Array<schema::UInt32>;
void arrayCheck(gin::Message<TestArray>::Builder builder){
auto one = builder.init(0);
auto two = builder.init(1);
auto three = builder.init(2);
one.set(24);
two.set(45);
three.set(1230);
auto reader = builder.asReader();
GIN_EXPECT(reader.get(0).get() == 24 && reader.get(1).get() == 45 && reader.get(2).get(), "Wrong values");
}
GIN_TEST("Message Array"){
auto root = gin::heapMessageRoot<TestArray>();
auto builder = root.build(3);
arrayCheck(builder);
}
using TestArrayStruct = schema::Struct<
schema::NamedMember<TestArray, "array">
>;
GIN_TEST("Message Array in Struct"){
auto root = gin::heapMessageRoot<TestArrayStruct>();
auto builder = root.build();
auto array = builder.init<"array">(3);
arrayCheck(array);
}
}
}

View File

@ -1,60 +1,65 @@
#include "suite/suite.h"
#include "source/proto_kel.h"
#include "source/kelgin/proto_kel.h"
#include <iostream>
namespace {
typedef gin::MessagePrimitive<uint32_t> TestSize;
namespace schema {
using namespace gin::schema;
}
using TestSize = schema::UInt32;
typedef gin::MessageList<gin::MessagePrimitive<uint32_t>, gin::MessagePrimitive<uint16_t>> TestList;
using TestTuple = schema::Tuple<schema::UInt32, schema::UInt16>;
typedef gin::MessageStruct<
gin::MessageStructMember<gin::MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
gin::MessageStructMember<gin::MessagePrimitive<std::string>, decltype("test_string"_t)>,
gin::MessageStructMember<gin::MessagePrimitive<std::string>, decltype("test_name"_t)>
> TestStruct;
using TestStruct = schema::Struct<
schema::NamedMember<schema::UInt32, "test_uint">,
schema::NamedMember<schema::String, "test_string">,
schema::NamedMember<schema::String, "test_name">
>;
typedef gin::MessageUnion<
gin::MessageUnionMember<gin::MessagePrimitive<uint32_t>, decltype("test_uint"_t)>,
gin::MessageUnionMember<gin::MessagePrimitive<std::string>, decltype("test_string"_t)>
> TestUnion;
using TestUnion = schema::Union<
schema::NamedMember<schema::UInt32, "test_uint">,
schema::NamedMember<schema::String, "test_string">
>;
GIN_TEST("Primitive Encoding"){
using namespace gin;
uint32_t value = 5;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestSize>();
auto root = heapMessageRoot<TestSize>();
auto builder = root.build();
root.set(value);
builder.set(value);
RingBuffer temp_buffer;
ProtoKelCodec codec;
Error error = ProtoKelEncodeImpl<TestSize>::encode(root.asReader(), temp_buffer);
Error error = codec.encode<TestSize>(root.read(), temp_buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(temp_buffer.readCompositeLength() == sizeof(value), "Bad Size: " + std::to_string(temp_buffer.readCompositeLength()));
GIN_EXPECT(temp_buffer[0] == 5 && temp_buffer[1] == 0 && temp_buffer[2] == 0 && temp_buffer[3] == 0, "Wrong encoded values");
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(temp_buffer.readCompositeLength() == (sizeof(value)+sizeof(msg_packet_length_t)), "Bad Size: " + std::to_string(temp_buffer.readCompositeLength()));
constexpr size_t pkt_shift = sizeof(msg_packet_length_t);
GIN_EXPECT(temp_buffer[pkt_shift] == 5 && temp_buffer[pkt_shift+1] == 0 && temp_buffer[pkt_shift+2] == 0 && temp_buffer[pkt_shift+3] == 0, "Wrong encoded values");
}
GIN_TEST("List Encoding"){
using namespace gin;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestList>();
auto root = heapMessageRoot<TestTuple>();
auto builder = root.build();
auto first = root.init<0>();
auto first = builder.init<0>();
first.set(2135231);
auto second = root.init<1>();
auto second = builder.init<1>();
second.set(43871);
RingBuffer buffer;
ProtoKelCodec codec;
Error error = codec.encode<TestList>(root.asReader(), buffer);
Error error = codec.encode<TestTuple>(root.read(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(buffer.readCompositeLength() == 14, "Bad Size: " + std::to_string(buffer.readCompositeLength()));
GIN_EXPECT("06 00 00 00\n00 00 00 00\nbf 94 20 00\n5f ab" == buffer.toHex(), "Not equal encoding\n"+buffer.toHex());
}
@ -62,25 +67,25 @@ GIN_TEST("List Encoding"){
GIN_TEST("Struct Encoding"){
using namespace gin;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestStruct>();
auto root = heapMessageRoot<TestStruct>();
auto builder = root.build();
auto test_uint = root.init<decltype("test_uint"_t)>();
auto test_uint = builder.init<"test_uint">();
test_uint.set(23);
std::string test_string = "foo";
auto string = root.init<decltype("test_string"_t)>();
auto string = builder.init<"test_string">();
string.set(test_string);
auto string_name = root.init<decltype("test_name"_t)>();
string_name.set("test_name"_t.view());
auto string_name = builder.init<"test_name">();
string_name.set("test_name");
RingBuffer buffer;
ProtoKelCodec codec;
Error error = codec.encode<TestStruct>(root.asReader(), buffer);
Error error = codec.encode<TestStruct>(builder.asReader(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(buffer.readCompositeLength() == 40, "Bad Size: " + std::to_string(buffer.readCompositeLength()));
GIN_EXPECT("20 00 00 00\n00 00 00 00\n17 00 00 00\n03 00 00 00\n00 00 00 00\n66 6f 6f 09\n00 00 00 00\n00 00 00 74\n65 73 74 5f\n6e 61 6d 65"
== buffer.toHex(), "Not equal encoding:\n"+buffer.toHex());
@ -89,42 +94,42 @@ GIN_TEST("Struct Encoding"){
GIN_TEST("Union Encoding"){
using namespace gin;
{
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestUnion>();
auto root = heapMessageRoot<TestUnion>();
auto builder = root.build();
auto test_uint = root.init<decltype("test_uint"_t)>();
auto test_uint = builder.init<"test_uint">();
test_uint.set(23);
RingBuffer buffer;
ProtoKelCodec codec;
Error error = codec.encode<TestUnion>(root.asReader(), buffer);
Error error = codec.encode<TestUnion>(builder.asReader(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(buffer.readCompositeLength() == 16, "Bad Size: " + std::to_string(buffer.readCompositeLength()));
GIN_EXPECT("08 00 00 00\n00 00 00 00\n00 00 00 00\n17 00 00 00"
== buffer.toHex(), "Not equal encoding:\n"+buffer.toHex());
}
{
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestUnion>();
auto root = heapMessageRoot<TestUnion>();
auto builder = root.build();
auto test_string = root.init<decltype("test_string"_t)>();
auto test_string = builder.init<"test_string">();
test_string.set("foo");
RingBuffer buffer;
ProtoKelCodec codec;
Error error = codec.encode<TestUnion>(root.asReader(), buffer);
Error error = codec.encode<TestUnion>(builder.asReader(), buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(buffer.readCompositeLength() == 23, "Bad Size: " + std::to_string(buffer.readCompositeLength()));
GIN_EXPECT("0f 00 00 00\n00 00 00 00\n01 00 00 00\n03 00 00 00\n00 00 00 00\n66 6f 6f"
== buffer.toHex(), "Not equal encoding:\n"+buffer.toHex());
}
}
GIN_TEST("List Decoding"){
GIN_TEST("Tuple Decoding"){
using namespace gin;
const uint8_t buffer_raw[] = {0x06, 0, 0, 0, 0, 0, 0, 0, 0xbf, 0x94, 0x20, 0x00, 0x5f, 0xab};
@ -133,13 +138,13 @@ GIN_TEST("List Decoding"){
ProtoKelCodec codec;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestList>();
auto root = heapMessageRoot<TestTuple>();
auto builder = root.build();
Error error = codec.decode<TestList>(root, buffer);
GIN_EXPECT(!error.failed(), std::string{"Error: "} + error.message());
Error error = codec.decode<TestTuple>(builder, buffer);
GIN_EXPECT(!error.failed(), error.message());
auto reader = root.asReader();
auto reader = builder.asReader();
auto first = reader.get<0>();
auto second = reader.get<1>();
@ -156,17 +161,17 @@ GIN_TEST("Struct Decoding"){
ProtoKelCodec codec;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestStruct>();
auto root = heapMessageRoot<TestStruct>();
auto builder = root.build();
Error error = codec.decode<TestStruct>(root, buffer);
auto reader = root.asReader();
Error error = codec.decode<TestStruct>(builder, buffer);
auto reader = builder.asReader();
auto foo_string = reader.get<decltype("test_string"_t)>();
auto test_uint = reader.get<decltype("test_uint"_t)>();
auto test_name = reader.get<decltype("test_name"_t)>();
auto foo_string = reader.get<"test_string">();
auto test_uint = reader.get<"test_uint">();
auto test_name = reader.get<"test_name">();
GIN_EXPECT(!error.failed(), std::string{"Error: "} + error.message());
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(foo_string.get() == "foo" && test_uint.get() == 23 && test_name.get() == "test_name", "Values not correctly decoded");
}
@ -179,15 +184,44 @@ GIN_TEST("Union Decoding"){
ProtoKelCodec codec;
auto builder = heapMessageBuilder();
auto root = builder.initRoot<TestUnion>();
auto reader = root.asReader();
auto root = heapMessageRoot<TestUnion>();
auto builder = root.build();
auto reader = builder.asReader();
Error error = codec.decode<TestUnion>(root, buffer);
Error error = codec.decode<TestUnion>(builder, buffer);
GIN_EXPECT(!error.failed(), "Error: " + error.message());
GIN_EXPECT(reader.holdsAlternative<decltype("test_string"_t)>(), "Wrong union value");
auto str_rd = reader.get<decltype("test_string"_t)>();
GIN_EXPECT(!error.failed(), error.message());
GIN_EXPECT(reader.hasAlternative<"test_string">(), "Wrong union value");
auto str_rd = reader.get<"test_string">();
GIN_EXPECT(str_rd.get() == "foo", "Wrong value: " + std::string{str_rd.get()});
}
using TestArrayStruct = schema::Array<
TestStruct
>;
GIN_TEST("Array Encoding"){
using namespace gin;
ProtoKelCodec codec;
auto root = heapMessageRoot<TestArrayStruct>();
auto builder = root.build(2);
auto one = builder.init(0);
auto two = builder.init(1);
one.init<"test_uint">().set(4);
one.init<"test_string">().set("foo");
one.init<"test_name">().set("Fedor");
two.init<"test_uint">().set(9);
two.init<"test_string">().set("bar");
two.init<"test_name">().set("Bravo");
RingBuffer buffer;
Error error = codec.encode<TestArrayStruct>(root.read(), buffer);
GIN_EXPECT(!error.failed(), "Error occured");
}
}

View File

@ -3,6 +3,7 @@
#include <string>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include "common.h"
@ -35,7 +36,8 @@ public:
}GIN_UNIQUE_NAME(testCase); \
void GIN_UNIQUE_NAME(TestCase)::run()
#define GIN_EXPECT(expr, msg) \
#define GIN_EXPECT(expr, msg_split) \
if( ! (expr) ){ \
throw std::runtime_error{msg}; \
auto msg = msg_split; \
throw std::runtime_error{std::string{msg}};\
}