merge conflict resolved
commit
c0b2712476
|
@ -112,9 +112,6 @@ SpacesInCStyleCastParentheses: false
|
|||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: ForContinuationAndIndentation
|
||||
...
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
47
README.md
47
README.md
|
@ -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
|
||||
|
|
26
SConstruct
26
SConstruct
|
@ -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/')
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
|
100
driver/io-unix.h
100
driver/io-unix.h
|
@ -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
|
||||
|
|
983
source/async.h
983
source/async.h
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
#include "io.h"
|
||||
|
||||
namespace gin {}
|
91
source/io.h
91
source/io.h
|
@ -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
|
845
source/json.h
845
source/json.h
|
@ -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
|
|
@ -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"))
|
|
@ -14,11 +14,19 @@ EventLoop ¤tEventLoop() {
|
|||
}
|
||||
} // 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);
|
||||
}
|
|
@ -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"
|
|
@ -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
|
|
@ -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) {
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
405
source/message.h
405
source/message.h
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 {};
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
177
test/json.cpp
177
test/json.cpp
|
@ -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" );
|
||||
}
|
||||
}
|
123
test/message.cpp
123
test/message.cpp
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}};\
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue