summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorClaudius "keldu" Holeksa <mail@keldu.de>2023-12-04 12:18:14 +0100
committerClaudius "keldu" Holeksa <mail@keldu.de>2023-12-04 12:18:14 +0100
commita14896f9ed209dd3f9597722e5a5697bd7dbf531 (patch)
tree089ca5cbbd206d1921f8f6b53292f5bc1902ca5c /modules
parent84ecdcbca9e55b1f57fbb832e12ff4fdbb86e7c9 (diff)
meta: Renamed folder containing source
Diffstat (limited to 'modules')
-rw-r--r--modules/SConscript8
-rw-r--r--modules/async/.nix/derivation.nix28
-rw-r--r--modules/async/SConscript38
-rw-r--r--modules/async/SConstruct66
-rw-r--r--modules/async/async.cpp419
-rw-r--r--modules/async/async.h1023
-rw-r--r--modules/async/async.tmpl.h767
-rw-r--r--modules/codec-json/.nix/derivation.nix30
-rw-r--r--modules/codec-json/SConscript38
-rw-r--r--modules/codec-json/SConstruct66
-rw-r--r--modules/codec-json/json.h120
-rw-r--r--modules/codec-json/json.tmpl.h734
-rw-r--r--modules/codec-minecraft/.nix/derivation.nix32
-rw-r--r--modules/codec-minecraft/SConscript38
-rw-r--r--modules/codec-minecraft/SConstruct66
-rw-r--r--modules/codec-minecraft/minecraft.h116
-rw-r--r--modules/codec-minecraft/var_int_data.cpp36
-rw-r--r--modules/codec-minecraft/var_int_data.h31
-rw-r--r--modules/codec-netcdf/.nix/derivation.nix33
-rw-r--r--modules/codec-netcdf/SConscript38
-rw-r--r--modules/codec-netcdf/SConstruct66
-rw-r--r--modules/codec-netcdf/netcdf.h151
-rw-r--r--modules/codec-netcdf/netcdf.tmpl.h348
-rw-r--r--modules/codec/.nix/derivation.nix28
-rw-r--r--modules/codec/SConscript38
-rw-r--r--modules/codec/SConstruct66
-rw-r--r--modules/codec/args.h123
-rw-r--r--modules/codec/csv.h25
-rw-r--r--modules/codec/data.h370
-rw-r--r--modules/codec/forst.h32
-rw-r--r--modules/codec/forst.tmpl.h7
-rw-r--r--modules/codec/interface.h103
-rw-r--r--modules/codec/rpc.h26
-rw-r--r--modules/codec/schema.h109
-rw-r--r--modules/codec/schema_hash.h105
-rw-r--r--modules/codec/schema_stringify.h118
-rw-r--r--modules/codec/simple.h389
-rw-r--r--modules/codec/stream_value.h64
-rw-r--r--modules/core/.nix/derivation.nix21
-rw-r--r--modules/core/SConscript42
-rw-r--r--modules/core/SConstruct66
-rw-r--r--modules/core/array.h96
-rw-r--r--modules/core/buffer.cpp436
-rw-r--r--modules/core/buffer.h199
-rw-r--r--modules/core/common.h75
-rw-r--r--modules/core/error.cpp156
-rw-r--r--modules/core/error.h248
-rw-r--r--modules/core/id.h54
-rw-r--r--modules/core/id_map.h137
-rw-r--r--modules/core/mcts.h52
-rw-r--r--modules/core/platonic.h103
-rw-r--r--modules/core/string_literal.h57
-rw-r--r--modules/core/templates.h150
-rw-r--r--modules/core/tree.h248
-rw-r--r--modules/device-hip/.nix/derivation.nix30
-rw-r--r--modules/device-hip/SConscript38
-rw-r--r--modules/device-hip/SConstruct66
-rw-r--r--modules/device-hip/rpc.h7
-rw-r--r--modules/io-fs/.nix/derivation.nix33
-rw-r--r--modules/io-fs/SConscript38
-rw-r--r--modules/io-fs/SConstruct66
-rw-r--r--modules/io-tls/.nix/derivation.nix32
-rw-r--r--modules/io-tls/SConscript38
-rw-r--r--modules/io-tls/SConstruct66
-rw-r--r--modules/io-tls/tls.cpp252
-rw-r--r--modules/io-tls/tls.h68
-rw-r--r--modules/io/.nix/derivation.nix29
-rw-r--r--modules/io/SConscript38
-rw-r--r--modules/io/SConstruct66
-rw-r--r--modules/io/io.cpp70
-rw-r--r--modules/io/io.h219
-rw-r--r--modules/io/io_helpers.cpp85
-rw-r--r--modules/io/io_helpers.h53
-rw-r--r--modules/io/io_unix.cpp894
-rw-r--r--modules/io_codec/.nix/derivation.nix31
-rw-r--r--modules/io_codec/SConscript38
-rw-r--r--modules/io_codec/SConstruct66
-rw-r--r--modules/io_codec/io_peer.h104
-rw-r--r--modules/io_codec/io_peer.tmpl.h117
-rw-r--r--modules/io_codec/rpc.h31
-rw-r--r--modules/test/.nix/derivation.nix28
-rw-r--r--modules/test/SConscript33
-rw-r--r--modules/test/SConstruct66
-rw-r--r--modules/test/suite.cpp107
-rw-r--r--modules/test/suite.h43
-rw-r--r--modules/tools/.nix/derivation.nix31
-rw-r--r--modules/tools/SConscript32
-rw-r--r--modules/tools/SConstruct71
-rw-r--r--modules/tools/c_gen_iface.cpp11
-rw-r--r--modules/tools/c_gen_iface.hpp535
-rw-r--r--modules/tools/cli_analyzer.hpp114
-rw-r--r--modules/window-opengl/.nix/derivation.nix37
-rw-r--r--modules/window-opengl/SConscript38
-rw-r--r--modules/window-opengl/SConstruct72
-rw-r--r--modules/window-opengl/gl_backends.h10
-rw-r--r--modules/window-opengl/gl_context.h43
-rw-r--r--modules/window-opengl/gl_window.h8
-rw-r--r--modules/window-opengl/gl_xcb.cpp269
-rw-r--r--modules/window-opengl/gl_xcb.h68
-rw-r--r--modules/window/.nix/derivation.nix34
-rw-r--r--modules/window/SConscript38
-rw-r--r--modules/window/SConstruct66
-rw-r--r--modules/window/backends.h10
-rw-r--r--modules/window/device.h18
-rw-r--r--modules/window/linux_xcb.h5
-rw-r--r--modules/window/old.dummy399
-rw-r--r--modules/window/video_mode.h11
-rw-r--r--modules/window/window.h79
-rw-r--r--modules/window/xcb.cpp290
-rw-r--r--modules/window/xcb.h105
110 files changed, 13281 insertions, 0 deletions
diff --git a/modules/SConscript b/modules/SConscript
new file mode 100644
index 0000000..8da5a3d
--- /dev/null
+++ b/modules/SConscript
@@ -0,0 +1,8 @@
+#!/bin/false
+
+Import('env')
+
+# Export to other libs
+Export('env');
+SConscript('core/SConscript');
+SConscript('async/SConscript');
diff --git a/modules/async/.nix/derivation.nix b/modules/async/.nix/derivation.nix
new file mode 100644
index 0000000..aad258f
--- /dev/null
+++ b/modules/async/.nix/derivation.nix
@@ -0,0 +1,28 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-async";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/async/SConscript b/modules/async/SConscript
new file mode 100644
index 0000000..69f8950
--- /dev/null
+++ b/modules/async/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+async_env = env.Clone();
+
+async_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+async_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += async_env.sources;
+env.headers += async_env.headers;
+
+## Shared lib
+objects_shared = []
+async_env.add_source_files(objects_shared, async_env.sources, shared=True);
+async_env.library_shared = async_env.SharedLibrary('#build/forstio-async', [objects_shared]);
+
+## Static lib
+objects_static = []
+async_env.add_source_files(objects_static, async_env.sources, shared=False);
+async_env.library_static = async_env.StaticLibrary('#build/forstio-async', [objects_static]);
+
+# Set Alias
+env.Alias('library_async', [async_env.library_shared, async_env.library_static]);
+
+env.targets += ['library_async'];
+
+# Install
+env.Install('$prefix/lib/', [async_env.library_shared, async_env.library_static]);
+env.Install('$prefix/include/forstio/async/', [async_env.headers]);
diff --git a/modules/async/SConstruct b/modules/async/SConstruct
new file mode 100644
index 0000000..0d7b7c6
--- /dev/null
+++ b/modules/async/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-core'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/async/async.cpp b/modules/async/async.cpp
new file mode 100644
index 0000000..360e455
--- /dev/null
+++ b/modules/async/async.cpp
@@ -0,0 +1,419 @@
+#include "async.h"
+#include <forstio/core/common.h>
+#include <forstio/core/error.h>
+
+#include <algorithm>
+#include <cassert>
+
+namespace saw {
+namespace {
+thread_local event_loop *local_loop = nullptr;
+
+event_loop &current_event_loop() {
+ event_loop *loop = local_loop;
+ assert(loop);
+ return *loop;
+}
+} // namespace
+
+conveyor_node::conveyor_node() {}
+
+conveyor_node_with_child_mixin::conveyor_node_with_child_mixin(
+ own<conveyor_node> &&child_, conveyor_node &owner)
+ : child{std::move(child_)} {
+ assert(child);
+
+ child->notify_parent_attached(owner);
+}
+
+error_or<own<conveyor_node>>
+conveyor_node_with_child_mixin::swap_child(own<conveyor_node> &&swapee) {
+ SAW_ASSERT(child) {
+ return make_error<err::invalid_state>("Child should exist if this function is called");
+ }
+ own<conveyor_node> old_child = std::move(child);
+
+ /**
+ * We need the parent of the old_child's next storage
+ */
+ conveyor_storage *old_storage = old_child->next_storage();
+ conveyor_storage *old_storage_parent =
+ old_storage ? old_storage->get_parent() : nullptr;
+
+ /**
+ * Swap in the new child
+ */
+ if (swapee) {
+ child = std::move(swapee);
+
+ /**
+ * Then we need to set the new child's storage parent since the next
+ * storage has a nullptr set And if the old_storage_parent is a nullptr,
+ * then it doesn't matter. So we don't check for it
+ */
+ conveyor_storage *swapee_storage = child->next_storage();
+ if (swapee_storage) {
+ swapee_storage->set_parent(old_storage_parent);
+ }
+ }
+
+ return old_child;
+}
+
+conveyor_storage::conveyor_storage() {}
+
+conveyor_storage::~conveyor_storage() {}
+
+conveyor_storage *conveyor_storage::get_parent() const { return parent_; }
+
+void conveyor_event_storage::set_parent(conveyor_storage *p) {
+ /*
+ * parent check isn't needed, but is used
+ * for the assert, because the storage should
+ * be armed if there was an element present
+ * and a valid parent
+ */
+ if (/*!parent && */ p && !is_armed() && queued() > 0) {
+ assert(!parent_);
+ if (p->space() > 0) {
+ arm_later();
+ }
+ }
+
+ parent_ = p;
+}
+
+conveyor_event_storage::conveyor_event_storage() : conveyor_storage{} {}
+
+conveyor_base::conveyor_base(own<conveyor_node> &&node_p)
+ : node_{std::move(node_p)} {}
+
+error propagate_error::operator()(const error &error) const {
+ return error.copy_error();
+}
+
+error propagate_error::operator()(error &&err) { return std::move(err); }
+
+event::event() : event(current_event_loop()) {}
+
+event::event(event_loop &loop) : loop_{loop} {}
+
+event::~event() { disarm(); }
+
+void event::arm_next() {
+ assert(&loop_ == local_loop);
+ if (prev_ == nullptr) {
+ // Push the next_insert_point back by one
+ // and inserts itself before that
+ next_ = *loop_.next_insert_point_;
+ prev_ = loop_.next_insert_point_;
+ *prev_ = this;
+ if (next_) {
+ next_->prev_ = &next_;
+ }
+
+ // Set the new insertion ptr location to next
+ loop_.next_insert_point_ = &next_;
+
+ // Pushes back the later insert point if it was pointing at the
+ // previous event
+ if (loop_.later_insert_point_ == prev_) {
+ loop_.later_insert_point_ = &next_;
+ }
+
+ // If tail_ points at the same location then
+ // we are at the end and have to update tail_ then.
+ // Technically should be possible by checking if
+ // next is a `nullptr`
+ if (loop_.tail_ == prev_) {
+ loop_.tail_ = &next_;
+ }
+
+ loop_.set_runnable(true);
+ }
+}
+
+void event::arm_later() {
+ assert(&loop_ == local_loop);
+
+ if (prev_ == nullptr) {
+ next_ = *loop_.later_insert_point_;
+ prev_ = loop_.later_insert_point_;
+ *prev_ = this;
+ if (next_) {
+ next_->prev_ = &next_;
+ }
+
+ loop_.later_insert_point_ = &next_;
+ if (loop_.tail_ == prev_) {
+ loop_.tail_ = &next_;
+ }
+
+ loop_.set_runnable(true);
+ }
+}
+
+void event::arm_last() {
+ assert(&loop_ == local_loop);
+
+ if (prev_ == nullptr) {
+ next_ = *loop_.later_insert_point_;
+ prev_ = loop_.later_insert_point_;
+ *prev_ = this;
+ if (next_) {
+ next_->prev_ = &next_;
+ }
+
+ if (loop_.tail_ == prev_) {
+ loop_.tail_ = &next_;
+ }
+
+ loop_.set_runnable(true);
+ }
+}
+
+void event::disarm() {
+ if (prev_ != nullptr) {
+ if (loop_.tail_ == &next_) {
+ loop_.tail_ = prev_;
+ }
+
+ if (loop_.next_insert_point_ == &next_) {
+ loop_.next_insert_point_ = prev_;
+ }
+
+ *prev_ = next_;
+ if (next_) {
+ next_->prev_ = prev_;
+ }
+
+ prev_ = nullptr;
+ next_ = nullptr;
+ }
+}
+
+bool event::is_armed() const { return prev_ != nullptr; }
+
+conveyor_sink::conveyor_sink() : node_{nullptr} {}
+
+conveyor_sink::conveyor_sink(own<conveyor_node> &&node_p)
+ : node_{std::move(node_p)} {}
+
+void event_loop::set_runnable(bool runnable) { is_runnable_ = runnable; }
+
+event_loop::event_loop() {}
+
+event_loop::event_loop(own<class event_port> &&ep)
+ : event_port_{std::move(ep)} {}
+
+event_loop::~event_loop() { assert(local_loop != this); }
+
+void event_loop::enter_scope() {
+ assert(!local_loop);
+ local_loop = this;
+}
+
+void event_loop::leave_scope() {
+ assert(local_loop == this);
+ local_loop = nullptr;
+}
+
+bool event_loop::turn_loop() {
+ size_t turn_step = 0;
+ while (head_ && turn_step < 65536) {
+ if (!turn()) {
+ return false;
+ }
+ ++turn_step;
+ }
+ return true;
+}
+
+bool event_loop::turn() {
+ event *event = head_;
+
+ if (!event) {
+ return false;
+ }
+
+ head_ = event->next_;
+ if (head_) {
+ head_->prev_ = &head_;
+ }
+
+ next_insert_point_ = &head_;
+ if (later_insert_point_ == &event->next_) {
+ later_insert_point_ = &head_;
+ }
+ if (tail_ == &event->next_) {
+ tail_ = &head_;
+ }
+
+ event->next_ = nullptr;
+ event->prev_ = nullptr;
+
+ next_insert_point_ = &head_;
+
+ event->fire();
+
+ return true;
+}
+
+bool event_loop::wait(const std::chrono::steady_clock::duration &duration) {
+ if (event_port_) {
+ event_port_->wait(duration);
+ }
+
+ return turn_loop();
+}
+
+bool event_loop::wait(const std::chrono::steady_clock::time_point &time_point) {
+ if (event_port_) {
+ event_port_->wait(time_point);
+ }
+
+ return turn_loop();
+}
+
+bool event_loop::wait() {
+ if (event_port_) {
+ event_port_->wait();
+ }
+
+ return turn_loop();
+}
+
+bool event_loop::poll() {
+ if (event_port_) {
+ event_port_->poll();
+ }
+
+ return turn_loop();
+}
+
+event_port *event_loop::get_event_port() { return event_port_.get(); }
+
+conveyor_sink_set &event_loop::daemon() {
+ if (!daemon_sink_) {
+ daemon_sink_ = heap<conveyor_sink_set>();
+ }
+ return *daemon_sink_;
+}
+
+wait_scope::wait_scope(event_loop &loop) : loop_{loop} { loop_.enter_scope(); }
+
+wait_scope::~wait_scope() { loop_.leave_scope(); }
+
+void wait_scope::wait() { loop_.wait(); }
+
+void wait_scope::wait(const std::chrono::steady_clock::duration &duration) {
+ loop_.wait(duration);
+}
+
+void wait_scope::wait(const std::chrono::steady_clock::time_point &time_point) {
+ loop_.wait(time_point);
+}
+
+void wait_scope::poll() { loop_.poll(); }
+
+error_or<own<conveyor_node>>
+convert_conveyor_node_base::swap_child(own<conveyor_node> &&swapee) noexcept {
+ return child_mixin_.swap_child(std::move(swapee));
+}
+
+conveyor_storage *convert_conveyor_node_base::next_storage() noexcept {
+ if (!child_mixin_.child) {
+ return nullptr;
+ }
+ return child_mixin_.child->next_storage();
+}
+
+immediate_conveyor_node_base::immediate_conveyor_node_base()
+ : conveyor_event_storage{} {}
+
+merge_conveyor_node_base::merge_conveyor_node_base()
+ : conveyor_event_storage{} {}
+
+error_or<own<conveyor_node>> queue_buffer_conveyor_node_base::swap_child(
+ own<conveyor_node> &&swapee_) noexcept {
+ return child_mixin_.swap_child(std::move(swapee_));
+}
+
+void conveyor_sink_set::destroy_sink_conveyor_node(conveyor_node &node) {
+ if (!is_armed()) {
+ arm_last();
+ }
+
+ delete_nodes_.push(&node);
+}
+
+void conveyor_sink_set::fail(error &&error) {
+ /// @todo call error_handler
+}
+
+conveyor_sink_set::conveyor_sink_set(event_loop &event_loop)
+ : event{event_loop} {}
+
+void conveyor_sink_set::add(conveyor<void> &&sink) {
+ auto nas = conveyor<void>::from_conveyor(std::move(sink));
+ SAW_ASSERT(nas) { return; }
+ conveyor_storage *storage = nas->next_storage();
+
+ own<sink_conveyor_node> sink_node = nullptr;
+ try {
+ sink_node = heap<sink_conveyor_node>(std::move(nas), *this);
+ } catch (std::bad_alloc &) {
+ return;
+ }
+ if (storage) {
+ storage->set_parent(sink_node.get());
+ }
+
+ sink_nodes_.emplace_back(std::move(sink_node));
+}
+
+void conveyor_sink_set::fire() {
+ while (!delete_nodes_.empty()) {
+ conveyor_node *node = delete_nodes_.front();
+ /*auto erased = */ std::remove_if(sink_nodes_.begin(),
+ sink_nodes_.end(),
+ [node](own<conveyor_node> &element) {
+ return node == element.get();
+ });
+ delete_nodes_.pop();
+ }
+}
+
+convert_conveyor_node_base::convert_conveyor_node_base(own<conveyor_node> &&dep)
+ : child_mixin_{std::move(dep), *this} {}
+
+void convert_conveyor_node_base::get_result(error_or_value &err_or_val) {
+ get_impl(err_or_val);
+}
+
+void attach_conveyor_node_base::get_result(
+ error_or_value &err_or_val) noexcept {
+ if (child_mixin_.child) {
+ child_mixin_.child->get_result(err_or_val);
+ }
+}
+
+error_or<own<conveyor_node>>
+attach_conveyor_node_base::swap_child(own<conveyor_node> &&swapee_) noexcept {
+ return child_mixin_.swap_child(std::move(swapee_));
+}
+
+conveyor_storage *attach_conveyor_node_base::next_storage() noexcept {
+ if (!child_mixin_.child) {
+ return nullptr;
+ }
+
+ return child_mixin_.child->next_storage();
+}
+
+void detach_conveyor(conveyor<void> &&conveyor) {
+ event_loop &loop = current_event_loop();
+ conveyor_sink_set &sink = loop.daemon();
+ sink.add(std::move(conveyor));
+}
+} // namespace saw
diff --git a/modules/async/async.h b/modules/async/async.h
new file mode 100644
index 0000000..8190be0
--- /dev/null
+++ b/modules/async/async.h
@@ -0,0 +1,1023 @@
+#pragma once
+
+#include <forstio/core/common.h>
+#include <forstio/core/error.h>
+
+#include <chrono>
+#include <functional>
+#include <limits>
+#include <list>
+#include <queue>
+#include <type_traits>
+
+namespace saw {
+class conveyor_storage;
+class conveyor_node {
+public:
+ conveyor_node();
+ virtual ~conveyor_node() = default;
+
+ /**
+ * Internal method to retrieve results from children
+ */
+ virtual void get_result(error_or_value &err_or_val) = 0;
+
+ /**
+ * Swap out child with another one
+ */
+ virtual error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee_) = 0;
+
+ /**
+ * Retrieve the next storage node
+ */
+ virtual conveyor_storage *next_storage() = 0;
+
+ /**
+ * Notify that a new parent was attached
+ * Only relevant for the feeding nodes
+ */
+ virtual void notify_parent_attached(conveyor_node &){};
+};
+
+class conveyor_node_with_child_mixin final {
+public:
+ own<conveyor_node> child = nullptr;
+
+ conveyor_node_with_child_mixin(own<conveyor_node> &&child_,
+ conveyor_node &owner_);
+ ~conveyor_node_with_child_mixin() = default;
+
+ /**
+ * Swap out children and return the child ptr, since the caller is the child
+ * itself. Stack needs to be cleared before the child is destroyed, so the
+ * swapped out node is returned as well.
+ */
+ error_or<own<conveyor_node>> swap_child(own<conveyor_node> &&swapee);
+};
+
+class conveyor_node_with_parent_mixin final {
+public:
+ conveyor_node *parent = nullptr;
+
+ error_or<own<conveyor_node>>
+ swap_child_of_parent(own<conveyor_node> &&swapee) {
+ SAW_ASSERT(parent) {
+ return make_error<err::invalid_state>(
+ "Can't swap child, because parent doesn't exist");
+ }
+
+ return parent->swap_child(std::move(swapee));
+ }
+ void change_parent(conveyor_node *p) { parent = p; }
+};
+
+class event_loop;
+class wait_scope;
+/*
+ * Event class similar to capn'proto.
+ * https://github.com/capnproto/capnproto
+ */
+class event {
+private:
+ event_loop &loop_;
+ event **prev_ = nullptr;
+ event *next_ = nullptr;
+
+ friend class event_loop;
+
+public:
+ event();
+ event(event_loop &loop);
+ virtual ~event();
+
+ virtual void fire() = 0;
+
+ void arm_next();
+ void arm_later();
+ void arm_last();
+ void disarm();
+
+ bool is_armed() const;
+};
+
+class conveyor_storage {
+protected:
+ conveyor_storage *parent_ = nullptr;
+
+public:
+ conveyor_storage();
+ virtual ~conveyor_storage();
+
+ virtual size_t space() const = 0;
+ virtual size_t queued() const = 0;
+ virtual void child_has_fired() = 0;
+ virtual void parent_has_fired() = 0;
+
+ virtual void set_parent(conveyor_storage *parent) = 0;
+ conveyor_storage *get_parent() const;
+};
+
+class conveyor_event_storage : public conveyor_storage, public event {
+public:
+ conveyor_event_storage();
+ virtual ~conveyor_event_storage() = default;
+
+ void set_parent(conveyor_storage *parent) override;
+};
+
+class conveyor_base {
+protected:
+ own<conveyor_node> node_;
+
+public:
+ conveyor_base(own<conveyor_node> &&node_p);
+ virtual ~conveyor_base() = default;
+
+ conveyor_base(conveyor_base &&) = default;
+ conveyor_base &operator=(conveyor_base &&) = default;
+
+ void get(error_or_value &err_or_val);
+};
+
+template <typename T> class conveyor;
+
+template <typename T> conveyor<T> chained_conveyor_type(T *);
+
+// template <typename T> Conveyor<T> chainedConveyorType(Conveyor<T> *);
+
+template <typename T> T remove_error_or_type(T *);
+
+template <typename T> T remove_error_or_type(error_or<T> *);
+
+template <typename T>
+using remove_error_or = decltype(remove_error_or_type((T *)nullptr));
+
+template <typename T>
+using chained_conveyors = decltype(chained_conveyor_type((T *)nullptr));
+
+template <typename Func, typename T>
+using conveyor_result =
+ chained_conveyors<remove_error_or<return_type<Func, T>>>;
+
+struct propagate_error {
+public:
+ error operator()(const error &err) const;
+ error operator()(error &&err);
+};
+
+class conveyor_sink {
+private:
+ own<conveyor_node> node_;
+
+public:
+ conveyor_sink();
+ conveyor_sink(own<conveyor_node> &&node);
+
+ conveyor_sink(conveyor_sink &&) = default;
+ conveyor_sink &operator=(conveyor_sink &&) = default;
+};
+
+template <typename T> class merge_conveyor_node_data;
+
+template <typename T> class merge_conveyor {
+private:
+ lent<merge_conveyor_node_data<T>> data_;
+
+public:
+ merge_conveyor() = default;
+ merge_conveyor(lent<merge_conveyor_node_data<T>> d);
+ ~merge_conveyor();
+
+ void attach(conveyor<T> conv);
+};
+
+/**
+ * Main interface for async operations.
+ */
+template <typename T> class conveyor final : public conveyor_base {
+public:
+ /**
+ * Construct an immediately fulfilled node
+ */
+ conveyor(fix_void<T> value);
+
+ /**
+ * Construct an immediately failed node
+ */
+ conveyor(error &&err);
+
+ /**
+ * Construct a conveyor with a child node
+ */
+ conveyor(own<conveyor_node> node_p);
+
+ conveyor(conveyor<T> &&) = default;
+ conveyor<T> &operator=(conveyor<T> &&) = default;
+
+ /**
+ * This method converts values or errors from children
+ */
+ template <typename Func, typename ErrorFunc = propagate_error>
+ [[nodiscard]] conveyor_result<Func, T>
+ then(Func &&func, ErrorFunc &&error_func = propagate_error());
+
+ /**
+ * 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>, merge_conveyor<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 = propagate_error>
+ void detach(ErrorFunc &&err_func = propagate_error());
+ /**
+ * Creates a local sink which drops elements, but lifetime control remains
+ * in your hand.
+ */
+ template <typename ErrorFunc = propagate_error>
+ [[nodiscard]] conveyor_sink
+ sink(ErrorFunc &&error_func = propagate_error());
+
+ /**
+ * If no sink() or detach() is used you have to take elements out of the
+ * chain yourself.
+ */
+ error_or<fix_void<T>> take();
+
+ /** @todo implement
+ * Specifically pump elements through this chain with the provided
+ * wait_scope
+ */
+ void poll(wait_scope &wait_scope);
+
+ // helper
+ static conveyor<T> to_conveyor(own<conveyor_node> node);
+
+ // helper
+ static own<conveyor_node> from_conveyor(conveyor<T> conveyor);
+};
+
+template <typename Func> conveyor_result<Func, void> exec_later(Func &&func);
+
+/*
+ * Join Conveyors into a single one
+ */
+template <typename... Args>
+conveyor<std::tuple<Args...>>
+join_conveyors(std::tuple<conveyor<Args>...> &conveyors);
+
+template <typename T> class conveyor_feeder {
+public:
+ virtual ~conveyor_feeder() = 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;
+
+ virtual error swap(conveyor<T> &&conveyor) noexcept = 0;
+};
+
+template <> class conveyor_feeder<void> {
+public:
+ virtual ~conveyor_feeder() = default;
+
+ virtual void feed(void_t &&value = void_t{}) = 0;
+ virtual void fail(error &&error) = 0;
+
+ virtual size_t space() const = 0;
+ virtual size_t queued() const = 0;
+
+ virtual error swap(conveyor<void_t> &&conveyor) noexcept = 0;
+};
+
+template <typename T> struct conveyor_and_feeder {
+ own<conveyor_feeder<T>> feeder;
+ class conveyor<T> conveyor;
+};
+
+template <typename T> conveyor_and_feeder<T> new_conveyor_and_feeder();
+
+template <typename T> conveyor_and_feeder<T> one_time_conveyor_and_feeder();
+
+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 event_port {
+public:
+ virtual ~event_port() = default;
+
+ virtual conveyor<void> on_signal(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 sink_conveyor_node;
+
+class conveyor_sink_set 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 sink_conveyor_node;
+
+ void destroy_sink_conveyor_node(conveyor_node &sink_node);
+ void fail(error &&err);
+
+ std::list<own<conveyor_node>> sink_nodes_;
+
+ std::queue<conveyor_node *> delete_nodes_;
+
+ std::function<void(error &&)> error_handler_;
+
+public:
+ // ConveyorSinks();
+ // ConveyorSinks(EventLoop& event_loop);
+ conveyor_sink_set() = default;
+ conveyor_sink_set(event_loop &event_loop);
+
+ void add(conveyor<void> &&node);
+
+ void fire() override;
+};
+
+/*
+ * EventLoop class similar to capn'proto.
+ * https://github.com/capnproto/capnproto
+ */
+class event_loop {
+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<event_port> event_port_ = nullptr;
+
+ own<conveyor_sink_set> daemon_sink_ = nullptr;
+
+ // functions
+ void set_runnable(bool runnable);
+
+ friend class wait_scope;
+ void enter_scope();
+ void leave_scope();
+
+ bool turn_loop();
+ bool turn();
+
+public:
+ event_loop();
+ event_loop(own<event_port> &&port);
+ ~event_loop();
+
+ event_loop(event_loop &&) = default;
+ event_loop &operator=(event_loop &&) = default;
+
+ bool wait();
+ bool wait(const std::chrono::steady_clock::duration &);
+ bool wait(const std::chrono::steady_clock::time_point &);
+ bool poll();
+
+ event_port *get_event_port();
+
+ conveyor_sink_set &daemon();
+};
+
+/*
+ * WaitScope class similar to capn'proto.
+ * https://github.com/capnproto/capnproto
+ */
+class wait_scope {
+private:
+ event_loop &loop_;
+
+public:
+ wait_scope(event_loop &loop);
+ ~wait_scope();
+
+ void wait();
+ void wait(const std::chrono::steady_clock::duration &);
+ void wait(const std::chrono::steady_clock::time_point &);
+ void poll();
+};
+
+template <typename Func> conveyor_result<Func, void> yield_next(Func &&func);
+
+template <typename Func> conveyor_result<Func, void> yield_later(Func &&func);
+
+template <typename Func> conveyor_result<Func, void> yield_last(Func &&func);
+} // namespace saw
+
+// Secret stuff
+// Aka private semi hidden classes
+namespace saw {
+
+template <typename Out, typename In> struct fix_void_caller {
+ template <typename Func> static Out apply(Func &func, In &&in) {
+ return func(std::move(in));
+ }
+};
+
+template <typename Out> struct fix_void_caller<Out, void_t> {
+ template <typename Func> static Out apply(Func &func, void_t &&in) {
+ (void)in;
+ return func();
+ }
+};
+
+template <typename In> struct fix_void_caller<void_t, In> {
+ template <typename Func> static void_t apply(Func &func, In &&in) {
+ func(std::move(in));
+ return void_t{};
+ }
+};
+
+template <> struct fix_void_caller<void_t, void_t> {
+ template <typename Func> static void_t apply(Func &func, void_t &&in) {
+ (void)in;
+ func();
+ return void_t{};
+ }
+};
+
+template <typename T> class adapt_conveyor_node;
+
+template <typename T>
+class adapt_conveyor_feeder final : public conveyor_feeder<unfix_void<T>> {
+private:
+ adapt_conveyor_node<T> *feedee_ = nullptr;
+
+public:
+ ~adapt_conveyor_feeder();
+
+ void set_feedee(adapt_conveyor_node<T> *feedee);
+
+ void feed(T &&value) override;
+ void fail(error &&error) override;
+
+ size_t space() const override;
+ size_t queued() const override;
+
+ error swap(conveyor<T> &&conv) noexcept override;
+};
+
+template <typename T>
+class adapt_conveyor_node final : public conveyor_node,
+ public conveyor_event_storage {
+private:
+ adapt_conveyor_feeder<T> *feeder_ = nullptr;
+
+ std::queue<error_or<unfix_void<T>>> storage_;
+
+ conveyor_node_with_parent_mixin parent_node_;
+
+public:
+ adapt_conveyor_node();
+ ~adapt_conveyor_node();
+
+ void set_feeder(adapt_conveyor_feeder<T> *feeder);
+
+ void feed(T &&value);
+ void fail(error &&error);
+
+ // ConveyorNode
+ void get_result(error_or_value &err_or_val) override;
+
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee) noexcept override;
+
+ conveyor_storage *next_storage() noexcept override;
+ void notify_parent_attached(conveyor_node &) noexcept override;
+
+ // ConveyorStorage
+ size_t space() const override;
+ size_t queued() const override;
+
+ void child_has_fired() override;
+ void parent_has_fired() override;
+
+ // Event
+ void fire() override;
+};
+
+template <typename T> class one_time_conveyor_node;
+
+template <typename T>
+class one_time_conveyor_feeder final : public conveyor_feeder<unfix_void<T>> {
+private:
+ one_time_conveyor_node<T> *feedee_ = nullptr;
+
+public:
+ ~one_time_conveyor_feeder();
+
+ void set_feedee(one_time_conveyor_node<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 one_time_conveyor_node final : public conveyor_node,
+ public conveyor_storage,
+ public event {
+private:
+ one_time_conveyor_feeder<T> *feeder_ = nullptr;
+
+ bool passed_ = false;
+ maybe<error_or<T>> storage_ = std::nullopt;
+
+public:
+ ~one_time_conveyor_node();
+
+ void set_feeder(one_time_conveyor_feeder<T> *feeder);
+
+ void feed(T &&value);
+ void fail(error &&error);
+
+ // ConveyorNode
+ void get_result(error_or_value &err_or_val) override;
+
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee) override;
+
+ // ConveyorStorage
+ size_t space() const override;
+ size_t queued() const override;
+
+ void child_has_fired() override {}
+ void parent_has_fired() override;
+
+ // Event
+ void fire() override;
+};
+
+/**
+ * This class buffers and saves incoming elements and acts as an interrupt node
+ * for processing calls
+ */
+class queue_buffer_conveyor_node_base : public conveyor_node,
+ public conveyor_event_storage {
+protected:
+ conveyor_node_with_child_mixin child_mixin_;
+
+public:
+ queue_buffer_conveyor_node_base(own<conveyor_node> child_)
+ : conveyor_event_storage{}, child_mixin_{std::move(child_), *this} {}
+ virtual ~queue_buffer_conveyor_node_base() = default;
+
+ /**
+ * Use mixin
+ */
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee_) noexcept override;
+
+ conveyor_storage *next_storage() noexcept override {
+ return static_cast<conveyor_storage *>(this);
+ }
+};
+
+template <typename T>
+class queue_buffer_conveyor_node final
+ : public queue_buffer_conveyor_node_base {
+private:
+ std::queue<error_or<T>> storage_;
+ size_t max_store_;
+
+public:
+ queue_buffer_conveyor_node(own<conveyor_node> dep, size_t max_size)
+ : queue_buffer_conveyor_node_base{std::move(dep)}, max_store_{
+ max_size} {}
+ // Event
+ void fire() override;
+ // ConveyorNode
+ void get_result(error_or_value &eov) noexcept override;
+
+ // ConveyorStorage
+ size_t space() const override;
+ size_t queued() const override;
+
+ void child_has_fired() override;
+ void parent_has_fired() override;
+};
+
+class attach_conveyor_node_base : public conveyor_node {
+protected:
+ conveyor_node_with_child_mixin child_mixin_;
+
+public:
+ attach_conveyor_node_base(own<conveyor_node> &&child_)
+ : child_mixin_{std::move(child_), *this} {}
+
+ virtual ~attach_conveyor_node_base() = default;
+
+ void get_result(error_or_value &err_or_val) noexcept override;
+
+ /**
+ * Use mixin
+ */
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee_) noexcept override;
+
+ conveyor_storage *next_storage() noexcept override;
+};
+
+template <typename... Args>
+class attach_conveyor_node final : public attach_conveyor_node_base {
+public:
+ attach_conveyor_node(own<conveyor_node> &&dep, Args &&...args)
+ : attach_conveyor_node_base(std::move(dep)), attached_data_{
+ std::move(args...)} {}
+
+private:
+ std::tuple<Args...> attached_data_;
+};
+
+class convert_conveyor_node_base : public conveyor_node {
+public:
+ convert_conveyor_node_base(own<conveyor_node> &&dep);
+ virtual ~convert_conveyor_node_base() = default;
+
+ void get_result(error_or_value &err_or_val) override;
+
+ virtual void get_impl(error_or_value &err_or_val) = 0;
+
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee) noexcept override;
+
+ conveyor_storage *next_storage() noexcept override;
+
+protected:
+ conveyor_node_with_child_mixin child_mixin_;
+};
+
+template <typename T, typename DepT, typename Func, typename ErrorFunc>
+class convert_conveyor_node final : public convert_conveyor_node_base {
+private:
+ Func func_;
+ ErrorFunc error_func_;
+
+ static_assert(std::is_same<DepT, remove_error_or<DepT>>::value,
+ "Should never be of type ErrorOr");
+
+public:
+ convert_conveyor_node(own<conveyor_node> &&dep, Func &&func,
+ ErrorFunc &&error_func)
+ : convert_conveyor_node_base(std::move(dep)), func_{std::move(func)},
+ error_func_{std::move(error_func)} {}
+
+ void get_impl(error_or_value &err_or_val) noexcept override {
+ error_or<unfix_void<DepT>> dep_eov;
+ error_or<unfix_void<remove_error_or<T>>> &eov =
+ err_or_val.as<unfix_void<remove_error_or<T>>>();
+ if (child_mixin_.child) {
+ child_mixin_.child->get_result(dep_eov);
+ if (dep_eov.is_value()) {
+ try {
+
+ eov = fix_void_caller<T, DepT>::apply(
+ func_, std::move(dep_eov.get_value()));
+ } catch (const std::bad_alloc &) {
+ eov = make_error<err::out_of_memory>("Out of memory");
+ } catch (const std::exception &) {
+ eov = make_error<err::invalid_state>(
+ "Exception in chain occured. Return ErrorOr<T> if you "
+ "want to handle errors which are recoverable");
+ }
+ } else if (dep_eov.is_error()) {
+ eov = error_func_(std::move(dep_eov.get_error()));
+ } else {
+ eov = make_error<err::invalid_state>("No value set in dependency");
+ }
+ } else {
+ eov = make_error<err::invalid_state>("Conveyor doesn't have child");
+ }
+ }
+};
+
+class sink_conveyor_node final : public conveyor_node,
+ public conveyor_event_storage {
+private:
+ conveyor_node_with_child_mixin child_mixin_;
+ conveyor_sink_set *conveyor_sink_;
+
+public:
+ sink_conveyor_node(own<conveyor_node> node, conveyor_sink_set &conv_sink)
+ : conveyor_event_storage{}, child_mixin_{std::move(node), *this},
+ conveyor_sink_{&conv_sink} {}
+
+ sink_conveyor_node(own<conveyor_node> node)
+ : conveyor_event_storage{}, child_mixin_{std::move(node), *this},
+ 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_mixin_.child = nullptr;
+
+ if (conveyor_sink_) {
+ conveyor_sink_->destroy_sink_conveyor_node(*this);
+ conveyor_sink_ = nullptr;
+ }
+ }
+
+ // ConveyorStorage
+ size_t space() const override { return 1; }
+ size_t queued() const override { return 0; }
+
+ // ConveyorNode
+ void get_result(error_or_value &err_or_val) noexcept override {
+ err_or_val.as<void_t>() =
+ make_error<err::invalid_state>("In a sink node no result can be returned");
+ }
+
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee) noexcept override {
+ return child_mixin_.swap_child(std::move(swapee));
+ }
+
+ // ConveyorStorage
+ void child_has_fired() override {
+ if (child_mixin_.child) {
+ error_or<void> dep_eov;
+ child_mixin_.child->get_result(dep_eov);
+ if (dep_eov.is_error()) {
+ if (dep_eov.get_error().is_critical()) {
+ if (!is_armed()) {
+ arm_last();
+ }
+ }
+ if (conveyor_sink_) {
+ conveyor_sink_->fail(std::move(dep_eov.get_error()));
+ }
+ }
+ }
+ }
+
+ /*
+ * No parent needs to be fired since we always have space
+ */
+ void parent_has_fired() override {}
+
+ conveyor_storage *next_storage() override {
+ // Should never happen though
+ assert(false);
+ return nullptr;
+ // return static_cast<ConveyorStorage*>(this);
+ }
+};
+
+class immediate_conveyor_node_base : public conveyor_node,
+ public conveyor_event_storage {
+private:
+public:
+ immediate_conveyor_node_base();
+
+ virtual ~immediate_conveyor_node_base() = default;
+
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee) noexcept override {
+ (void)swapee;
+ return make_error<err::not_supported>("Node doesn't support swapping");
+ }
+
+ conveyor_storage *next_storage() noexcept override {
+ return static_cast<conveyor_storage *>(this);
+ }
+};
+
+template <typename T>
+class immediate_conveyor_node final : public immediate_conveyor_node_base {
+private:
+ error_or<fix_void<T>> value_;
+ uint8_t retrieved_;
+
+public:
+ immediate_conveyor_node(fix_void<T> &&val);
+ immediate_conveyor_node(error &&error);
+
+ // ConveyorStorage
+ size_t space() const override;
+ size_t queued() const override;
+
+ void child_has_fired() override;
+ void parent_has_fired() override;
+
+ // ConveyorNode
+ void get_result(error_or_value &err_or_val) noexcept override {
+ if (retrieved_ > 0) {
+ err_or_val.as<fix_void<T>>() =
+ make_error<err::buffer_exhausted>("Already taken value");
+ } else {
+ err_or_val.as<fix_void<T>>() = std::move(value_);
+ }
+ if (queued() > 0) {
+ ++retrieved_;
+ }
+ }
+
+ // Event
+ void fire() override;
+};
+
+/*
+ * Collects every incoming value and throws it in one lane
+ */
+class merge_conveyor_node_base : public conveyor_node,
+ public conveyor_event_storage {
+public:
+ merge_conveyor_node_base();
+
+ virtual ~merge_conveyor_node_base() = default;
+
+ conveyor_storage *next_storage() noexcept override {
+ return static_cast<conveyor_storage *>(this);
+ }
+};
+
+template <typename T>
+class merge_conveyor_node : public merge_conveyor_node_base {
+private:
+ class appendage final : public conveyor_node, public conveyor_storage {
+ public:
+ own<conveyor_node> child;
+ merge_conveyor_node *merger;
+
+ maybe<error_or<fix_void<T>>> error_or_value_;
+
+ public:
+ appendage(own<conveyor_node> n, merge_conveyor_node &m)
+ : conveyor_storage{}, child{std::move(n)}, merger{&m},
+ error_or_value_{std::nullopt} {}
+
+ bool child_storage_has_element_queued() const {
+ if (!child) {
+ return false;
+ }
+ conveyor_storage *storage = child->next_storage();
+ if (storage) {
+ return storage->queued() > 0;
+ }
+ return false;
+ }
+
+ void get_appendage_result(error_or_value &eov);
+
+ /**
+ * ConveyorNode
+ */
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&swapee_) override;
+
+ conveyor_storage *next_storage() noexcept override {
+ return static_cast<conveyor_storage *>(this);
+ }
+
+ void get_result(error_or_value &err_or_val) override;
+
+ /**
+ * ConveyorStorage
+ */
+ size_t space() const override;
+
+ size_t queued() const override;
+
+ void child_has_fired() override;
+
+ void parent_has_fired() override;
+
+ void set_parent(conveyor_storage *par) override;
+ };
+
+ friend class merge_conveyor_node_data<T>;
+ friend class appendage;
+
+ our<merge_conveyor_node_data<T>> data_;
+ size_t next_appendage_ = 0;
+
+public:
+ merge_conveyor_node(our<merge_conveyor_node_data<T>> data);
+ ~merge_conveyor_node();
+ // ConveyorNode
+ error_or<own<conveyor_node>>
+ swap_child(own<conveyor_node> &&c) noexcept override;
+
+ // Event
+ void get_result(error_or_value &err_or_val) noexcept override;
+
+ void fire() override;
+
+ // ConveyorStorage
+ size_t space() const override;
+ size_t queued() const override;
+ void child_has_fired() override;
+ void parent_has_fired() override;
+};
+
+template <typename T> class merge_conveyor_node_data {
+public:
+ std::vector<own<typename merge_conveyor_node<T>::appendage>> appendages;
+
+ merge_conveyor_node<T> *merger = nullptr;
+
+public:
+ void attach(conveyor<T> conv);
+
+ void governing_node_destroyed();
+};
+
+/*
+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 get_result(ErrorOrValue& eov) override;
+ };
+
+ std::tuple<Appendage<Args>...> appendages;
+
+public:
+};
+
+*/
+
+} // namespace saw
+
+#include "async.tmpl.h"
diff --git a/modules/async/async.tmpl.h b/modules/async/async.tmpl.h
new file mode 100644
index 0000000..9569f60
--- /dev/null
+++ b/modules/async/async.tmpl.h
@@ -0,0 +1,767 @@
+#pragma once
+
+#include <forstio/core/common.h>
+#include <forstio/core/error.h>
+
+#include <cassert>
+// Template inlining
+
+namespace saw {
+
+template <typename Func> conveyor_result<Func, void> execLater(Func &&func) {
+ conveyor<void> conveyor{fix_void<void>{}};
+ return conveyor.then(std::move(func));
+}
+
+template <typename T>
+conveyor<T>::conveyor(fix_void<T> value) : conveyor_base(nullptr) {
+ // Is there any way to do this?
+ // @todo new conveyor_base constructor for Immediate values
+
+ own<immediate_conveyor_node<fix_void<T>>> immediate =
+ heap<immediate_conveyor_node<fix_void<T>>>(std::move(value));
+
+ if (!immediate) {
+ return;
+ }
+
+ node_ = std::move(immediate);
+}
+
+template <typename T>
+conveyor<T>::conveyor(error &&err) : conveyor_base(nullptr) {
+ own<immediate_conveyor_node<fix_void<T>>> immediate =
+ heap<immediate_conveyor_node<fix_void<T>>>(std::move(err));
+
+ if (!immediate) {
+ return;
+ }
+
+ node_ = std::move(immediate);
+}
+
+template <typename T>
+conveyor<T>::conveyor(own<conveyor_node> node_p)
+ : conveyor_base{std::move(node_p)} {}
+
+template <typename T>
+template <typename Func, typename ErrorFunc>
+conveyor_result<Func, T> conveyor<T>::then(Func &&func,
+ ErrorFunc &&error_func) {
+ own<conveyor_node> conversion_node =
+ heap<convert_conveyor_node<fix_void<return_type<Func, T>>, fix_void<T>,
+ Func, ErrorFunc>>(
+ std::move(node_), std::move(func), std::move(error_func));
+
+ return conveyor<remove_error_or<return_type<Func, T>>>::to_conveyor(
+ std::move(conversion_node));
+}
+
+template <typename T> conveyor<T> conveyor<T>::buffer(size_t size) {
+ SAW_ASSERT(node_) { return conveyor<T>{own<conveyor_node>{nullptr}}; }
+ conveyor_storage *storage = node_->next_storage();
+ SAW_ASSERT(storage) { return conveyor<T>{own<conveyor_node>{nullptr}}; }
+
+ own<queue_buffer_conveyor_node<fix_void<T>>> storage_node =
+ heap<queue_buffer_conveyor_node<fix_void<T>>>(std::move(node_), size);
+
+ conveyor_storage *storage_ptr =
+ static_cast<conveyor_storage *>(storage_node.get());
+
+ storage->set_parent(storage_ptr);
+ return conveyor<T>{std::move(storage_node)};
+}
+
+template <typename T>
+template <typename... Args>
+conveyor<T> conveyor<T>::attach(Args &&...args) {
+ own<attach_conveyor_node<Args...>> attach_node =
+ heap<attach_conveyor_node<Args...>>(std::move(node_),
+ std::move(args...));
+ return conveyor<T>{std::move(attach_node)};
+}
+
+template <typename T>
+std::pair<conveyor<T>, merge_conveyor<T>> conveyor<T>::merge() {
+ our<merge_conveyor_node_data<T>> data =
+ share<merge_conveyor_node_data<T>>();
+
+ own<merge_conveyor_node<T>> merge_node = heap<merge_conveyor_node<T>>(data);
+
+ SAW_ASSERT(node_) {
+ return std::make_pair(conveyor<T>{own<conveyor_node>{nullptr}},
+ merge_conveyor<T>{});
+ }
+ conveyor_storage *storage = node_->next_storage();
+ SAW_ASSERT(storage) {
+ return std::make_pair(conveyor<T>{own<conveyor_node>{nullptr}},
+ merge_conveyor<T>{});
+ }
+
+ data->attach(conveyor<T>::to_conveyor(std::move(node_)));
+
+ merge_conveyor<T> node_ref{data};
+
+ return std::make_pair(conveyor<T>{std::move(merge_node)},
+ std::move(node_ref));
+}
+
+template <>
+template <typename ErrorFunc>
+conveyor_sink conveyor<void>::sink(ErrorFunc &&error_func) {
+ conveyor_storage *storage = node_->next_storage();
+ SAW_ASSERT(storage) { return conveyor_sink{}; }
+
+ own<sink_conveyor_node> sink_node =
+ heap<sink_conveyor_node>(std::move(node_));
+ conveyor_storage *storage_ptr =
+ static_cast<conveyor_storage *>(sink_node.get());
+
+ storage->set_parent(storage_ptr);
+
+ return conveyor_sink{std::move(sink_node)};
+}
+
+void detach_conveyor(conveyor<void> &&conveyor);
+
+template <typename T>
+template <typename ErrorFunc>
+void conveyor<T>::detach(ErrorFunc &&func) {
+ detach_conveyor(std::move(then([](T &&) {}, std::move(func))));
+}
+
+template <>
+template <typename ErrorFunc>
+void conveyor<void>::detach(ErrorFunc &&func) {
+ detach_conveyor(std::move(then([]() {}, std::move(func))));
+}
+
+template <typename T>
+conveyor<T> conveyor<T>::to_conveyor(own<conveyor_node> node) {
+ return conveyor<T>{std::move(node)};
+}
+
+template <typename T>
+own<conveyor_node> conveyor<T>::from_conveyor(conveyor<T> conveyor) {
+ return std::move(conveyor.node_);
+}
+
+template <typename T> error_or<fix_void<T>> conveyor<T>::take() {
+ SAW_ASSERT(node_) {
+ return error_or<fix_void<T>>{
+ make_error<err::invalid_state>("conveyor in invalid state")};
+ }
+ conveyor_storage *storage = node_->next_storage();
+ if (storage) {
+ if (storage->queued() > 0) {
+ error_or<fix_void<T>> result;
+ node_->get_result(result);
+ return result;
+ } else {
+ return error_or<fix_void<T>>{
+ make_error<err::buffer_exhausted>("conveyor buffer has no elements")};
+ }
+ } else {
+ return error_or<fix_void<T>>{
+ make_error<err::invalid_state>("conveyor node has no child storage")};
+ }
+}
+
+template <typename T> conveyor_and_feeder<T> new_conveyor_and_feeder() {
+ own<adapt_conveyor_feeder<fix_void<T>>> feeder =
+ heap<adapt_conveyor_feeder<fix_void<T>>>();
+ own<adapt_conveyor_node<fix_void<T>>> node =
+ heap<adapt_conveyor_node<fix_void<T>>>();
+
+ feeder->set_feedee(node.get());
+ node->set_feeder(feeder.get());
+
+ return conveyor_and_feeder<T>{std::move(feeder),
+ conveyor<T>::to_conveyor(std::move(node))};
+}
+
+// QueueBuffer
+template <typename T> void queue_buffer_conveyor_node<T>::fire() {
+ if (child_mixin_.child) {
+ if (!storage_.empty()) {
+ if (storage_.front().is_error()) {
+ if (storage_.front().get_error().is_critical()) {
+ child_mixin_.child = nullptr;
+ }
+ }
+ }
+ }
+
+ bool has_space_before_fire = space() > 0;
+
+ if (parent_) {
+ parent_->child_has_fired();
+ if (!storage_.empty() && parent_->space() > 0) {
+ arm_later();
+ }
+ }
+
+ if (!child_mixin_.child) {
+ while (!storage_.empty()) {
+ storage_.pop();
+ }
+ return;
+ }
+
+ conveyor_storage *ch_storage = child_mixin_.child->next_storage();
+ if (ch_storage && !has_space_before_fire) {
+ ch_storage->parent_has_fired();
+ }
+}
+
+template <typename T>
+void queue_buffer_conveyor_node<T>::get_result(error_or_value &eov) noexcept {
+ error_or<T> &err_or_val = eov.as<T>();
+ err_or_val = std::move(storage_.front());
+ storage_.pop();
+}
+
+template <typename T> size_t queue_buffer_conveyor_node<T>::space() const {
+ return max_store_ - storage_.size();
+}
+
+template <typename T> size_t queue_buffer_conveyor_node<T>::queued() const {
+ return storage_.size();
+}
+
+template <typename T> void queue_buffer_conveyor_node<T>::child_has_fired() {
+ if (child_mixin_.child && storage_.size() < max_store_) {
+ error_or<T> eov;
+ child_mixin_.child->get_result(eov);
+
+ if (eov.is_error()) {
+ if (eov.get_error().is_critical()) {
+ }
+ }
+
+ storage_.push(std::move(eov));
+ if (!is_armed()) {
+ arm_later();
+ }
+ }
+}
+
+template <typename T> void queue_buffer_conveyor_node<T>::parent_has_fired() {
+ SAW_ASSERT(parent_) { return; }
+
+ if (parent_->space() == 0) {
+ return;
+ }
+
+ if (queued() > 0) {
+ arm_later();
+ }
+}
+
+template <typename T>
+immediate_conveyor_node<T>::immediate_conveyor_node(fix_void<T> &&val)
+ : value_{std::move(val)}, retrieved_{0} {}
+
+template <typename T>
+immediate_conveyor_node<T>::immediate_conveyor_node(error &&error)
+ : value_{std::move(error)}, retrieved_{0} {}
+
+template <typename T> size_t immediate_conveyor_node<T>::space() const {
+ return 0;
+}
+
+template <typename T> size_t immediate_conveyor_node<T>::queued() const {
+ return retrieved_ > 1 ? 0 : 1;
+}
+
+template <typename T> void immediate_conveyor_node<T>::child_has_fired() {
+ // Impossible case
+ assert(false);
+}
+
+template <typename T> void immediate_conveyor_node<T>::parent_has_fired() {
+ SAW_ASSERT(parent_) { return; }
+ assert(parent_->space() > 0);
+
+ if (queued() > 0) {
+ arm_next();
+ }
+}
+
+template <typename T> void immediate_conveyor_node<T>::fire() {
+
+ if (parent_) {
+ parent_->child_has_fired();
+ if (queued() > 0 && parent_->space() > 0) {
+ arm_last();
+ }
+ }
+}
+
+template <typename T>
+merge_conveyor<T>::merge_conveyor(lent<merge_conveyor_node_data<T>> d)
+ : data_{std::move(d)} {}
+
+template <typename T> merge_conveyor<T>::~merge_conveyor() {}
+
+template <typename T> void merge_conveyor<T>::attach(conveyor<T> conveyor) {
+ auto sp = data_.lock();
+ SAW_ASSERT(sp) { return; }
+
+ sp->attach(std::move(conveyor));
+}
+
+template <typename T>
+merge_conveyor_node<T>::merge_conveyor_node(our<merge_conveyor_node_data<T>> d)
+ : data_{d} {
+ SAW_ASSERT(data_) { return; }
+
+ data_->merger = this;
+}
+
+template <typename T> merge_conveyor_node<T>::~merge_conveyor_node() {}
+
+template <typename T>
+error_or<own<conveyor_node>>
+merge_conveyor_node<T>::swap_child(own<conveyor_node> &&swapee_) noexcept {
+ (void)swapee_;
+ return make_error<err::invalid_state>(
+ "merge_conveyor_node<T>::appendage should block calls to this class");
+}
+
+template <typename T>
+void merge_conveyor_node<T>::get_result(error_or_value &eov) noexcept {
+ error_or<fix_void<T>> &err_or_val = eov.as<fix_void<T>>();
+
+ SAW_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 = make_error<err::invalid_state>("No value in Merge appendages");
+}
+
+template <typename T> void merge_conveyor_node<T>::fire() {
+ SAW_ASSERT(queued() > 0) { return; }
+
+ if (parent_) {
+ parent_->child_has_fired();
+
+ if (queued() > 0 && parent_->space() > 0) {
+ arm_later();
+ }
+ }
+}
+
+template <typename T> size_t merge_conveyor_node<T>::space() const { return 0; }
+
+template <typename T> size_t merge_conveyor_node<T>::queued() const {
+ SAW_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 merge_conveyor_node<T>::child_has_fired() {
+ /// This can never happen
+ assert(false);
+}
+
+template <typename T> void merge_conveyor_node<T>::parent_has_fired() {
+ SAW_ASSERT(parent_) { return; }
+ if (queued() > 0) {
+ if (parent_->space() > 0) {
+ arm_later();
+ }
+ }
+}
+
+/**
+ * merge_conveyor_node<T>::Apendage
+ */
+
+template <typename T>
+error_or<own<conveyor_node>>
+merge_conveyor_node<T>::appendage::swap_child(own<conveyor_node> &&swapee_) {
+ own<conveyor_node> old_child = std::move(child);
+
+ child = std::move(swapee_);
+
+ // This case should never happen
+ SAW_ASSERT(old_child) { return make_error<err::invalid_state>("No child exists"); }
+
+ return old_child;
+}
+
+template <typename T>
+void merge_conveyor_node<T>::appendage::get_result(error_or_value &eov) {
+ error_or<fix_void<T>> &err_or_val = eov.as<fix_void<T>>();
+
+ SAW_ASSERT(queued() > 0) {
+ err_or_val =
+ make_error<err::invalid_state>("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> size_t merge_conveyor_node<T>::appendage::space() const {
+ SAW_ASSERT(merger) { return 0; }
+
+ if (error_or_value_.has_value()) {
+ return 0;
+ }
+
+ return 1;
+}
+
+template <typename T> size_t merge_conveyor_node<T>::appendage::queued() const {
+ SAW_ASSERT(merger) { return 0; }
+
+ if (error_or_value_.has_value()) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/// @todo delete this function. Replaced by the regular get_result
+template <typename T>
+void merge_conveyor_node<T>::appendage::get_appendage_result(
+ error_or_value &eov) {
+ error_or<fix_void<T>> &err_or_val = eov.as<fix_void<T>>();
+
+ SAW_ASSERT(queued() > 0) {
+ err_or_val =
+ make_error<err::invalid_state>("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 merge_conveyor_node<T>::appendage::child_has_fired() {
+ SAW_ASSERT(!error_or_value_.has_value()) { return; }
+ error_or<fix_void<T>> eov;
+ child->get_result(eov);
+
+ error_or_value_ = std::move(eov);
+
+ if (!merger->is_armed()) {
+ merger->arm_later();
+ }
+}
+
+template <typename T>
+void merge_conveyor_node<T>::appendage::parent_has_fired() {
+ conveyor_storage *child_storage = child->next_storage();
+ if (child_storage) {
+ child_storage->parent_has_fired();
+ }
+}
+
+template <typename T>
+void merge_conveyor_node<T>::appendage::set_parent(conveyor_storage *par) {
+ SAW_ASSERT(merger) { return; }
+
+ SAW_ASSERT(child) { return; }
+
+ parent_ = par;
+}
+
+template <typename T>
+void merge_conveyor_node_data<T>::attach(conveyor<T> conv) {
+ auto nas = conveyor<T>::from_conveyor(std::move(conv));
+ SAW_ASSERT(nas) { return; }
+ conveyor_storage *storage = nas->next_storage();
+ SAW_ASSERT(storage) { return; }
+
+ auto merge_node_appendage =
+ heap<typename merge_conveyor_node<T>::appendage>(std::move(nas),
+ *merger);
+ auto merge_node_appendage_ptr = merge_node_appendage.get();
+
+ storage->set_parent(merge_node_appendage.get());
+
+ SAW_ASSERT(merger) { return; }
+
+ conveyor_storage *mrg_storage = merger->next_storage();
+ SAW_ASSERT(mrg_storage) { return; }
+
+ merge_node_appendage->set_parent(mrg_storage);
+
+ appendages.push_back(std::move(merge_node_appendage));
+
+ /// @todo return this. necessary? maybe for the weird linking setup
+ /// maybe not
+ // return merge_node_appendage_ptr;
+}
+
+template <typename T>
+void merge_conveyor_node_data<T>::governing_node_destroyed() {
+ appendages.clear();
+ merger = nullptr;
+}
+
+template <typename T> adapt_conveyor_feeder<T>::~adapt_conveyor_feeder() {
+ if (feedee_) {
+ feedee_->set_feeder(nullptr);
+ feedee_ = nullptr;
+ }
+}
+
+template <typename T>
+void adapt_conveyor_feeder<T>::set_feedee(adapt_conveyor_node<T> *feedee_p) {
+ feedee_ = feedee_p;
+}
+
+template <typename T> void adapt_conveyor_feeder<T>::feed(T &&value) {
+ if (feedee_) {
+ feedee_->feed(std::move(value));
+ }
+}
+
+template <typename T> void adapt_conveyor_feeder<T>::fail(error &&error) {
+ if (feedee_) {
+ feedee_->fail(std::move(error));
+ }
+}
+
+template <typename T> size_t adapt_conveyor_feeder<T>::queued() const {
+ if (feedee_) {
+ return feedee_->queued();
+ }
+ return 0;
+}
+
+template <typename T> size_t adapt_conveyor_feeder<T>::space() const {
+ if (feedee_) {
+ return feedee_->space();
+ }
+ return 0;
+}
+
+template <typename T>
+error adapt_conveyor_feeder<T>::swap(conveyor<T> &&conv) noexcept {
+ SAW_ASSERT(feedee_) { return make_error<err::invalid_state>("No feedee connected"); }
+
+ auto node = conveyor<T>::from_conveyor(std::move(conv));
+
+ feedee_->swap_child(std::move(node));
+
+ return no_error();
+}
+
+template <typename T>
+adapt_conveyor_node<T>::adapt_conveyor_node() : conveyor_event_storage{} {}
+
+template <typename T> adapt_conveyor_node<T>::~adapt_conveyor_node() {
+ if (feeder_) {
+ feeder_->set_feedee(nullptr);
+ feeder_ = nullptr;
+ }
+}
+
+template <typename T>
+error_or<own<conveyor_node>>
+adapt_conveyor_node<T>::swap_child(own<conveyor_node> &&swapee) noexcept {
+ // This should return the owning pointer of this instance
+ auto myself_err = parent_node_.swap_child_of_parent(std::move(swapee));
+
+ if (myself_err.is_error()) {
+ return myself_err;
+ }
+
+ auto &myself = myself_err.get_value();
+
+ assert(myself.get() == this);
+
+ return myself_err;
+}
+
+template <typename T>
+conveyor_storage *adapt_conveyor_node<T>::next_storage() noexcept {
+ return static_cast<conveyor_storage *>(this);
+}
+
+template <typename T>
+void adapt_conveyor_node<T>::notify_parent_attached(
+ conveyor_node &par) noexcept {
+ parent_node_.change_parent(&par);
+}
+
+template <typename T>
+void adapt_conveyor_node<T>::set_feeder(adapt_conveyor_feeder<T> *feeder_p) {
+ feeder_ = feeder_p;
+}
+
+template <typename T> void adapt_conveyor_node<T>::feed(T &&value) {
+ storage_.push(std::move(value));
+ arm_next();
+}
+
+template <typename T> void adapt_conveyor_node<T>::fail(error &&error) {
+ storage_.push(std::move(error));
+ arm_next();
+}
+
+template <typename T> size_t adapt_conveyor_node<T>::queued() const {
+ return storage_.size();
+}
+
+template <typename T> size_t adapt_conveyor_node<T>::space() const {
+ return std::numeric_limits<size_t>::max() - storage_.size();
+}
+
+template <typename T>
+void adapt_conveyor_node<T>::get_result(error_or_value &err_or_val) {
+ if (!storage_.empty()) {
+ err_or_val.as<T>() = std::move(storage_.front());
+ storage_.pop();
+ } else {
+ err_or_val.as<T>() = make_error<err::invalid_state>(
+ "Signal for retrieval of storage sent even though no "
+ "data is present");
+ }
+}
+
+template <typename T> void adapt_conveyor_node<T>::child_has_fired() {
+ // Adapt node has no children
+ assert(false);
+}
+
+template <typename T> void adapt_conveyor_node<T>::parent_has_fired() {
+ SAW_ASSERT(parent_) { return; }
+
+ if (parent_->space() == 0) {
+ return;
+ }
+}
+
+template <typename T> void adapt_conveyor_node<T>::fire() {
+ if (parent_) {
+ parent_->child_has_fired();
+
+ if (storage_.size() > 0) {
+ arm_later();
+ }
+ }
+}
+
+template <typename T> one_time_conveyor_feeder<T>::~one_time_conveyor_feeder() {
+ if (feedee_) {
+ feedee_->set_feeder(nullptr);
+ feedee_ = nullptr;
+ }
+}
+
+template <typename T>
+void one_time_conveyor_feeder<T>::set_feedee(
+ one_time_conveyor_node<T> *feedee_p) {
+ feedee_ = feedee_p;
+}
+
+template <typename T> void one_time_conveyor_feeder<T>::feed(T &&value) {
+ if (feedee_) {
+ feedee_->feed(std::move(value));
+ }
+}
+
+template <typename T> void one_time_conveyor_feeder<T>::fail(error &&error) {
+ if (feedee_) {
+ feedee_->fail(std::move(error));
+ }
+}
+
+template <typename T> size_t one_time_conveyor_feeder<T>::queued() const {
+ if (feedee_) {
+ return feedee_->queued();
+ }
+ return 0;
+}
+
+template <typename T> size_t one_time_conveyor_feeder<T>::space() const {
+ if (feedee_) {
+ return feedee_->space();
+ }
+ return 0;
+}
+
+template <typename T> one_time_conveyor_node<T>::~one_time_conveyor_node() {
+ if (feeder_) {
+ feeder_->set_feedee(nullptr);
+ feeder_ = nullptr;
+ }
+}
+
+template <typename T>
+void one_time_conveyor_node<T>::set_feeder(
+ one_time_conveyor_feeder<T> *feeder_p) {
+ feeder_ = feeder_p;
+}
+
+template <typename T> void one_time_conveyor_node<T>::feed(T &&value) {
+ storage_ = std::move(value);
+ arm_next();
+}
+
+template <typename T> void one_time_conveyor_node<T>::fail(error &&error) {
+ storage_ = std::move(error);
+ arm_next();
+}
+
+template <typename T> size_t one_time_conveyor_node<T>::queued() const {
+ return storage_.has_value() ? 1 : 0;
+}
+
+template <typename T> size_t one_time_conveyor_node<T>::space() const {
+ return passed_ ? 0 : 1;
+}
+
+template <typename T>
+void one_time_conveyor_node<T>::get_result(error_or_value &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>() = make_error<err::invalid_state>(
+ "Signal for retrieval of storage sent even though no "
+ "data is present");
+ }
+}
+
+template <typename T> void one_time_conveyor_node<T>::fire() {
+ if (parent_) {
+ parent_->child_has_fired();
+ }
+}
+
+} // namespace saw
diff --git a/modules/codec-json/.nix/derivation.nix b/modules/codec-json/.nix/derivation.nix
new file mode 100644
index 0000000..3738e43
--- /dev/null
+++ b/modules/codec-json/.nix/derivation.nix
@@ -0,0 +1,30 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-codec-json";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.codec
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/codec-json/SConscript b/modules/codec-json/SConscript
new file mode 100644
index 0000000..772ac0b
--- /dev/null
+++ b/modules/codec-json/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+codec_json_env = env.Clone();
+
+codec_json_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+codec_json_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += codec_json_env.sources;
+env.headers += codec_json_env.headers;
+
+## Shared lib
+objects_shared = []
+codec_json_env.add_source_files(objects_shared, codec_json_env.sources, shared=True);
+codec_json_env.library_shared = codec_json_env.SharedLibrary('#build/forstio-codec-json', [objects_shared]);
+
+## Static lib
+objects_static = []
+codec_json_env.add_source_files(objects_static, codec_json_env.sources, shared=False);
+codec_json_env.library_static = codec_json_env.StaticLibrary('#build/forstio-codec-json', [objects_static]);
+
+# Set Alias
+env.Alias('library_codec_json', [codec_json_env.library_shared, codec_json_env.library_static]);
+
+env.targets += ['library_codec_json'];
+
+# Install
+env.Install('$prefix/lib/', [codec_json_env.library_shared, codec_json_env.library_static]);
+env.Install('$prefix/include/forstio/codec/json/', [codec_json_env.headers]);
diff --git a/modules/codec-json/SConstruct b/modules/codec-json/SConstruct
new file mode 100644
index 0000000..edd5f57
--- /dev/null
+++ b/modules/codec-json/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-codec'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/codec-json/json.h b/modules/codec-json/json.h
new file mode 100644
index 0000000..56cadc0
--- /dev/null
+++ b/modules/codec-json/json.h
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <forstio/core/buffer.h>
+#include <forstio/core/common.h>
+#include <forstio/codec/data.h>
+
+#include <algorithm>
+
+namespace saw {
+namespace encode {
+struct Json {};
+}
+
+template<typename Schema>
+class data<Schema, encode::Json> {
+private:
+ ring_buffer buffer_;
+public:
+ data():buffer_{}{}
+
+ data(std::size_t ring_size_):buffer_{ring_size_}{}
+
+ data(ring_buffer buff_):
+ buffer_{std::move(buff_)}
+ {}
+
+ data(const std::string_view& view__):
+ buffer_{view__.size()}
+ {
+ auto ptr = reinterpret_cast<const uint8_t*>(view__.data());
+ if(!ptr){
+ return;
+ }
+ buffer_.push(*ptr, view__.size());
+ }
+
+ buffer& get_buffer(){
+ return buffer_;
+ }
+
+ const buffer& get_buffer() const {
+ return buffer_;
+ }
+
+ error push(uint8_t val){
+ return buffer_.push(val);
+ }
+
+ std::size_t get_size() const {
+ return buffer_.read_composite_length();
+ }
+
+ uint8_t& at(std::size_t i){
+ return buffer_.read(i);
+ }
+
+ const uint8_t& at(std::size_t i) const {
+ return buffer_.read(i);
+ }
+};
+}
+
+#include "json.tmpl.h"
+
+namespace saw {
+
+/**
+ * Codec class for json
+ */
+template<typename Schema>
+class codec<Schema, encode::Json> {
+public:
+ struct config {
+ size_t depth = 16;
+ size_t length = 1024;
+ };
+private:
+ config cfg_;
+public:
+ /**
+ * Default constructor
+ */
+ codec(){}
+
+ /**
+ * Constructor
+ */
+ codec(config cfg__):cfg_{std::move(cfg__)}{}
+
+ SAW_FORBID_COPY(codec);
+ SAW_DEFAULT_MOVE(codec);
+
+ template <typename FromEncoding>
+ error_or<void> encode(const data<Schema, FromEncoding>& from_encode, data<Schema, encode::Json>& to_encode){
+ // To Be encoded
+ buffer_view buff_v{to_encode.get_buffer()};
+ auto eov = impl::json_encode<Schema, Schema, FromEncoding>::encode(from_encode, buff_v);
+ if(eov.is_error()){
+ return std::move(eov.get_error());
+ }
+ to_encode.get_buffer().write_advance(buff_v.write_offset());
+
+ return void_t{};
+ }
+
+ template <typename ToEncoding>
+ error_or<void> decode(data<Schema, encode::Json>& from_decode, data<Schema, ToEncoding>& to_decode){
+ buffer_view buff_v{from_decode.get_buffer()};
+
+ auto eov = impl::json_decode<Schema, Schema, ToEncoding>::decode(buff_v, to_decode);
+ if(eov.is_error()){
+ return std::move(eov.get_error());
+ }
+ from_decode.get_buffer().read_advance(buff_v.read_offset());
+
+ return void_t {};
+ }
+};
+}
+
diff --git a/modules/codec-json/json.tmpl.h b/modules/codec-json/json.tmpl.h
new file mode 100644
index 0000000..4b3a1a2
--- /dev/null
+++ b/modules/codec-json/json.tmpl.h
@@ -0,0 +1,734 @@
+#pragma once
+
+#include <charconv>
+#include <sstream>
+
+#include <iostream>
+
+namespace saw {
+namespace impl {
+template<typename Schema, typename RootSchema, typename FromEncode>
+class json_encode {
+ static_assert(always_false<Schema, RootSchema, FromEncode>, "This schema type is not being handled by the json encoding.");
+};
+
+template<typename T, size_t N, typename RootSchema, typename FromEncode>
+struct json_encode<schema::Primitive<T,N>, RootSchema, FromEncode> {
+ static error_or<void> encode(const data<schema::Primitive<T,N>, FromEncode>& from, buffer& to) {
+ auto val = from.get();
+ std::array<uint8_t, 256> data;
+ auto tc_result = std::to_chars(reinterpret_cast<char*>(data.data()), reinterpret_cast<char*>(data.data())+data.size(), val);
+
+ if(tc_result.ec != std::errc{}){
+ return make_error<err::critical>();
+ }
+
+ size_t bytes_written = 0;
+ for(char* ptr = reinterpret_cast<char*>(data.data()); ptr != tc_result.ptr; ++ptr){
+ ++bytes_written;
+ }
+
+ auto& buff = to;
+ error err = buff.write_require_length(bytes_written);
+ if(!err.template is_type<err::no_error>()){
+ return std::move(err);
+ }
+
+ for(char* ptr = reinterpret_cast<char*>(data.data()); ptr != tc_result.ptr; ++ptr){
+ uint8_t* un_ptr = reinterpret_cast<uint8_t*>(ptr);
+ buff.push(un_ptr[0]);
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename RootSchema, typename FromEncode>
+struct json_encode<schema::String, RootSchema, FromEncode> {
+ static error_or<void> encode(const data<schema::String, FromEncode>& from, buffer& to) {
+ {
+ auto err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ for(std::size_t i = 0; i < from.size(); ++i){
+ auto err = to.push(from.get_at(i));
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ {
+ auto err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename... T, typename RootSchema, typename FromEncode>
+struct json_encode<schema::Tuple<T...>, RootSchema, FromEncode> {
+ template<size_t i>
+ static error_or<void> encode_element(const data<schema::Tuple<T...>, FromEncode>& from, buffer& to){
+ auto eov = json_encode<typename parameter_pack_type<i, T...>::type, RootSchema, FromEncode>::encode(from.template get<i>(), to);
+
+ if(eov.is_error()){
+ return eov;
+ }
+
+ if constexpr ( (i+1) < sizeof...(T)){
+ {
+ auto eov_ele = to.push(',');
+ if(!eov_ele.template is_type<err::no_error>()){
+ return eov_ele;
+ }
+ }
+ {
+ auto eov_ele = encode_element<i+1>(from, to);
+ if(eov_ele.is_error()){
+ return eov_ele;
+ }
+ }
+
+
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Tuple<T...>, FromEncode>& from, buffer& to) {
+ {
+ auto err = to.push('[');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ if constexpr ( sizeof...(T) > 0 ){
+ auto eov = encode_element<0>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ auto err = to.push(']');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename T, size_t D, typename RootSchema, typename FromEncode>
+struct json_encode<schema::Array<T,D>, RootSchema, FromEncode> {
+ template<size_t Level>
+ static error_or<void> encode_level(const data<schema::Array<T,D>, FromEncode>& from, buffer& to, std::array<std::size_t, D>& index){
+ if constexpr (Level == D){
+ auto eov = json_encode<T, RootSchema, FromEncode>::encode(from.at(index), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ } else {
+ {
+ auto err = to.push('[');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ for(std::size_t i = 0; i < from.get_dim_size(Level); ++i){
+ if( i > 0 ){
+ auto err = to.push(',');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ {
+ index[Level] = i;
+ auto eov = encode_level<Level+1>(from, to, index);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+ {
+ auto err = to.push(']');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Array<T,D>, FromEncode>& from, buffer& to) {
+ std::array<std::size_t, D> index;
+ return encode_level<0>(from, to, index);
+ }
+};
+
+template<typename... T, string_literal... Key, typename RootSchema, typename FromEncode>
+struct json_encode<schema::Struct<schema::Member<T,Key>...>, RootSchema, FromEncode> {
+
+ template<size_t i>
+ static error_or<void> encode_element(const data<schema::Struct<schema::Member<T,Key>...>, FromEncode>& from, buffer& to){
+ // Encode the name
+ {
+ std::string_view view = parameter_key_pack_type<i, Key...>::literal.view();
+ error err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ err = to.push(*reinterpret_cast<const uint8_t *>(view.data()), view.size());
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ // Add the separator
+ {
+ auto eov_ele = to.push(':');
+ if(!eov_ele.template is_type<err::no_error>()){
+ return eov_ele;
+ }
+ }
+
+ // Encode the value
+ auto eov = json_encode<typename parameter_pack_type<i, T...>::type, RootSchema, FromEncode>::encode(from.template get<parameter_key_pack_type<i, Key...>::literal>(), to);
+
+ // Go to the next element
+ if constexpr ( (i+1) < sizeof...(T)){
+ {
+ auto eov_ele = to.push(',');
+ if(!eov_ele.template is_type<err::no_error>()){
+ return eov_ele;
+ }
+ }
+ {
+ auto eov_ele = encode_element<i+1>(from, to);
+ if(eov_ele.is_error()){
+ return eov_ele;
+ }
+ }
+
+
+ }
+
+ return void_t{};
+ }
+ static error_or<void> encode(const data<schema::Struct<schema::Member<T,Key>...>, FromEncode>& from, buffer& to) {
+ {
+ auto err = to.push('{');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ if constexpr ( sizeof...(T) > 0 ){
+ auto eov = encode_element<0>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ auto err = to.push('}');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename... T, string_literal... Key, typename RootSchema, typename FromEncode>
+struct json_encode<schema::Union<schema::Member<T,Key>...>, RootSchema, FromEncode> {
+
+ template<size_t i>
+ static error_or<void> encode_element(const data<schema::Union<schema::Member<T,Key>...>, FromEncode>& from, buffer& to){
+ if(from.template holds_alternative<typename parameter_key_pack_type<i, Key...>::literal>()){
+ // Encode the name
+ {
+ std::string_view view = parameter_key_pack_type<i, Key...>::literal.view();
+ error err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ err = to.push(*reinterpret_cast<const uint8_t *>(view.data()), view.size());
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ err = to.push('"');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ // Add the separator
+ {
+ auto eov_ele = to.push(':');
+ if(!eov_ele.template is_type<err::no_error>()){
+ return eov_ele;
+ }
+ }
+
+ // Encode the value
+ auto eov = json_encode<typename parameter_pack_type<i, T...>::type, RootSchema, FromEncode>::encode(from.template get<parameter_key_pack_type<i, Key...>::literal>(), to);
+ }
+ // Go to the next element
+ if constexpr ( (i+1) < sizeof...(T)){
+ {
+ auto eov_ele = encode_element<i+1>(from, to);
+ if(eov_ele.is_error()){
+ return eov_ele;
+ }
+ }
+
+
+ }
+
+ return void_t{};
+ }
+ static error_or<void> encode(const data<schema::Union<schema::Member<T,Key>...>, FromEncode>& from, buffer& to) {
+ {
+ auto err = to.push('{');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+ if constexpr ( sizeof...(T) > 0 ){
+ auto eov = encode_element<0>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ auto err = to.push('}');
+ if(!err.template is_type<err::no_error>()){
+ return err;
+ }
+ }
+
+ return void_t{};
+ }
+};
+
+struct json_helper {
+ static bool is_whitespace(int8_t ch) {
+ return ch == '\t' || ch == ' ' || ch == '\r' || ch == '\n';
+ }
+
+ static void skip_whitespace(buffer_view& buff) {
+ while(buff.read_composite_length() > 0 && json_helper::is_whitespace(buff.read())) {
+ buff.read_advance(1);
+ }
+ }
+};
+
+template<typename Schema, typename RootSchema, typename ToDecode>
+struct json_decode;
+
+template<typename T, size_t N, typename RootSchema, typename ToDecode>
+struct json_decode<schema::Primitive<T,N>, RootSchema, ToDecode> {
+ static error_or<void> decode(buffer_view& buff, data<schema::Primitive<T,N>, ToDecode>& to) {
+ assert(
+ ( buff.read() >= '0' && buff.read() <= '9')
+ || ( buff.read() == '+' || buff.read() == '-')
+ );
+
+ std::size_t offset = 0;
+
+ if (buff.read() == '-'){
+ ++offset;
+ } else if (buff.read() == '+'){
+ return make_error<err::not_supported>();
+ }
+ if (offset >= buff.read_composite_length()) {
+ return make_error<err::buffer_exhausted>();
+ }
+ if (buff.read(offset) >= '1' && buff.read(offset) <= '9') {
+ ++offset;
+
+ if(offset >= buff.read_composite_length()) {
+ return make_error<err::buffer_exhausted>();
+ }
+
+ while(1){
+ if (buff.read(offset) >= '0' && buff.read(offset) <= '9') {
+ ++offset;
+
+ if(offset >= buff.read_composite_length()) {
+ break;
+ }
+ continue;
+ }
+ break;
+ }
+ } else if (buff.read(offset) == '0' ) {
+ ++offset;
+ } else {
+ return make_error<err::buffer_exhausted>();
+ }
+
+ {
+ std::string_view num_view{reinterpret_cast<char*>(&buff.read()), offset};
+ typename native_data_type<schema::Primitive<T,N>>::type result;
+ auto fc_result = std::from_chars(num_view.data(), num_view.data() + num_view.size(), result);
+ if(fc_result.ec != std::errc{}){
+ return make_error<err::invalid_state>();
+ }
+
+ to.set(result);
+ }
+ buff.read_advance(offset);
+
+ return void_t{};
+ }
+};
+
+template<typename RootSchema, typename ToDecode>
+struct json_decode<schema::String, RootSchema, ToDecode> {
+ static error_or<void> decode(buffer_view& buff, data<schema::String, ToDecode>& to){
+ assert(buff.read() == '"');
+ buff.read_advance(1);
+
+ std::stringstream iss;
+ bool string_done = false;
+ while(!string_done){
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ switch(buff.read()){
+ case '\\':{
+ buff.read_advance(1);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ switch(buff.read()){
+ case '\\':
+ case '/':
+ case '"':
+ iss<< buff.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': {
+ buff.read_advance(1);
+ if(buff.read_composite_length() < 4){
+ return make_error<err::buffer_exhausted>();
+ }
+ iss<<'?';
+ iss<<'?';
+ iss<<'?';
+ iss<<'?';
+
+ buff.read_advance(3);
+ } break;
+ }
+ } break;
+ case '"':
+ string_done = true;
+ break;
+ default:{
+ // Avoids Control sequences
+ if(buff.read() >= ' ' && buff.read() <= '~'){
+ iss<<buff.read();
+ }
+ // Avoid Unicode
+ else if (buff.read() < 0) {
+ do {
+ buff.read_advance(1);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ } while( buff.read() < 0 );
+ iss<<'?';
+ }
+ break;
+ }
+ }
+ buff.read_advance(1);
+ }
+
+ std::string raw = iss.str();
+ to.set(std::move(raw));
+
+ return void_t{};
+ }
+};
+
+template<typename... T, string_literal... Lits, typename RootSchema, typename ToDecode>
+struct json_decode<schema::Struct<schema::Member<T,Lits>...>, RootSchema, ToDecode> {
+ template<std::size_t i>
+ static error_or<void> decode_field_search(buffer_view& buff, data<schema::Struct<schema::Member<T,Lits>...>, ToDecode>& to, std::array<bool, sizeof...(T)>& fields, const data<schema::String,ToDecode>& search_name){
+ if constexpr ( i < sizeof...(T)){
+ using Type = typename parameter_pack_type<i, T...>::type;
+ constexpr static string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ if(search_name == Literal.view()){
+ if(fields[i]){
+ return make_error<err::invalid_state>();
+ }
+ fields[i] = true;
+ auto eov = json_decode<Type, RootSchema, ToDecode>::decode(buff, to.template get<Literal>());
+ if(eov.is_error()){
+ return eov;
+ }
+ }else {
+ decode_field_search<i+1>(buff, to, fields, search_name);
+ }
+ }else {
+ return make_error<err::invalid_state>();
+ }
+ return void_t{};
+ }
+
+ static error_or<void> decode_fields(buffer_view& buff, data<schema::Struct<schema::Member<T,Lits>...>, ToDecode>& to, std::array<bool, sizeof...(T)>& fields){
+ for(;;){
+ data<schema::String, ToDecode> name;
+ auto eov = json_decode<schema::String, RootSchema, ToDecode>::decode(buff, name);
+ if(eov.is_error()){
+ return eov;
+ }
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ if(buff.read() != ':'){
+ return make_error<err::invalid_state>();
+ }
+ buff.read_advance(1);
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ {
+ auto eov = decode_field_search<0>(buff, to, fields, name);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ if(buff.read() == ','){
+ buff.read_advance(1);
+ }else if(buff.read() == '}'){
+ // If not all fields are set, the dataset is incomplete
+ for(auto& iter : fields){
+ if(!iter){
+ return make_error<err::invalid_state>();
+ }
+ }
+ buff.read_advance(1);
+ return void_t{};
+ }else{
+ return make_error<err::invalid_state>();
+ }
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> decode(buffer_view& buff, data<schema::Struct<schema::Member<T,Lits>...>, ToDecode>& to){
+ std::array<bool, sizeof...(T)> found_fields;
+ std::fill(found_fields.begin(), found_fields.end(), false);
+
+ assert(buff.read() == '{');
+ buff.read_advance(1);
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ // Check if there are no elements present in the JSON Struct
+ if(buff.read() == '}'){
+ if(sizeof...(T) > 0){
+ return make_error<err::invalid_state>();
+ }
+ buff.read_advance(1);
+ return void_t{};
+ }
+
+ auto eov = decode_fields(buff, to, found_fields);
+ if(eov.is_error()){
+ return eov;
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename... T, typename RootSchema, typename ToDecode>
+struct json_decode<schema::Tuple<T...>, RootSchema, ToDecode> {
+ template<std::size_t i>
+ static error_or<void> decode_element(buffer_view& buff, data<schema::Tuple<T...>, ToDecode>& to){
+ if constexpr (i < sizeof...(T)){
+ if constexpr ( i > 0 ){
+ if(buff.read() != ','){
+ return make_error<err::invalid_state>();
+ }
+ buff.read_advance(1);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+ }
+ using Type = typename parameter_pack_type<i, T...>::type;
+
+ auto eov = json_decode<Type, RootSchema, ToDecode>::decode(buff, to.template get<i>());
+ if(eov.is_error()){
+ return eov;
+ }
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ eov = decode_element<i+1>(buff, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }else{
+ if(buff.read() != ']'){
+ return make_error<err::invalid_state>();
+ }
+ buff.read_advance(1);
+ }
+ return void_t{};
+ }
+
+ static error_or<void> decode(buffer_view& buff, data<schema::Tuple<T...>, ToDecode>& to){
+ assert(buff.read() == '[');
+ buff.read_advance(1);
+
+ json_helper::skip_whitespace(buff);
+ if(buff.read_composite_length() == 0){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ auto eov = decode_element<0>(buff, to);
+ if(eov.is_error()){
+ return eov;
+ }
+
+ return void_t{};
+ }
+};
+
+// The whole std::vector approach is hacky af. ToDo change it maybe?
+template<typename T, size_t D, typename RootSchema, typename ToDecode>
+struct json_decode<schema::Array<T,D>, RootSchema, ToDecode> {
+ template<size_t Level>
+ static error_or<void> decode_flat_level(buffer_view& buff, std::vector<data<T, encode::Native>>& to, std::array<std::size_t, D>& index, std::array<std::size_t, D>& dims, bool log_dim){
+ if constexpr (Level == D) {
+ json_helper::skip_whitespace(buff);
+ try {
+ to.push_back({});
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+ auto eov = json_decode<T, RootSchema, ToDecode>::decode(buff, to.back());
+ if(eov.is_error()){
+ return eov;
+ }
+ } else {
+ assert(buff.read() == '[');
+ buff.read_advance(1);
+
+ json_helper::skip_whitespace(buff);
+ if ( buff.read_composite_length() == 0 ){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ index[Level] = 0;
+ for(;;){
+ // We should have an element right now
+ auto eov = decode_flat_level<Level+1>(buff,to,index,dims, index[Level] == 0 && log_dim);
+ if(eov.is_error()){
+ return eov;
+ }
+ json_helper::skip_whitespace(buff);
+ if ( buff.read_composite_length() == 0 ){
+ return make_error<err::buffer_exhausted>();
+ }
+
+ ++index[Level];
+ if(buff.read() == ','){
+ buff.read_advance(1);
+ } else if(buff.read() == ']'){
+ buff.read_advance(1);
+ break;
+ } else {
+ return make_error<err::invalid_state>();
+ }
+ json_helper::skip_whitespace(buff);
+ if ( buff.read_composite_length() == 0 ){
+ return make_error<err::buffer_exhausted>();
+ }
+ }
+ if(log_dim){
+ dims[Level] = index[Level];
+ }else if (dims[Level] != index[Level]){
+ return make_error<err::invalid_state>();
+ }
+ }
+ return void_t{};
+ }
+
+ template<std::size_t Level>
+ static error_or<void> decode_unflat_level(std::vector<data<T,encode::Native>>& flat, data<schema::Array<T,D>, ToDecode>& to, std::array<std::size_t, D>& index, std::size_t& flat_index) {
+ if constexpr ( Level == D ){
+ auto& flat_data = flat.at(flat_index);
+ to.at(index) = std::move(flat_data);
+ ++flat_index;
+ }else {
+ const std::size_t dim_size = to.get_dim_size(Level);
+ for(index[Level] = 0; index[Level] < dim_size; ++index[Level]){
+
+ auto eov = decode_unflat_level<Level+1>(flat, to, index, flat_index);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> decode(buffer_view& buff, data<schema::Array<T,D>, ToDecode>& to){
+ std::array<std::size_t, D> index;
+ std::array<std::size_t, D> dims;
+ std::fill(dims.begin(), dims.end(), 0);
+ std::vector<data<T,encode::Native>> flat_array;
+ auto eov = decode_flat_level<0>(buff, flat_array, index, dims, true);
+ if(eov.is_error()){
+ return eov;
+ }
+
+ to = {dims};
+ std::size_t flat_index = 0;
+
+ return decode_unflat_level<0>(flat_array, to, index, flat_index);
+ }
+};
+}
+}
diff --git a/modules/codec-minecraft/.nix/derivation.nix b/modules/codec-minecraft/.nix/derivation.nix
new file mode 100644
index 0000000..7704d13
--- /dev/null
+++ b/modules/codec-minecraft/.nix/derivation.nix
@@ -0,0 +1,32 @@
+{ lib
+, stdenvNoCC
+, scons
+, clang
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenvNoCC.mkDerivation {
+ pname = "forstio-codec-minecraft";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.codec
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/codec-minecraft/SConscript b/modules/codec-minecraft/SConscript
new file mode 100644
index 0000000..4d1deab
--- /dev/null
+++ b/modules/codec-minecraft/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+codec_minecraft_env = env.Clone();
+
+codec_minecraft_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+codec_minecraft_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += codec_minecraft_env.sources;
+env.headers += codec_minecraft_env.headers;
+
+## Shared lib
+objects_shared = []
+codec_minecraft_env.add_source_files(objects_shared, codec_minecraft_env.sources, shared=True);
+codec_minecraft_env.library_shared = codec_minecraft_env.SharedLibrary('#build/forstio-codec-minecraft', [objects_shared]);
+
+## Static lib
+objects_static = []
+codec_minecraft_env.add_source_files(objects_static, codec_minecraft_env.sources, shared=False);
+codec_minecraft_env.library_static = codec_minecraft_env.StaticLibrary('#build/forstio-codec-minecraft', [objects_static]);
+
+# Set Alias
+env.Alias('library_codec_minecraft', [codec_minecraft_env.library_shared, codec_minecraft_env.library_static]);
+
+env.targets += ['library_codec_minecraft'];
+
+# Install
+env.Install('$prefix/lib/', [codec_minecraft_env.library_shared, codec_minecraft_env.library_static]);
+env.Install('$prefix/include/forstio/codec/minecraft/', [codec_minecraft_env.headers]);
diff --git a/modules/codec-minecraft/SConstruct b/modules/codec-minecraft/SConstruct
new file mode 100644
index 0000000..edd5f57
--- /dev/null
+++ b/modules/codec-minecraft/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-codec'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/codec-minecraft/minecraft.h b/modules/codec-minecraft/minecraft.h
new file mode 100644
index 0000000..670b019
--- /dev/null
+++ b/modules/codec-minecraft/minecraft.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <forstio/core/error.h>
+#include <forstio/codec/data.h>
+#include <forstio/codec/stream_value.h>
+
+namespace saw {
+namespace encode {
+struct Minecraft {};
+struct VarIntTransport {};
+}
+
+template<typename T>
+class data<T, encode::Minecraft> {
+private:
+ ring_buffer buffer_;
+public:
+ data() = default;
+ data(std::size_t size):
+ buffer_{size}
+ {}
+
+ buffer& get_buffer(){
+ return buffer_;
+ }
+};
+
+namespace mc {
+namespace impl {
+union minecraft_signed_four_conversion {
+ int32_t s;
+ uint32_t u;
+};
+
+union minecraft_signed_eight_conversion {
+ int64_t s;
+ uint64_t u;
+};
+
+template<typename Schema, typename FromEnc>
+class minecraft_encode {
+ static_assert(always_false<Schema, FromEnc>, "This schema type is not being handled by the Minecraft encoding.");
+};
+
+template<typename FromEnc>
+class minecraft_encode<schema::VarInt, FromEnc>{
+ static error_or<void> encode(const data<schema::VarInt, FromEnc>& from, buffer& to){
+ uint8_t encode_index = 0;
+ minecraft_signed_four_conversion value;
+ value.s = from.get();
+
+ /**
+ * VarInt max length is 5 bytes
+ */
+ std::array<uint8_t, 5> encode_data;
+
+ do {
+ uint8_t step = static_cast<uint8_t>(value.u & 0x7F);
+ value.u = value.u >> 7;
+ if(value.u != 0){
+ step |= 0x80;
+ }
+ encode_data[encode_index] = step;
+ ++encode_index;
+ }while(value.u != 0);
+
+ auto err = buffer.push(encode_data[0], encode_index);
+ if (!err.template is_type<err::no_error>()) {
+ return err;
+ }
+
+ return no_error();
+ }
+};
+
+template<typename Schema, typename FromEnc>
+class minecraft_decode {
+ static_assert(always_false<Schema, FromEnc>, "This schema type is not being handled by the Minecraft encoding.");
+};
+
+template<typename FromEnc>
+class minecraft_decode<schema::VarInt, FromEnc>{
+ static error_or<void> decode(buffer& from, data<schema::VarInt, FromEnc>& to){
+ uint8_t num_reads = 0;
+
+ minecraft_signed_four_conversion value;
+ value.u = 0;
+
+ uint8_t read{};
+ do {
+ auto err = from.pop(read);
+ if( !err.template is_type<err::no_error>() ){
+ return err;
+ }
+ value.u |= ((read & 0x7F) << (7*num_reads));
+ ++num_reads;
+ if(num_reads > 5){
+ return make_error<err::>();
+ }
+ } while( (read & 0x80) != 0);
+
+ to.set(value.s);
+
+ return no_error();
+ }
+};
+
+}
+}
+
+
+
+namespace mc {
+
+}
+}
diff --git a/modules/codec-minecraft/var_int_data.cpp b/modules/codec-minecraft/var_int_data.cpp
new file mode 100644
index 0000000..59e4317
--- /dev/null
+++ b/modules/codec-minecraft/var_int_data.cpp
@@ -0,0 +1,36 @@
+#include "var_int_data.h"
+
+namespace saw {
+data<schema::VarInt,encode::Native>::data():
+ data_{}
+{}
+
+data<schema::VarInt,encode::Native>::data(int32_t data):
+ data_{data}
+{}
+
+int32_t data<schema::VarInt, encode::Native>::get() const {
+ return data_;
+}
+
+void data<schema::VarInt, encode::Native>::set(int32_t data) {
+ data_ = data;
+}
+
+data<schema::VarLong,encode::Native>::data():
+ data_{}
+{}
+
+data<schema::VarLong,encode::Native>::data(int64_t data):
+ data_{data}
+{}
+
+int64_t data<schema::VarLong, encode::Native>::get() const {
+ return data_;
+}
+
+void data<schema::VarLong, encode::Native>::set(int64_t data) {
+ data_ = data;
+}
+
+}
diff --git a/modules/codec-minecraft/var_int_data.h b/modules/codec-minecraft/var_int_data.h
new file mode 100644
index 0000000..807c930
--- /dev/null
+++ b/modules/codec-minecraft/var_int_data.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <forstio/core/data.h>
+
+namespace saw {
+template<>
+class data<schema::VarInt, encode::Native> {
+private:
+ int32_t data_;
+public:
+ data();
+ data(int32_t data);
+
+ int32_t get() const;
+ void set(int32_t);
+};
+
+template<>
+class data<schema::VarLong, encode::Native> {
+private:
+ int64_t data_;
+public:
+ data();
+ data(int64_t data);
+
+ int64_t get() const;
+ void set(int64_t);
+};
+
+
+}
diff --git a/modules/codec-netcdf/.nix/derivation.nix b/modules/codec-netcdf/.nix/derivation.nix
new file mode 100644
index 0000000..770942e
--- /dev/null
+++ b/modules/codec-netcdf/.nix/derivation.nix
@@ -0,0 +1,33 @@
+{ lib
+, stdenv
+, scons
+, clang
+, clang-tools
+, version
+, forstio
+, netcdf
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-codec-netcdf";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.codec
+ netcdf
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/codec-netcdf/SConscript b/modules/codec-netcdf/SConscript
new file mode 100644
index 0000000..a469f77
--- /dev/null
+++ b/modules/codec-netcdf/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+codec_netcdf_env = env.Clone();
+
+codec_netcdf_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+codec_netcdf_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += codec_netcdf_env.sources;
+env.headers += codec_netcdf_env.headers;
+
+## Shared lib
+objects_shared = []
+codec_netcdf_env.add_source_files(objects_shared, codec_netcdf_env.sources, shared=True);
+codec_netcdf_env.library_shared = codec_netcdf_env.SharedLibrary('#build/forstio-codec-netcdf', [objects_shared]);
+
+## Static lib
+objects_static = []
+codec_netcdf_env.add_source_files(objects_static, codec_netcdf_env.sources, shared=False);
+codec_netcdf_env.library_static = codec_netcdf_env.StaticLibrary('#build/forstio-codec-netcdf', [objects_static]);
+
+# Set Alias
+env.Alias('library_codec_netcdf', [codec_netcdf_env.library_shared, codec_netcdf_env.library_static]);
+
+env.targets += ['library_codec_netcdf'];
+
+# Install
+env.Install('$prefix/lib/', [codec_netcdf_env.library_shared, codec_netcdf_env.library_static]);
+env.Install('$prefix/include/forstio/codec/netcdf/', [codec_netcdf_env.headers]);
diff --git a/modules/codec-netcdf/SConstruct b/modules/codec-netcdf/SConstruct
new file mode 100644
index 0000000..edd5f57
--- /dev/null
+++ b/modules/codec-netcdf/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-codec'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/codec-netcdf/netcdf.h b/modules/codec-netcdf/netcdf.h
new file mode 100644
index 0000000..cc9aedb
--- /dev/null
+++ b/modules/codec-netcdf/netcdf.h
@@ -0,0 +1,151 @@
+#pragma once
+
+#include <forstio/core/string_literal.h>
+#include <forstio/core/error.h>
+#include <forstio/codec/data.h>
+
+#include <netcdf.h>
+#include <netcdf_mem.h>
+
+namespace saw {
+namespace encode {
+/**
+ * Template type hint
+ */
+struct Netcdf {};
+}
+
+/**
+ * Class representing the files system netcdf file
+ */
+template<typename Schema>
+class data<Schema, encode::Netcdf> {
+private:
+ std::vector<uint8_t> buff_;
+public:
+ data() = default;
+
+ data(std::vector<uint8_t> buff):
+ buff_{std::move(buff)}
+ {}
+
+ template<size_t N>
+ data(const std::array<uint8_t, N>& arr):
+ buff_{arr.begin(), arr.end()}
+ {}
+
+ std::vector<uint8_t>& get_data() {
+ return buff_;
+ }
+
+ const std::vector<uint8_t>& get_data() const {
+ return buff_;
+ }
+};
+
+template<typename Schema>
+class codec<Schema, encode::Netcdf>{
+ static_assert(always_false<Schema,encode::Netcdf>, "NetCDF only supports Structs as a root object");
+};
+
+}
+#include "netcdf.tmpl.h"
+namespace saw {
+
+template<typename... Vals, string_literal... Keys>
+class codec<schema::Struct<schema::Member<Vals,Keys>...>, encode::Netcdf> {
+private:
+ using Schema = schema::Struct<schema::Member<Vals,Keys>...>;
+public:
+ SAW_FORBID_COPY(codec);
+ SAW_DEFAULT_MOVE(codec);
+
+ /**
+ * Default constructor
+ */
+ codec() = default;
+
+ /**
+ * Encoder function
+ */
+ template<typename FromEncoding>
+ error_or<void> encode(const data<Schema, FromEncoding>& from, data<Schema,encode::Netcdf>& to) {
+ int rc{};
+ int ncid{};
+
+ rc = nc_create_mem("forstio_internal_write_memory", 0,/*NC_NETCDF4, */0, &ncid);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+
+ {
+ auto eov = impl::netcdf_encode<Schema,FromEncoding>::encode_meta(from, ncid);
+ if(eov.is_error()){
+ nc_close(ncid);
+ return eov;
+ }
+ }
+
+ rc = nc_enddef(ncid);
+ if(rc != NC_NOERR){
+ nc_close(ncid);
+ return make_error<err::critical>();
+ }
+
+ {
+ auto eov = impl::netcdf_encode<Schema,FromEncoding>::encode(from, ncid);
+ if(eov.is_error()){
+ nc_close(ncid);
+ return eov;
+ }
+ }
+
+ NC_memio nc_memio;
+ rc = nc_close_memio(ncid, &nc_memio);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+
+ if(!nc_memio.memory || nc_memio.size == 0){
+ return make_error<err::critical>();
+ }
+
+ try {
+ to.get_data().resize(nc_memio.size);
+ }catch(const std::exception& e){
+ (void) e;
+ return make_error<err::out_of_memory>();
+ }
+
+ for(std::size_t i = 0; i < nc_memio.size; ++i){
+ to.get_data().at(i) = static_cast<uint8_t*>(nc_memio.memory)[i];
+ }
+
+ free(nc_memio.memory);
+ nc_memio.memory = nullptr;
+
+ return void_t{};
+ }
+
+ /**
+ * Decoder function
+ */
+ template<typename ToEncoding>
+ error_or<void> decode(data<Schema, encode::Netcdf>& from_decode, data<Schema,ToEncoding>& to_decode) {
+ int ncid{};
+ int rc{};
+
+ rc = nc_open_mem("forstio_internal_read_memory", NC_NOWRITE, from_decode.get_data().size(), &from_decode.get_data()[0], &ncid);
+ if(rc != NC_NOERR){
+ // Don't know how to get the error, so fail critically.
+ return make_error<err::critical>();
+ }
+
+ auto eov = impl::netcdf_decode<Schema, ToEncoding>::decode(to_decode, ncid);
+
+ nc_close(ncid);
+
+ return eov;
+ }
+};
+}
diff --git a/modules/codec-netcdf/netcdf.tmpl.h b/modules/codec-netcdf/netcdf.tmpl.h
new file mode 100644
index 0000000..bf257e4
--- /dev/null
+++ b/modules/codec-netcdf/netcdf.tmpl.h
@@ -0,0 +1,348 @@
+#pragma once
+
+namespace saw {
+namespace impl {
+template<typename Schema>
+struct netcdf_primitive_id {
+ static_assert(always_false<Schema>, "Not a primitive id");
+};
+
+template<>
+struct netcdf_primitive_id<schema::Int32> {
+ static constexpr nc_type value = NC_INT;
+};
+
+template<>
+struct netcdf_primitive_id<schema::UInt32> {
+ static constexpr nc_type value = NC_UINT;
+};
+
+template<>
+struct netcdf_primitive_id<schema::Int64> {
+ static constexpr nc_type value = NC_INT64;
+};
+
+template<>
+struct netcdf_primitive_id<schema::UInt64> {
+ static constexpr nc_type value = NC_UINT64;
+};
+
+template<>
+struct netcdf_primitive_id<schema::Float32> {
+ static constexpr nc_type value = NC_FLOAT;
+};
+
+template<>
+struct netcdf_primitive_id<schema::Float64> {
+ static constexpr nc_type value = NC_DOUBLE;
+};
+
+template<typename Schema>
+struct netcdf_is_group {
+ static constexpr bool value = false;
+};
+
+template<typename... T, string_literal... Lits>
+struct netcdf_is_group<schema::Struct<schema::Member<T,Lits>...>> {
+ static constexpr bool value = true;
+};
+
+template<typename Schema, typename ToEncode>
+struct netcdf_encode;
+
+template<typename T, size_t N, typename ToEncode>
+struct netcdf_encode<schema::Primitive<T,N>, ToEncode> {
+ using Schema = schema::Primitive<T,N>;
+
+ static error_or<void> encode(const data<Schema, ToEncode>& from, int ncid, int ncvarid){
+ int rc{};
+
+ typename native_data_type<Schema>::type val = from.get();
+
+ rc = nc_put_var(ncid, ncvarid, &val);
+ if(rc != NC_NOERR) {
+ return make_error<err::critical>();
+ }
+
+ return void_t{};
+ }
+
+ static error_or<void> encode_meta(const data<Schema, ToEncode>& from, int ncid, int ncvarid){
+ (void) from;
+ (void) ncid;
+ (void) ncvarid;
+ return void_t{};
+ }
+};
+
+template<typename T, size_t Dim, typename ToEncode>
+struct netcdf_encode<schema::Array<T,Dim>, ToEncode> {
+ using Schema = schema::Array<T,Dim>;
+
+ template<size_t Level>
+ static error_or<void> encode_level(data<Schema,ToEncode>& from, int ncid, int ncvarid){
+
+ return make_error<err::not_implemented>();
+ }
+
+ static error_or<void> encode(data<Schema, ToEncode>& from, int ncid, int ncvarid){
+
+ return make_error<err::not_implemented>();
+ }
+
+ static error_or<void> encode_meta(const data<Schema, ToEncode>& from, int ncid, int ncvarid){
+
+ return make_error<err::not_implemented>();
+ }
+};
+
+template<typename... T, string_literal... Lits, typename ToEncode>
+struct netcdf_encode<schema::Struct<schema::Member<T,Lits>...>, ToEncode> {
+ using Schema = schema::Struct<schema::Member<T,Lits>...>;
+
+ template<std::size_t i>
+ static error_or<void> encode_member(const data<Schema, ToEncode>& from, int ncid){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ {
+ /**
+ * We have to ask for the internal id of the netcdf structure
+ */
+ if constexpr (netcdf_is_group<Type>::value){
+ return make_error<err::not_implemented>();
+ }else{
+ int nc_varid{};
+ int rc {};
+ rc = nc_inq_varid(ncid, Literal.data.data(), &nc_varid);
+ if(rc != NC_NOERR) {
+ return make_error<err::critical>();
+ }
+ auto eov = netcdf_encode<Type, ToEncode>::encode(from.template get<Literal>(), ncid, nc_varid);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = encode_member<i+1>(from, ncid);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<Schema, ToEncode>& from, int ncid){
+ if constexpr (sizeof...(T) > 0){
+ auto eov = encode_member<0>(from, ncid);
+ return eov;
+ }
+
+ return void_t{};
+ }
+
+ template<std::size_t i>
+ static error_or<void> encode_meta_member(const data<Schema, ToEncode>& from, int ncid){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ {
+ /**
+ * We have to ask for the internal id of the netcdf structure
+ */
+ if constexpr (netcdf_is_group<Type>::value){
+ return make_error<err::not_implemented>();
+ }else{
+ int nc_varid{};
+ int rc {};
+ rc = nc_def_var(ncid, Literal.data.data(), netcdf_primitive_id<Type>::value, 0, nullptr, &nc_varid);
+ if(rc != NC_NOERR) {
+ return make_error<err::critical>();
+ }
+ auto eov = netcdf_encode<Type, ToEncode>::encode_meta(from.template get<Literal>(), ncid, nc_varid);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = encode_meta_member<i+1>(from, ncid);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ return void_t{};
+ }
+
+ static error_or<void> encode_meta(const data<Schema, ToEncode>& from, int ncid){
+ if constexpr (sizeof...(T) > 0){
+ auto eov = encode_meta_member<0>(from, ncid);
+ return eov;
+ }
+
+ return void_t{};
+ }
+};
+
+template<typename Schema, typename ToDecode>
+struct netcdf_decode;
+
+template<typename T, size_t N, typename ToDecode>
+struct netcdf_decode<schema::Primitive<T,N>, ToDecode> {
+ using Schema = schema::Primitive<T,N>;
+
+ static error_or<void> decode(data<Schema, ToDecode>& to, int from, int nc_varid){
+ int rc{};
+
+ nc_type nc_vartype{};
+ int nc_dimnum{};
+ std::array<int,NC_MAX_VAR_DIMS> nc_dimids;
+
+ rc = nc_inq_var(from, nc_varid, nullptr, &nc_vartype, &nc_dimnum, &nc_dimids[0], nullptr);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+ if(nc_vartype != netcdf_primitive_id<schema::Primitive<T,N>>::value){
+ return make_error<err::critical>();
+ }
+ if(nc_dimnum != 0){
+ return make_error<err::critical>();
+ }
+
+ typename native_data_type<schema::Primitive<T,N>>::type val{};
+ rc = nc_get_var(from, nc_varid, &val);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+
+ to.set(val);
+
+ return void_t {};
+ }
+};
+
+template<typename T, size_t Dim, typename ToDecode>
+struct netcdf_decode<schema::Array<T,Dim>, ToDecode> {
+ using Schema = schema::Array<T,Dim>;
+
+ template<std::size_t Level>
+ static error_or<void> decode_level(data<Schema, ToDecode>& to, int from, int nc_varid, std::array<std::size_t, Dim>& index, const std::array<size_t,Dim>& count){
+ if constexpr ( Level == Dim ){
+ int rc{};
+ typename native_data_type<T>::type val;
+ rc = nc_get_vara(from, nc_varid, index.data(), count.data(), &val);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+ to.at(index).set(val);
+ }else{
+ const std::size_t dim_size = to.get_dim_size(Level);
+ for(index[Level] = 0; index[Level] < dim_size; ++index[Level]){
+ auto eov = decode_level<Level+1>(to, from, nc_varid, index, count);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+
+ return void_t{};
+ }
+
+ static error_or<void> decode(data<Schema, ToDecode>& to, int from, int nc_varid){
+ int rc{};
+
+ nc_type nc_vartype{};
+
+ int nc_dimnum{};
+ std::array<int, NC_MAX_VAR_DIMS> nc_dimids;
+
+ rc = nc_inq_var(from, nc_varid, nullptr, &nc_vartype, &nc_dimnum, &nc_dimids[0], nullptr);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+ if(nc_vartype != netcdf_primitive_id<T>::value){
+ return make_error<err::critical>();
+ }
+ if(nc_dimnum != Dim){
+ return make_error<err::critical>();
+ }
+
+ std::array<std::size_t, Dim> dims;
+ std::array<size_t, Dim> count;
+ for(std::size_t i = 0; i < Dim; ++i){
+ rc = nc_inq_dim(from, nc_dimids[i], nullptr, &dims[i]);
+ if(rc != NC_NOERR){
+ return make_error<err::critical>();
+ }
+ count[i] = 1;
+ }
+
+ to = {dims};
+ std::array<std::size_t, Dim> index;
+
+ return decode_level<0>(to, from, nc_varid, index, count);
+ }
+};
+
+template<typename... T, string_literal... Lits, typename ToDecode>
+struct netcdf_decode<schema::Struct<schema::Member<T,Lits>...>, ToDecode> {
+ using Schema = schema::Struct<schema::Member<T,Lits>...>;
+
+ template<std::size_t i>
+ static error_or<void> decode_member(data<Schema,ToDecode>& to, int from){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ {
+ /**
+ * We have to ask for the internal id of the netcdf structure
+ */
+ if constexpr (netcdf_is_group<Type>::value){
+ int nc_group_id{};
+ int rc {};
+ rc = nc_inq_ncid(from, Literal.data.data(), &nc_group_id);
+ if(rc != NC_NOERR) {
+ return make_error<err::critical>();
+ }
+ auto eov = netcdf_decode<Type, ToDecode>::decode(to.template get<Literal>(), nc_group_id);
+ if(eov.is_error()){
+ return eov;
+ }
+ }else{
+ int nc_varid{};
+ int rc {};
+ rc = nc_inq_varid(from, Literal.data.data(), &nc_varid);
+ if(rc != NC_NOERR) {
+ return make_error<err::critical>();
+ }
+ auto eov = netcdf_decode<Type, ToDecode>::decode(to.template get<Literal>(), from, nc_varid);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = decode_member<i+1>(to, from);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ return void_t{};
+ }
+
+
+ static error_or<void> decode(data<Schema, ToDecode>& to, int from){
+ if constexpr (sizeof...(T) > 0){
+ auto eov = decode_member<0>(to, from);
+ return eov;
+ }
+
+ return void_t{};
+ }
+};
+}
+}
diff --git a/modules/codec/.nix/derivation.nix b/modules/codec/.nix/derivation.nix
new file mode 100644
index 0000000..bcf2be7
--- /dev/null
+++ b/modules/codec/.nix/derivation.nix
@@ -0,0 +1,28 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-codec";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ buildInputs = [
+ forstio.core
+ ];
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/codec/SConscript b/modules/codec/SConscript
new file mode 100644
index 0000000..c038d42
--- /dev/null
+++ b/modules/codec/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+codec_env = env.Clone();
+
+codec_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+codec_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += codec_env.sources;
+env.headers += codec_env.headers;
+
+## Shared lib
+objects_shared = []
+codec_env.add_source_files(objects_shared, codec_env.sources, shared=True);
+codec_env.library_shared = codec_env.SharedLibrary('#build/forstio-codec', [objects_shared]);
+
+## Static lib
+objects_static = []
+codec_env.add_source_files(objects_static, codec_env.sources, shared=False);
+codec_env.library_static = codec_env.StaticLibrary('#build/forstio-codec', [objects_static]);
+
+# Set Alias
+env.Alias('library_codec', [codec_env.library_shared, codec_env.library_static]);
+
+env.targets += ['library_codec'];
+
+# Install
+env.Install('$prefix/lib/', [codec_env.library_shared, codec_env.library_static]);
+env.Install('$prefix/include/forstio/codec/', [codec_env.headers]);
diff --git a/modules/codec/SConstruct b/modules/codec/SConstruct
new file mode 100644
index 0000000..0d7b7c6
--- /dev/null
+++ b/modules/codec/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-core'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/codec/args.h b/modules/codec/args.h
new file mode 100644
index 0000000..6bb75a2
--- /dev/null
+++ b/modules/codec/args.h
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "schema.h"
+
+namespace saw {
+namespace encode {
+struct Args {};
+}
+
+namespace schema {
+template<typename InnerSchemaStruct, typename InnerSchemaTuple>
+using Args = Struct<
+ Member<String, "program">,
+ Member<InnerSchemaStruct, "args">,
+ Member<InnerSchemaTuple, "positionals">
+>;
+}
+
+template<typename T>
+class data<T, encode::Args> {
+ static_assert(always_false<T>,"Not supported. Use the schema::Args alias or check how it's defined.");
+};
+
+template<typename... Str, string_literal... Lits, typename... Tup>
+class data<schema::Args<schema::Struct<schema::Member<Str,Lits>...>, schema::Tuple<Tup...>>, encode::Args> {
+private:
+ char** argv_;
+ int argc_;
+public:
+ data(char** argv, int argc):
+ argv_{argv},
+ argc_{argc}
+ {}
+
+ size_t size() const {
+ if(argc_ < 0){
+ return 0;
+ }
+
+ static_assert(sizeof(int) <= sizeof(size_t), "size_t is smaller than int");
+
+ return static_cast<size_t>(argc_);
+ }
+
+ std::string_view arg_view(size_t i){
+ if(i < size()){
+ return std::string_view{argv_[i]};
+ }
+ return "";
+ }
+};
+
+namespace impl {
+template<typename T, typename ToDec>
+struct args_decode {
+ static_assert(always_false<T,ToDec>, "Not supported");
+};
+
+template<typename... Str, string_literal... Lits, typename... Tup, typename ToDec>
+struct args_decode<schema::Args<schema::Struct<schema::Member<Str,Lits>...>,schema::Tuple<Tup...>>,ToDec> {
+ using Schema = schema::Args<
+ schema::Struct<schema::Member<Str,Lits>...>,
+ schema::Tuple<Tup...>
+ >;
+
+ static error_or<void> decode(data<Schema, encode::Args>& from, data<Schema,ToDec>& to){
+ if(from.size() == 0){
+ return make_error<err::invalid_state>();
+ }
+
+ to.get<"program">().set(std::string{from.arg_view(0)});
+ std::size_t tuple_pos = 0;
+ for(size_t i = 1; i < from.size(); ++i){
+ auto view = from.arg_view(i);
+ if(view.starts_with("--")){
+ view.remove_prefix(std::min(2u, view.size()));
+ ++i;
+
+ if( i >= from.size() ){
+ return make_error<err::invalid_state>();
+ }
+
+ auto value_view = from.arg_view(i);
+
+ auto eov = decode_struct_member<0>(to.get<"args">(), view, value_view);
+ if(eov.is_error()){
+ return eov;
+ }
+ } else {
+ auto eov = decode_tuple_member<0>(to.get<"positionals">(), view);
+ if(eov.is_error()){
+ return eov;
+ }
+ ++tuple_pos;
+ }
+ }
+
+ if(tuple_pos != sizeof...(Tup)){
+ return make_error<err::invalid_state>();
+ }
+
+ return void_t{};
+ }
+};
+}
+
+template<typename Schema>
+class codec<Schema, encode::Args> {
+public:
+ template<typename ToDec>
+ error_or<void> decode(data<Schema, encode::Args>& from, data<Schema, ToDec>& to){
+ struct name_and_value {
+ std::string name;
+ std::string value;
+ };
+ std::string program;
+ std::vector<name_and_value> navs;
+ std::vector<std::string> positionals;
+
+ return void_t{};
+ }
+};
+}
diff --git a/modules/codec/csv.h b/modules/codec/csv.h
new file mode 100644
index 0000000..67c2c1d
--- /dev/null
+++ b/modules/codec/csv.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "data.h"
+
+namespace saw {
+namespace encode {
+struct Csv {};
+}
+
+template<typename Schema>
+class codec<Schema, encode::Csv> {
+public:
+ template<typename FromEncode>
+ static error_or<void> encode(const data<Schema, FromEncode>& from, data<Schema,encode::Csv>& to){
+
+ return make_error<err::not_implemented>();
+ }
+
+ template<typename ToDecode>
+ static error_or<void> decode(data<Schema,encode::Csv>& from, data<Schema, FromEncode>& to){
+
+ return make_error<err::not_implemented>();
+ }
+};
+}
diff --git a/modules/codec/data.h b/modules/codec/data.h
new file mode 100644
index 0000000..237ef5a
--- /dev/null
+++ b/modules/codec/data.h
@@ -0,0 +1,370 @@
+#pragma once
+
+#include <forstio/core/common.h>
+#include <forstio/core/templates.h>
+
+#include <cassert>
+
+#include <array>
+#include <concepts>
+#include <variant>
+#include <vector>
+
+#include "schema.h"
+
+namespace saw {
+namespace encode {
+struct Native {};
+}
+template<typename Schema, typename Encode>
+class codec;
+/*
+ * Helper for the basic message container, so the class doesn't have to be
+ * specialized 10 times.
+ */
+template <class T> struct native_data_type;
+
+template <>
+struct native_data_type<schema::Primitive<schema::SignedInteger, 1>> {
+ using type = int8_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::SignedInteger, 2>> {
+ using type = int16_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::SignedInteger, 4>> {
+ using type = int32_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::SignedInteger, 8>> {
+ using type = int64_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::UnsignedInteger, 1>> {
+ using type = uint8_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::UnsignedInteger, 2>> {
+ using type = uint16_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::UnsignedInteger, 4>> {
+ using type = uint32_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::UnsignedInteger, 8>> {
+ using type = uint64_t;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::FloatingPoint, 4>> {
+ using type = float;
+};
+
+template <>
+struct native_data_type<schema::Primitive<schema::FloatingPoint, 8>> {
+ using type = double;
+};
+
+template<typename T, typename Encoding = encode::Native>
+class data {
+private:
+ static_assert(always_false<T>, "Type not supported");
+};
+
+template<typename... T, string_literal... literals>
+class data<schema::Union<schema::Member<T, literals>...>, encode::Native> {
+private:
+ std::variant<data<T,encode::Native>...> value_;
+public:
+ data() = default;
+
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ template<string_literal lit>
+ void set(data<typename parameter_pack_type<parameter_key_pack_index<lit, literals...>::value, T...>::type, encode::Native> val){
+ value_ = std::move(val);
+ }
+
+ template<string_literal lit>
+ data<typename parameter_pack_type<parameter_key_pack_index<lit, literals...>::value, T...>::type, encode::Native>& init(){
+ value_ = data<typename parameter_pack_type<parameter_key_pack_index<lit, literals...>::value, T...>::type, encode::Native>{};
+ return get<lit>();
+ }
+
+ template<string_literal lit>
+ bool holds_alternative() const {
+ return (parameter_key_pack_index<lit, literals...>::value == value_.index());
+ }
+
+ template<string_literal lit>
+ data<typename parameter_pack_type<parameter_key_pack_index<lit, literals...>::value, T...>::type, encode::Native>& get(){
+ return std::get<parameter_key_pack_index<lit, literals...>::value>(value_);
+ }
+
+ template<string_literal lit>
+ const data<typename parameter_pack_type<parameter_key_pack_index<lit, literals...>::value, T...>::type, encode::Native>& get() const{
+ return std::get<parameter_key_pack_index<lit, literals...>::value>(value_);
+ }
+};
+
+template<typename... T, string_literal... literals>
+class data<schema::Struct<schema::Member<T, literals>...>, encode::Native> {
+private:
+ std::tuple<data<T,encode::Native>...> value_;
+public:
+ data() = default;
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ template<string_literal literal>
+ data<
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ literal, literals...
+ >::value
+ , T...>::type
+ , encode::Native>& get(){
+ return std::get<parameter_key_pack_index<literal, literals...>::value>(value_);
+ }
+
+ template<string_literal literal>
+ const data<
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ literal, literals...
+ >::value
+ , T...>::type
+ , encode::Native>& get() const {
+ return std::get<parameter_key_pack_index<literal, literals...>::value>(value_);
+ }
+
+ constexpr size_t size() const {
+ return sizeof...(T);
+ }
+};
+
+template<typename... T>
+class data<schema::Tuple<T...>, encode::Native> {
+private:
+ std::tuple<data<T,encode::Native>...> value_;
+public:
+ data() = default;
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ template<size_t i>
+ data<typename parameter_pack_type<i,T...>::type, encode::Native>& get(){
+ return std::get<i>(value_);
+ }
+
+ template<size_t i>
+ const data<typename parameter_pack_type<i,T...>::type, encode::Native>& get() const{
+ return std::get<i>(value_);
+ }
+
+ constexpr size_t size() const {
+ return sizeof...(T);
+ }
+};
+
+template<typename T, size_t Dim>
+class data<schema::Array<T,Dim>, encode::Native> {
+ private:
+ std::array<std::size_t, Dim> dims_;
+ std::vector<data<T, encode::Native>> value_;
+
+ std::size_t get_full_size() const {
+ std::size_t s = 1;
+
+ for(std::size_t iter = 0; iter < Dim; ++iter){
+ assert(dims_.at(iter) > 0);
+ s *= dims_.at(iter);
+ }
+
+ return s;
+ }
+ public:
+ data() = default;
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ data(const std::array<std::size_t, Dim>& i):
+ dims_{i},
+ value_{}
+ {
+ value_.resize(get_full_size());
+ }
+
+ template<std::integral... Dims>
+ data(Dims... size_):
+ data{{size_...}}
+ {
+ static_assert(sizeof...(Dims)==Dim, "Argument size must be equal to the Dimension");
+ }
+
+ data<T, encode::Native>& at(const std::array<std::size_t, Dim>& ind){
+ return value_.at(this->get_flat_index(ind));
+ }
+
+ const data<T, encode::Native>& at(const std::array<std::size_t, Dim>& ind) const {
+ return value_.at(this->get_flat_index(ind));
+ }
+
+ template<std::integral... Dims>
+ data<T, encode::Native>& at(Dims... i){
+ return value_.at(this->get_flat_index({i...}));
+ }
+
+ template<std::integral... Dims>
+ const data<T, encode::Native>& at(Dims... i) const {
+ return value_.at(this->get_flat_index({i...}));
+ }
+
+ std::size_t get_dim_size(std::size_t i) const {
+ return dims_.at(i);
+ }
+
+ size_t size() const { return value_.size();}
+
+private:
+ std::size_t get_flat_index(const std::array<std::size_t, Dim>& i) const {
+ std::size_t s = 0;
+
+ std::size_t stride = 1;
+
+ for(std::size_t iter = 0; iter < Dim; ++iter){
+ assert(i.at(iter) < dims_.at(iter));
+ s += i.at(iter) * stride;
+ stride *= dims_.at(iter);
+ }
+
+ return s;
+ }
+};
+
+template<typename T, std::size_t... D>
+class data<schema::FixedArray<T,D...>, encode::Native> {
+private:
+ //using inner_type = std::array<data<T, encode::Native>, multiply_helper<Dims...>::value>;
+ //std::unique_ptr<inner_type> value_;
+ std::vector<data<T, encode::Native>> value_;
+
+public:
+ data(){
+ value_.resize(ct_multiply<std::size_t, D...>::value);
+ }
+
+ data<T, encode::Native>& at(const std::array<std::size_t, sizeof...(D)>& ind){
+ return value_.at(this->get_flat_index(ind));
+ }
+
+ const data<T, encode::Native>& at(const std::array<std::size_t, sizeof...(D)>& ind) const {
+ return value_.at(this->get_flat_index(ind));
+ }
+
+ template<std::integral... Dims>
+ data<T, encode::Native>& at(Dims... i) {
+ return value_.at(this->get_flat_index({i...}));
+ }
+
+ template<std::integral... Dims>
+ const data<T, encode::Native>& at(Dims... i) const {
+ return value_.at(this->get_flat_index({i...}));
+ }
+
+ template<std::size_t i>
+ std::size_t get_dim_size() const {
+ return parameter_pack_value<i, std::size_t, D...>::value;
+ }
+private:
+ std::size_t get_flat_index(const std::array<std::size_t, sizeof...(D)>& i) const {
+ std::size_t s = 0;
+
+ std::size_t stride = 1;
+
+ constexpr static std::array<std::size_t, sizeof...(D)> dims_{D...};
+
+ for(std::size_t iter = 0; iter < sizeof...(D); ++iter){
+ assert(i.at(iter) < dims_.at(iter));
+ s += i.at(iter) * stride;
+ stride *= dims_.at(iter);
+ }
+
+ return s;
+ }
+};
+
+template<>
+class data<schema::String, encode::Native> {
+private:
+ std::string value_;
+public:
+ data() = default;
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ data(std::string value__):value_{std::move(value__)}{}
+ data(std::size_t size_){
+ value_.resize(size_);
+ }
+
+ std::size_t size() const {
+ return value_.size();
+ }
+
+ void set(std::string str){
+ value_ = std::move(str);
+ }
+
+ char& at(size_t i) {
+ return value_.at(i);
+ }
+
+ const char& at(size_t i) const {
+ return value_.at(i);
+ }
+
+ char get_at(size_t i) const{
+ return value_.at(i);
+ }
+
+ void set_at(size_t i, char val){
+ value_.at(i) = val;
+ }
+
+ bool operator==(const std::string_view& val)const{
+ return value_ == val;
+ }
+};
+
+template<typename T, size_t N>
+class data<schema::Primitive<T,N>, encode::Native> {
+private:
+ typename native_data_type<schema::Primitive<T,N>>::type value_;
+public:
+ data():value_{{}}{};
+ SAW_DEFAULT_COPY(data);
+ SAW_DEFAULT_MOVE(data);
+
+ data(typename native_data_type<schema::Primitive<T,N>>::type value__):
+ value_{std::move(value__)}{}
+
+ void set(typename native_data_type<schema::Primitive<T,N>>::type val){
+ value_ = val;
+ }
+
+ typename native_data_type<schema::Primitive<T,N>>::type get() const {return value_;}
+};
+
+
+}
diff --git a/modules/codec/forst.h b/modules/codec/forst.h
new file mode 100644
index 0000000..7e8fbf0
--- /dev/null
+++ b/modules/codec/forst.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "data.h"
+
+namespace saw {
+namespace encode {
+struct KelForst {};
+}
+}
+
+#include "forst.tmpl.hpp"
+
+namespace saw {
+class data<schema::String, encode::KelForst> {
+private:
+ own<buffer> buff_;
+public:
+ data(own<buffer> buff):
+ buff_{std::move(buff)}
+ {}
+};
+
+template<typename... T, string_literal... Keys>
+class data<schema::Struct<schema::Member<T,Keys>...>, encode::KelForst> {
+private:
+ own<buffer> buff_;
+public:
+ data(own<buffer> buff):
+ buff_{std::move(buff)}
+ {}
+};
+}
diff --git a/modules/codec/forst.tmpl.h b/modules/codec/forst.tmpl.h
new file mode 100644
index 0000000..30d18ef
--- /dev/null
+++ b/modules/codec/forst.tmpl.h
@@ -0,0 +1,7 @@
+namespace saw {
+namespace impl {
+struct forst_decode {
+
+};
+}
+}
diff --git a/modules/codec/interface.h b/modules/codec/interface.h
new file mode 100644
index 0000000..b1422a9
--- /dev/null
+++ b/modules/codec/interface.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <forstio/core/error.h>
+#include "schema.h"
+#include "data.h"
+
+namespace saw {
+template<typename SchemaFunc, typename Encode, typename Func>
+class function;
+
+template<typename Request, typename Response, typename Encode, typename Func>
+class function<schema::Function<Request, Response>, Encode, Func> {
+private:
+ Func func_;
+public:
+ function(Func func):
+ func_{std::move(func)}
+ {}
+
+ error_or<data<Response, Encode>> call(data<Request, Encode> req){
+ return func_(std::move(req));
+ }
+};
+
+template<typename T, typename Encode, typename... Funcs>
+class interface;
+
+template<typename... Requests, typename... Responses, string_literal... Names, typename Encode, typename... Funcs>
+class interface<schema::Interface<schema::Member<schema::Function<Requests, Responses>, Names>...>, Encode, Funcs...> {
+private:
+ std::tuple<function<schema::Function<Requests, Responses>, Encode, Funcs>...> funcs_;
+public:
+ interface(function<schema::Function<Requests, Responses>, Encode, Funcs>... funcs):
+ funcs_{std::move(funcs)...}
+ {}
+
+ /**
+ * Get the function based on the string literal matching the position in the tuple
+ */
+ template<string_literal Lit>
+ function<
+ schema::Function<
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ Lit, Names...
+ >::value
+ , Requests...>::type
+ ,
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ Lit, Names...
+ >::value
+ , Responses...>::type
+ >
+ ,
+ Encode
+ ,
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ Lit, Names...
+ >::value
+ , Funcs...>::type
+ >& get(){
+ return std::get<parameter_key_pack_index<Lit, Names...>::value>(funcs_);
+ }
+
+ template<string_literal Lit>
+ error_or<data<
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ Lit, Names...
+ >::value
+ , Responses...>::type
+ , Encode>> call(
+ data<
+ typename parameter_pack_type<
+ parameter_key_pack_index<
+ Lit, Names...
+ >::value
+ , Requests...>::type
+ , Encode> req
+ ){
+ return get<Lit>().call(std::move(req));
+ }
+
+};
+
+template<typename T, typename Encode>
+struct function_factory {
+ template<typename Func>
+ static function<T,Encode, Func> create(Func func){
+ return function<T,Encode,Func>{std::move(func)};
+ }
+};
+
+template<typename T, typename Encode>
+struct interface_factory {
+ template<typename... Func>
+ static interface<T,Encode, Func...> create(Func... func){
+ return interface<T,Encode, Func...>{std::move(func)...};
+ }
+};
+}
diff --git a/modules/codec/rpc.h b/modules/codec/rpc.h
new file mode 100644
index 0000000..b21ecf8
--- /dev/null
+++ b/modules/codec/rpc.h
@@ -0,0 +1,26 @@
+#pragma once
+
+namespace saw {
+template<typename T>
+class remote {
+ static_assert(always_false<T>, "Type of remote not supported");
+};
+
+template<typename T>
+class rpc_client {
+ template<typename... T>
+ struct request {
+ private:
+ std::tuple<id<T>> ids_;
+ public:
+ error_or<data<T>> wait();
+ };
+
+ template<typename... T>
+ request<T...> request_data(id<T>... data);
+};
+
+template<typename T>
+class rpc_server {
+};
+}
diff --git a/modules/codec/schema.h b/modules/codec/schema.h
new file mode 100644
index 0000000..a8494fe
--- /dev/null
+++ b/modules/codec/schema.h
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <forstio/core/common.h>
+#include <forstio/core/string_literal.h>
+
+namespace saw {
+namespace schema {
+// NOLINTBEGIN
+template <typename T, string_literal Literal> struct Member {};
+
+template <typename... T> struct Struct {
+ static_assert(
+ always_false<T...>,
+ "This schema template doesn't support this type of template argument");
+};
+
+template <typename... V, string_literal... K>
+struct Struct<Member<V, K>...> {};
+
+template <typename... T> struct Union {
+ static_assert(
+ always_false<T...>,
+ "This schema template doesn't support this type of template argument");
+};
+
+template <typename... V, string_literal... K>
+struct Union<Member<V, K>...> {};
+
+template <typename T, size_t Dim = 1> struct Array {};
+
+template <class T> struct is_array {
+ constexpr static bool value = false;
+};
+
+template <class T, size_t Dim> struct is_array<schema::Array<T,Dim>> {
+ constexpr static bool value = true;
+};
+
+template<typename T, size_t... S> struct FixedArray {};
+
+template <typename... T> struct Tuple {};
+
+/**
+ * This acts as a separator of different encodings being mashed together
+ * For example we can transport any base64 encodings in JSON
+ *
+ * using WrappedExample = schema::Tuple<
+ * schema::Wrapper<schema::String, encode::Base64>
+ * >;
+ *
+ * data<WrappedExample, encode::Json> ex_data;
+ */
+template <typename T, typename Enc>
+class Wrapper {};
+
+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>;
+
+/**
+ * Classes allowing to distinguish Ints from VarInts
+ */
+template<typename T, std::size_t MaxLen>
+struct VariableLengthPrimitive {};
+
+using VarInt = VariableLengthPrimitive<SignedInteger, 5>;
+using VarLong = VariableLengthPrimitive<SignedInteger, 10>;
+
+/**
+ * Classes enabling Rpc calls
+ */
+template <class Request, class Response>
+struct Function {};
+
+template <class... T> struct Interface {
+ static_assert(
+ always_false<T...>,
+ "This schema template doesn't support this type of template argument");
+};
+
+template <class... Requests, class... Responses, string_literal... Names>
+struct Interface<Member<Function<Requests, Responses>,Names>...> {};
+
+// NOLINTEND
+} // namespace schema
+} // namespace saw
diff --git a/modules/codec/schema_hash.h b/modules/codec/schema_hash.h
new file mode 100644
index 0000000..5690166
--- /dev/null
+++ b/modules/codec/schema_hash.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include "schema.h"
+
+namespace saw {
+struct schema_hash_combine {
+ static constexpr uint64_t apply(uint64_t seed, uint64_t v){
+ return (seed ^ v) * 1099511628211u;
+
+
+ return seed ^( std::hash<uint64_t>{}(v) + 0x9e3779b9 + (seed<<6) + (seed >> 2));
+ }
+};
+
+template<typename Schema>
+struct schema_hash {
+ static_assert(always_false<Schema>, "Not schema_hashable");
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::SignedInteger,1>> {
+ static constexpr uint64_t base_value = 0u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::SignedInteger,2>> {
+ static constexpr uint64_t base_value = 1u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::SignedInteger,4>> {
+ static constexpr uint64_t base_value = 2u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::SignedInteger,8>> {
+ static constexpr uint64_t base_value = 3u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::UnsignedInteger,1>> {
+ static constexpr uint64_t base_value = 4u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::UnsignedInteger,2>> {
+ static constexpr uint64_t base_value = 5u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::UnsignedInteger,4>> {
+ static constexpr uint64_t base_value = 6u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::UnsignedInteger,8>> {
+ static constexpr uint64_t base_value = 7u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::FloatingPoint,4>> {
+ static constexpr uint64_t base_value = 8u;
+};
+
+template<>
+struct schema_hash<schema::Primitive<schema::FloatingPoint,8>> {
+ static constexpr uint64_t base_value = 9u;
+};
+
+template<typename... T>
+struct schema_hash<schema::Tuple<T...>> {
+ static constexpr uint64_t base_value = 10u;
+};
+
+template<typename... T, string_literal Literal>
+struct schema_hash<schema::Struct<schema::Member<T,Literal>...>> {
+ static constexpr uint64_t base_value = 11u;
+};
+
+template<typename... T, string_literal Literal>
+struct schema_hash<schema::Union<schema::Member<T,Literal>...>> {
+ static constexpr uint64_t base_value = 12u;
+};
+
+template<typename T, size_t Dim>
+struct schema_hash<schema::Array<T,Dim>> {
+ static constexpr uint64_t base_value = 13u;
+};
+
+template<>
+struct schema_hash<schema::String> {
+ static constexpr uint64_t base_value = 14u;
+};
+
+template<typename T, typename N>
+struct schema_hash<schema::Wrapper<T,N>> {
+ static constexpr uint64_t base_value = 15u;
+};
+
+template<typename T, size_t... Dims>
+struct schema_hash<schema::FixedArray<T,Dims...>> {
+ static constexpr uint64_t base_value = 16u;
+};
+
+}
diff --git a/modules/codec/schema_stringify.h b/modules/codec/schema_stringify.h
new file mode 100644
index 0000000..a82081a
--- /dev/null
+++ b/modules/codec/schema_stringify.h
@@ -0,0 +1,118 @@
+#pragma once
+
+#include "schema.h"
+
+#include <sstream>
+#include <type_traits>
+
+namespace saw {
+template<typename Schema>
+struct schema_stringify {
+ static_assert(always_false<Schema>, "Not supported");
+};
+
+template<typename T, size_t Dim>
+struct schema_stringify<schema::Array<T,Dim>> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw::schema::Array<";
+ schema_stringify<T>::apply(iss);
+ iss << ",";
+ iss << ct_convert_to_digits<Dim, 10>::literal.view();
+ iss << ">";
+ }
+};
+
+template<typename T, size_t N>
+struct schema_stringify<schema::Primitive<T,N>> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw::schema::Primitive<";
+ schema_stringify<T>::apply(iss);
+ iss << ",";
+ iss << ct_convert_to_digits<N,10>::literal.view();
+ iss << ">";
+ }
+};
+
+template<>
+struct schema_stringify<schema::SignedInteger> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw:schema::SignedInteger";
+ }
+};
+
+template<>
+struct schema_stringify<schema::UnsignedInteger> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw:schema::UnsignedInteger";
+ }
+};
+
+template<>
+struct schema_stringify<schema::FloatingPoint> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw:schema::FloatingPoint";
+ }
+};
+
+template<typename... T>
+struct schema_stringify_member {
+ static void apply(std::stringstream& iss) {
+ (void)iss;
+ }
+};
+
+template<typename T0, string_literal Name, typename... TL>
+struct schema_stringify_member<schema::Member<T0,Name>, TL...> {
+
+ static void apply(std::stringstream& iss) {
+ iss << "saw::schema::Member<";
+ schema_stringify<T0>::apply(iss);
+ iss << ",\"";
+ iss << Name.view();
+ iss << "\">";
+ if constexpr ( sizeof...(TL) > 0){
+ iss << ",";
+ schema_stringify_member<TL...>::apply(iss);
+ }
+ }
+};
+
+template<typename... T, string_literal... Lits>
+struct schema_stringify<schema::Struct<schema::Member<T,Lits>...>> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw::schema::Struct<";
+ schema_stringify_member<schema::Member<T,Lits>...>::apply(iss);
+ iss << ">";
+ }
+};
+
+template<typename... T, string_literal... Lits>
+struct schema_stringify<schema::Union<schema::Member<T,Lits>...>> {
+ static void apply(std::stringstream& iss) {
+ iss << "saw::schema::Union<";
+ schema_stringify_member<schema::Member<T,Lits>...>::apply(iss);
+ iss << ">";
+ }
+};
+
+template<typename Req, typename Resp>
+struct schema_stringify<schema::Function<Req, Resp>> {
+ static void apply(std::stringstream& iss){
+ iss << "saw::schema::Function<";
+ schema_stringify<Req>::apply(iss);
+ iss << ",";
+ schema_stringify<Resp>::apply(iss);
+ iss << ">";
+ }
+};
+
+template<typename... T, string_literal... Lits>
+struct schema_stringify<schema::Interface<schema::Member<T,Lits>...>>{
+ static void apply(std::stringstream& iss){
+ iss << "saw::schema::Interface<";
+ schema_stringify_member<schema::Member<T,Lits>...>::apply(iss);
+ iss << ">";
+ }
+};
+
+}
diff --git a/modules/codec/simple.h b/modules/codec/simple.h
new file mode 100644
index 0000000..8760754
--- /dev/null
+++ b/modules/codec/simple.h
@@ -0,0 +1,389 @@
+#pragma once
+
+#include "data.h"
+#include "stream_value.h"
+
+#include <forstio/core/buffer.h>
+#include <forstio/core/error.h>
+
+namespace saw {
+namespace encode {
+struct KelSimple {};
+}
+
+template<typename T>
+class data<T, encode::KelSimple> {
+private:
+ ring_buffer buffer_;
+public:
+ data() = default;
+
+ buffer& get_buffer(){
+ return buffer_;
+ }
+};
+
+namespace impl {
+template<typename Schema, typename FromEnc>
+class kelsimple_encode {
+ static_assert(always_false<Schema, FromEnc>, "This schema type is not being handled by the kelsimple encoding.");
+};
+
+template<typename T, size_t N, typename FromEnc>
+struct kelsimple_encode<schema::Primitive<T,N>, FromEnc> {
+ static error_or<void> encode(const data<schema::Primitive<T,N>, FromEnc>& from, buffer& to){
+ auto eov = stream_value<schema::Primitive<T,N>>::encode(from.get(), to);
+ return eov;
+ }
+};
+
+template<typename T, size_t Dim, typename FromEnc>
+struct kelsimple_encode<schema::Array<T,Dim>, FromEnc> {
+ template<std::size_t Level>
+ static error_or<void> encode_level(const data<schema::Array<T,Dim>, FromEnc>& from, buffer& to, std::array<std::size_t, Dim>& index){
+ if constexpr (Dim == Level){
+ return kelsimple_encode<T,FromEnc>::encode(from.at(index), to);
+ } else {
+ const std::size_t dim_size = from.get_dim_size(Level);
+ for(index[Level] = 0; (index.at(Level) < dim_size); ++index[Level]){
+ auto eov = encode_level<Level+1>(from, to, index);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Array<T,Dim>, FromEnc>& from, buffer& to){
+ {
+ for(uint64_t i = 0; i < Dim; ++i){
+ auto eov = stream_value<schema::UInt64>::encode(from.get_dim_size(i), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+ {
+ std::array<std::size_t, Dim> index;
+ std::fill(index.begin(), index.end(), 0);
+
+ return encode_level<0>(from, to, index);
+ }
+ return void_t{};
+ }
+};
+
+template<typename... T, string_literal... Lits, typename FromEnc>
+struct kelsimple_encode<schema::Struct<schema::Member<T,Lits>...>,FromEnc> {
+ template<std::size_t i>
+ static error_or<void> encode_member(const data<schema::Struct<schema::Member<T,Lits>...>, FromEnc>& from, buffer& to){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ {
+ auto eov = kelsimple_encode<Type, FromEnc>::encode(from.template get<Literal>(), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = encode_member<i+1>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Struct<schema::Member<T,Lits>...>, FromEnc>& from, buffer& to){
+ return encode_member<0>(from, to);
+ }
+};
+
+template<typename... T, string_literal... Lits, typename FromEnc>
+struct kelsimple_encode<schema::Union<schema::Member<T,Lits>...>,FromEnc> {
+ template<std::size_t i>
+ static error_or<void> encode_member(const data<schema::Union<schema::Member<T,Lits>...>, FromEnc>& from, buffer& to){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ if (from.template holds_alternative<Literal>()) {
+ {
+ auto eov = stream_value<schema::UInt64>::encode(static_cast<uint64_t>(i), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ auto eov = kelsimple_encode<Type, FromEnc>::encode(from.template get<Literal>(), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+
+ if constexpr ( (i+1) < sizeof...(T) ){
+ auto eov = encode_member<i+1>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Union<schema::Member<T,Lits>...>, FromEnc>& from, buffer& to){
+ return encode_member<0>(from, to);
+ }
+};
+
+template<typename... T, typename FromEnc>
+struct kelsimple_encode<schema::Tuple<T...>, FromEnc> {
+ template<std::size_t i>
+ static error_or<void> encode_member(const data<schema::Tuple<T...>, FromEnc>& from, buffer& to){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ {
+ auto eov = kelsimple_encode<Type, FromEnc>::encode(from.template get<i>(), to);
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = encode_member<i+1>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> encode(const data<schema::Tuple<T...>, FromEnc>& from, buffer& to){
+ return encode_member<0>(from, to);
+ }
+};
+
+template<typename FromEnc>
+struct kelsimple_encode<schema::String, FromEnc> {
+ static error_or<void> encode(const data<schema::String, FromEnc>& from, buffer& to){
+ const auto str_size = from.size();
+ typename native_data_type<schema::UInt64>::type str_len = static_cast<uint64_t>(str_size);
+ {
+ auto eov = stream_value<schema::UInt64>::encode(str_len, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ for(std::size_t i = 0; i < str_size; ++i){
+ auto eov = stream_value<schema::Int8>::encode(from.at(i), to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ return void_t{};
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+template<typename Schema, typename FromEnc>
+class kelsimple_decode {
+ static_assert(always_false<Schema, FromEnc>, "This schema type is not being handled by the kelsimple encoding.");
+};
+
+template<typename T, size_t N, typename FromEnc>
+struct kelsimple_decode<schema::Primitive<T,N>, FromEnc> {
+ static error_or<void> decode(buffer& from, data<schema::Primitive<T,N>, FromEnc>& to){
+ typename native_data_type<schema::Primitive<T,N>>::type val{};
+ auto eov = stream_value<schema::Primitive<T,N>>::decode(val, from);
+ if (eov.is_value()) {
+ to.set(val);
+ }
+ return eov;
+ }
+
+};
+
+template<typename T, size_t Dim, typename FromEnc>
+struct kelsimple_decode<schema::Array<T,Dim>, FromEnc> {
+ template<std::size_t Level>
+ static error_or<void> decode_level(buffer& from, data<schema::Array<T,Dim>, FromEnc>& to, std::array<std::size_t, Dim>& index){
+ if constexpr (Level == Dim){
+ return kelsimple_decode<T, FromEnc>::decode(from, to.at(index));
+ }else{
+ const std::size_t dim_size = to.get_dim_size(Level);
+ for(index[Level] = 0; index[Level] < dim_size; ++index[Level]){
+ auto eov = decode_level<Level+1>(from, to, index);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ }
+ return void_t{};
+ }
+
+ static error_or<void> decode(buffer& from, data<schema::Array<T,Dim>, FromEnc>& to){
+ {
+ std::array<std::size_t, Dim> dims{};
+ for(std::size_t i = 0; i < Dim; ++i){
+ uint64_t val{};
+ auto eov = stream_value<schema::UInt64>::decode(val, from);
+ if(eov.is_error()){
+ return eov;
+ }
+ dims.at(i) = static_cast<std::size_t>(val);
+ }
+ to = data<schema::Array<T,Dim>,FromEnc>{dims};
+ }
+ {
+ std::array<std::size_t, Dim> index{};
+ return decode_level<0>(from, to, index);
+ }
+ return void_t{};
+ }
+};
+template<typename... T, string_literal... Lits, typename FromEnc>
+struct kelsimple_decode<schema::Struct<schema::Member<T,Lits>...>,FromEnc> {
+ template<std::size_t i>
+ static error_or<void> decode_member(buffer& from, data<schema::Struct<schema::Member<T,Lits>...>, FromEnc>& to){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+ {
+ auto eov = kelsimple_decode<Type, FromEnc>::decode(from, to.template get<Literal>());
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = decode_member<i+1>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+
+ }
+ static error_or<void> decode(buffer& from, data<schema::Struct<schema::Member<T,Lits>...>, FromEnc>& to){
+ return decode_member<0>(from, to);
+ }
+
+};
+
+template<typename... T, string_literal... Lits, typename FromEnc>
+struct kelsimple_decode<schema::Union<schema::Member<T,Lits>...>,FromEnc> {
+ template<uint64_t i>
+ static error_or<void> decode_member(buffer& from, data<schema::Union<schema::Member<T,Lits>...>, FromEnc>& to, uint64_t val){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ constexpr string_literal Literal = parameter_key_pack_type<i, Lits...>::literal;
+
+ if( i == val ){
+ to.template set<Literal>(data<Type, FromEnc>{});
+ auto eov = kelsimple_decode<Type, FromEnc>::decode(from, to.template get<Literal>());
+ if(eov.is_error()){
+ return eov;
+ }
+ return void_t{};
+ }
+
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = decode_member<i+1>(from, to, val);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+
+ }
+ static error_or<void> decode(buffer& from, data<schema::Union<schema::Member<T,Lits>...>, FromEnc>& to){
+ uint64_t val{};
+ auto eov = stream_value<schema::UInt64>::decode(val, from);
+ if(eov.is_error()){
+ return eov;
+ }
+ if ( val >= sizeof...(T) ){
+ return make_error<err::invalid_state>();
+ }
+ return decode_member<0>(from, to, val);
+ }
+
+};
+
+template<typename... T, typename FromEnc>
+struct kelsimple_decode<schema::Tuple<T...>,FromEnc> {
+ template<std::size_t i>
+ static error_or<void> decode_member(buffer& from, data<schema::Tuple<T...>, FromEnc>& to){
+ using Type = typename parameter_pack_type<i,T...>::type;
+ {
+ auto eov = kelsimple_decode<Type, FromEnc>::decode(from, to.template get<i>());
+ }
+ if constexpr ((i+1) < sizeof...(T)){
+ auto eov = decode_member<i+1>(from, to);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ return void_t{};
+
+ }
+ static error_or<void> decode(buffer& from, data<schema::Tuple<T...>, FromEnc>& to){
+ return decode_member<0>(from, to);
+ }
+
+};
+template<typename FromEnc>
+struct kelsimple_decode<schema::String, FromEnc> {
+ static error_or<void> decode(buffer& from, data<schema::String, FromEnc>& to){
+ {
+ uint64_t val{};
+ auto eov = stream_value<schema::UInt64>::decode(val, from);
+ if(eov.is_error()){
+ return eov;
+ }
+ to = data<schema::String,FromEnc>{val};
+ }
+ const std::size_t str_size = to.size();
+ for(std::size_t i = 0; i < str_size; ++i){
+ int8_t val{};
+ auto eov = stream_value<schema::Int8>::decode(val, from);
+ if(eov.is_error()){
+ return eov;
+ }
+ to.set_at(i, val);
+ }
+ return void_t{};
+ }
+};
+
+}
+
+template<typename Schema>
+class codec<Schema, encode::KelSimple> {
+public:
+ struct config {
+ size_t depth = 16;
+ size_t length = 1024;
+ };
+private:
+ config cfg_;
+public:
+ codec() = default;
+
+ SAW_FORBID_COPY(codec);
+ SAW_DEFAULT_MOVE(codec);
+
+ template<typename FromEnc>
+ error_or<void> encode(const data<Schema, FromEnc>& from_enc, data<Schema, encode::KelSimple>& to_enc){
+ buffer_view buff_v{to_enc.get_buffer()};
+
+ auto eov = impl::kelsimple_encode<Schema, FromEnc>::encode(from_enc, buff_v);
+
+ to_enc.get_buffer().write_advance(buff_v.write_offset());
+
+ return eov;
+ }
+
+ template<typename ToDec>
+ error_or<void> decode(data<Schema, encode::KelSimple>& from_dec, data<Schema, ToDec>& to){
+ buffer_view buff_v{from_dec.get_buffer()};
+
+ auto eov = impl::kelsimple_decode<Schema,ToDec>::decode(buff_v, to);
+
+ return eov;
+ }
+};
+}
diff --git a/modules/codec/stream_value.h b/modules/codec/stream_value.h
new file mode 100644
index 0000000..09203cb
--- /dev/null
+++ b/modules/codec/stream_value.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "schema.h"
+
+#include <forstio/core/buffer.h>
+#include <forstio/core/error.h>
+
+#include <cstdint>
+#include <cstring>
+
+namespace saw {
+/**
+ * Helper class to encode/decode any primtive type into/from litte endian.
+ * The shift class does this by shifting bytes. This type of procedure is
+ * platform independent. So it does not matter if the memory layout is
+ * little endian or big endian
+ */
+template<typename T> class shift_stream_value {
+ static_assert(always_false<T>, "Shift Stream Value only supports Primitives");
+};
+
+template <typename T, size_t N> class shift_stream_value<schema::Primitive<T,N>> {
+public:
+ inline static error_or<void> decode(typename native_data_type<schema::Primitive<T,N>>::type &val, buffer &buff) {
+ if (buff.read_composite_length() < N) {
+ return make_error<err::buffer_exhausted>();
+ }
+
+ typename native_data_type<schema::Primitive<schema::UnsignedInteger,N>>::type raw = 0;
+
+ for (size_t i = 0; i < N; ++i) {
+ raw |= (static_cast<typename native_data_type<schema::Primitive<T,N>>::type>(buff.read(i)) << (i * 8));
+ }
+
+ memcpy(&val, &raw, N);
+ buff.read_advance(N);
+
+ return void_t{};
+ }
+
+ inline static error_or<void> encode(const typename native_data_type<schema::Primitive<T,N>>::type &val, buffer &buff) {
+ error err = buff.write_require_length(N);
+ if (err.failed()) {
+ return err;
+ }
+
+ typename native_data_type<schema::Primitive<schema::UnsignedInteger,N>>::type raw{};
+ memcpy(&raw, &val, N);
+
+ for (size_t i = 0; i < N; ++i) {
+ buff.write(i) = raw >> (i * 8);
+ }
+
+ buff.write_advance(N);
+
+ return void_t{};
+ }
+
+ inline static size_t size() { return N; }
+};
+
+template <typename T> using stream_value = shift_stream_value<T>;
+
+} // namespace saw
diff --git a/modules/core/.nix/derivation.nix b/modules/core/.nix/derivation.nix
new file mode 100644
index 0000000..1618651
--- /dev/null
+++ b/modules/core/.nix/derivation.nix
@@ -0,0 +1,21 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+}:
+
+stdenv.mkDerivation {
+ pname = "forstio-core";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/core/SConscript b/modules/core/SConscript
new file mode 100644
index 0000000..02bd050
--- /dev/null
+++ b/modules/core/SConscript
@@ -0,0 +1,42 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+core_env = env.Clone();
+
+core_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+core_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += core_env.sources;
+env.headers += core_env.headers;
+
+## Shared lib
+objects_shared = []
+core_env.add_source_files(objects_shared, core_env.sources, shared=True);
+core_env.library_shared = core_env.SharedLibrary('#build/forstio-core', [objects_shared]);
+
+## Static lib
+objects_static = []
+core_env.add_source_files(objects_static, core_env.sources, shared=False);
+core_env.library_static = core_env.StaticLibrary('#build/forstio-core', [objects_static]);
+
+# Set Alias
+env.Alias('library_core', [core_env.library_shared, core_env.library_static]);
+
+env.targets += ['library_core'];
+
+# Install
+env.Install('$prefix/lib/', [core_env.library_shared, core_env.library_static]);
+env.Install('$prefix/include/forstio/core/', [core_env.headers]);
+
+# Test
+# Export('core_env');
+# SConscript('tests/SConscript');
diff --git a/modules/core/SConstruct b/modules/core/SConstruct
new file mode 100644
index 0000000..865d131
--- /dev/null
+++ b/modules/core/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=[])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/core/array.h b/modules/core/array.h
new file mode 100644
index 0000000..f82b8d6
--- /dev/null
+++ b/modules/core/array.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <vector>
+
+namespace saw {
+/**
+ * Array container avoiding exceptions
+ */
+template<typename T>
+class array {
+private:
+ std::vector<T> data_;
+public:
+ array() = default;
+
+ SAW_FORBID_COPY(array);
+
+ T& at(size_t i) noexcept {
+ return data_.at(i);
+ }
+
+ const T& at(size_t i) noexcept const {
+ return data_.at(i);
+ }
+
+ size_t size() noexcept const {
+ return data_.size();
+ }
+
+ T& front() noexcept {
+ return data_.front();
+ }
+
+ const T& front() noexcept const {
+ return data_.front();
+ }
+
+ T& back() noexcept {
+ return data_.back();
+ }
+
+ const T& back() noexcept const {
+ return data_.back();
+ }
+
+ error_or<void> push(T val) noexcept {
+ try{
+ data_.push_back(std::move(val));
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+
+ error_or<void> pop() noexcept {
+ try{
+ data_.pop_back();
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+
+ error_or<void> resize(size_t i) noexcept {
+ try{
+ data_.resize(i);
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+
+ error_or<void> reserve(size_t i) noexcept {
+ try{
+ data_.reserve(i);
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+
+ error_or<void> shrink_to_fit() noexcept {
+ try{
+ data_.shrink_to_fit();
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+};
+}
diff --git a/modules/core/buffer.cpp b/modules/core/buffer.cpp
new file mode 100644
index 0000000..15f4cae
--- /dev/null
+++ b/modules/core/buffer.cpp
@@ -0,0 +1,436 @@
+#include "buffer.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+
+namespace saw {
+error buffer::push(const uint8_t &value) {
+ size_t write_remain = write_composite_length();
+ if (write_remain > 0) {
+ write() = value;
+ write_advance(1);
+ } else {
+ return make_error<err::buffer_exhausted>();
+ }
+ return no_error();
+}
+
+error buffer::push(const uint8_t &buffer, size_t size) {
+ error error = write_require_length(size);
+ if (error.failed()) {
+ return error;
+ }
+ const uint8_t *buffer_ptr = &buffer;
+ while (size > 0) {
+ size_t segment = std::min(write_segment_length(), size);
+ memcpy(&write(), buffer_ptr, segment);
+ write_advance(segment);
+ size -= segment;
+ buffer_ptr += segment;
+ }
+ return no_error();
+}
+
+error buffer::pop(uint8_t &value) {
+ if (read_composite_length() > 0) {
+ value = read();
+ read_advance(1);
+ } else {
+ return make_error<err::buffer_exhausted>();
+ }
+ return no_error();
+}
+
+error buffer::pop(uint8_t &buffer, size_t size) {
+ if (read_composite_length() >= size) {
+ uint8_t *buffer_ptr = &buffer;
+ while (size > 0) {
+ size_t segment = std::min(read_segment_length(), size);
+ memcpy(buffer_ptr, &read(), segment);
+ read_advance(segment);
+ size -= segment;
+ buffer_ptr += segment;
+ }
+ } else {
+ return make_error<err::buffer_exhausted>();
+ }
+ return no_error();
+}
+
+/*
+std::string buffer::to_hex() const {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ for (size_t i = 0; i < read_composite_length(); ++i) {
+ oss << std::setw(2) << (uint16_t)read(i);
+ if ((i + 1) < read_composite_length()) {
+ oss << ((i % 4 == 3) ? '\n' : ' ');
+ }
+ }
+ return oss.str();
+}
+*/
+
+std::string convert_to_string(const buffer& buff){
+ std::ostringstream oss;
+ for (size_t i = 0; i < buff.read_composite_length(); ++i) {
+ oss << buff.read(i);
+ }
+ return oss.str();
+}
+
+buffer_view::buffer_view(buffer &buffer)
+ : buffer_{&buffer}, read_offset_{0}, write_offset_{0} {}
+
+size_t buffer_view::read_position() const {
+ return read_offset_ + buffer_->read_position();
+}
+
+size_t buffer_view::read_composite_length() const {
+ assert(read_offset_ <= buffer_->read_composite_length());
+ if (read_offset_ > buffer_->read_composite_length()) {
+ return 0;
+ }
+
+ return buffer_->read_composite_length() - read_offset_;
+}
+
+size_t buffer_view::read_segment_length(size_t offset) const {
+ size_t off = offset + read_offset_;
+ assert(off <= buffer_->read_composite_length());
+ if (off > buffer_->read_composite_length()) {
+ return 0;
+ }
+
+ return buffer_->read_segment_length(off);
+}
+
+void buffer_view::read_advance(size_t bytes) {
+ size_t offset = bytes + read_offset_;
+ assert(offset <= buffer_->read_composite_length());
+ if (offset > buffer_->read_composite_length()) {
+ read_offset_ += buffer_->read_composite_length();
+ return;
+ }
+
+ read_offset_ += bytes;
+}
+
+uint8_t &buffer_view::read(size_t i) {
+ size_t pos = i + read_offset_;
+
+ assert(pos < buffer_->read_composite_length());
+
+ return buffer_->read(pos);
+}
+
+const uint8_t &buffer_view::read(size_t i) const {
+ size_t pos = i + read_offset_;
+
+ assert(pos < buffer_->read_composite_length());
+
+ return buffer_->read(pos);
+}
+
+size_t buffer_view::write_position() const {
+ return write_offset_ + buffer_->write_position();
+}
+
+size_t buffer_view::write_composite_length() const {
+ assert(write_offset_ <= buffer_->write_composite_length());
+ if (write_offset_ > buffer_->write_composite_length()) {
+ return 0;
+ }
+
+ return buffer_->write_composite_length() - write_offset_;
+}
+
+size_t buffer_view::write_segment_length(size_t offset) const {
+ size_t off = offset + write_offset_;
+ assert(off <= buffer_->write_composite_length());
+ if (off > buffer_->write_composite_length()) {
+ return 0;
+ }
+
+ return buffer_->write_segment_length(off);
+}
+
+void buffer_view::write_advance(size_t bytes) {
+ size_t offset = bytes + write_offset_;
+ assert(offset <= buffer_->write_composite_length());
+ if (offset > buffer_->write_composite_length()) {
+ write_offset_ += buffer_->write_composite_length();
+ return;
+ }
+
+ write_offset_ += bytes;
+}
+
+uint8_t &buffer_view::write(size_t i) {
+ size_t pos = i + write_offset_;
+
+ assert(pos < buffer_->write_composite_length());
+
+ return buffer_->write(pos);
+}
+
+const uint8_t &buffer_view::write(size_t i) const {
+ size_t pos = i + write_offset_;
+
+ assert(pos < buffer_->write_composite_length());
+
+ return buffer_->write(pos);
+}
+
+error buffer_view::write_require_length(size_t bytes) {
+ return buffer_->write_require_length(bytes + write_offset_);
+}
+
+size_t buffer_view::read_offset() const { return read_offset_; }
+
+size_t buffer_view::write_offset() const { return write_offset_; }
+
+ring_buffer::ring_buffer() : read_position_{0}, write_position_{0} {
+ buffer_.resize(RING_BUFFER_MAX_SIZE);
+}
+
+ring_buffer::ring_buffer(size_t size) : read_position_{0}, write_position_{0} {
+ buffer_.resize(size);
+}
+
+size_t ring_buffer::read_position() const { return read_position_; }
+
+/*
+ * If write is ahead of read it is a simple distance, but if read ist ahead of
+ * write then there are two segments
+ *
+ */
+size_t ring_buffer::read_composite_length() const {
+ return write_position() < read_position()
+ ? buffer_.size() - (read_position() - write_position())
+ : (write_reached_read_ ? buffer_.size()
+ : write_position() - read_position());
+}
+
+/*
+ * 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 ring_buffer::read_segment_length(size_t offset) const {
+ size_t read_composite = read_composite_length();
+ assert(offset <= read_composite);
+ offset = std::min(offset, read_composite);
+ size_t remaining = read_composite - offset;
+
+ size_t read_offset = read_position() + 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 (write_position() < read_offset) {
+ return buffer_.size() - read_offset;
+ }
+
+ if (write_position() == read_offset) {
+ if (remaining > 0) {
+ return buffer_.size() - read_offset;
+ } else {
+ return 0;
+ }
+ }
+
+ return write_position() - read_offset;
+}
+
+void ring_buffer::read_advance(size_t bytes) {
+ size_t read_composite = read_composite_length();
+
+ 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;
+ write_reached_read_ = bytes > 0 ? false : write_reached_read_;
+}
+
+uint8_t &ring_buffer::read(size_t i) {
+ assert(i < read_composite_length());
+ size_t pos = read_position_ + i;
+ pos = pos >= buffer_.size() ? pos - buffer_.size() : pos;
+ return buffer_[pos];
+}
+
+const uint8_t &ring_buffer::read(size_t i) const {
+ assert(i < read_composite_length());
+ size_t pos = read_position_ + i;
+ pos = pos >= buffer_.size() ? pos - buffer_.size() : pos;
+ return buffer_[pos];
+}
+
+size_t ring_buffer::write_position() const { return write_position_; }
+
+size_t ring_buffer::write_composite_length() const {
+ return read_position() > write_position()
+ ? (read_position() - write_position())
+ : (write_reached_read_
+ ? 0
+ : buffer_.size() - (write_position() - read_position()));
+}
+
+size_t ring_buffer::write_segment_length(size_t offset) const {
+ size_t write_composite = write_composite_length();
+ assert(offset <= write_composite);
+ offset = std::min(offset, write_composite);
+
+ size_t write_offset = write_position() + 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 ring_buffer::write_advance(size_t bytes) {
+ assert(bytes <= write_composite_length());
+ size_t advanced = write_position_ + bytes;
+ write_position_ = advanced >= buffer_.size() ? advanced - buffer_.size()
+ : advanced;
+
+ write_reached_read_ =
+ (write_position_ == read_position_ && bytes > 0 ? true : false);
+}
+
+uint8_t &ring_buffer::write(size_t i) {
+ assert(i < write_composite_length());
+ size_t pos = write_position_ + i;
+ pos = pos >= buffer_.size() ? pos - buffer_.size() : pos;
+ return buffer_[pos];
+}
+
+const uint8_t &ring_buffer::write(size_t i) const {
+ assert(i < write_composite_length());
+ size_t pos = write_position_ + i;
+ pos = pos >= buffer_.size() ? pos - buffer_.size() : pos;
+ return buffer_[pos];
+}
+/*
+ Error RingBuffer::increaseSize(size_t size){
+ size_t old_size = buffer.size();
+ size_t new_size = old_size + size;
+ buffer.resize(new_size);
+ if(readPosition() > writePosition() || (readPosition() ==
+ writePosition() && write_reached_read)){ size_t remaining = old_size -
+ writePosition(); size_t real_remaining = 0; while(remaining > 0){ size_t
+ segment = std::min(remaining, size); memcpy(&buffer[new_size-segment],
+ &buffer[old_size-segment], segment); remaining -= segment; size -= segment;
+ old_size -= segment;
+ new_size -= segment;
+ }
+ }
+
+ return noError();
+ }
+*/
+error ring_buffer::write_require_length(size_t bytes) {
+ size_t write_remain = write_composite_length();
+ if (bytes > write_remain) {
+ return make_error<err::buffer_exhausted>();
+ }
+ return no_error();
+}
+
+array_buffer::array_buffer(size_t size)
+ : read_position_{0}, write_position_{0} {
+ buffer_.resize(size);
+}
+
+size_t array_buffer::read_position() const { return read_position_; }
+
+size_t array_buffer::read_composite_length() const {
+ return write_position_ - read_position_;
+}
+
+size_t array_buffer::read_segment_length(size_t offset) const {
+ size_t read_composite = read_composite_length();
+ assert(offset <= read_composite);
+
+ offset = std::min(read_composite, offset);
+ size_t read_offset = read_position_ + offset;
+
+ return write_position_ - read_offset;
+}
+
+void array_buffer::read_advance(size_t bytes) {
+ assert(bytes <= read_composite_length());
+ read_position_ += bytes;
+}
+
+uint8_t &array_buffer::read(size_t i) {
+ assert(i < read_composite_length());
+
+ return buffer_[i + read_position_];
+}
+
+const uint8_t &array_buffer::read(size_t i) const {
+ assert(i + read_position_ < buffer_.size());
+
+ return buffer_[i + read_position_];
+}
+
+size_t array_buffer::write_position() const { return write_position_; }
+
+size_t array_buffer::write_composite_length() const {
+ assert(write_position_ <= buffer_.size());
+ return buffer_.size() - write_position_;
+}
+
+size_t array_buffer::write_segment_length(size_t offset) const {
+ assert(write_position_ <= buffer_.size());
+ size_t write_composite = write_composite_length();
+
+ assert(offset <= write_composite);
+ offset = std::min(write_composite, offset);
+ size_t write_offset = write_position_ + offset;
+
+ return buffer_.size() - write_offset;
+}
+
+void array_buffer::write_advance(size_t bytes) {
+ assert(bytes <= write_composite_length());
+ write_position_ += bytes;
+}
+
+uint8_t &array_buffer::write(size_t i) {
+ assert(i < write_composite_length());
+ return buffer_[i + write_position_];
+}
+
+const uint8_t &array_buffer::write(size_t i) const {
+ assert(i < write_composite_length());
+ return buffer_[i + write_position_];
+}
+error array_buffer::write_require_length(size_t bytes) {
+ size_t write_remain = write_composite_length();
+ if (bytes > write_remain) {
+ return make_error<err::buffer_exhausted>();
+ }
+ return no_error();
+}
+
+} // namespace saw
diff --git a/modules/core/buffer.h b/modules/core/buffer.h
new file mode 100644
index 0000000..f0cf76e
--- /dev/null
+++ b/modules/core/buffer.h
@@ -0,0 +1,199 @@
+#pragma once
+
+#include "error.h"
+
+#include <array>
+#include <cstdint>
+#include <deque>
+#include <list>
+#include <string>
+#include <vector>
+
+namespace saw {
+/*
+ * Access class to reduce templated BufferSegments bloat
+ */
+class buffer {
+protected:
+ ~buffer() = default;
+
+public:
+ virtual size_t read_position() const = 0;
+ virtual size_t read_composite_length() const = 0;
+ virtual size_t read_segment_length(size_t offset = 0) const = 0;
+ virtual void read_advance(size_t bytes) = 0;
+
+ virtual uint8_t &read(size_t i = 0) = 0;
+ virtual const uint8_t &read(size_t i = 0) const = 0;
+
+ virtual size_t write_position() const = 0;
+ virtual size_t write_composite_length() const = 0;
+ virtual size_t write_segment_length(size_t offset = 0) const = 0;
+ virtual void write_advance(size_t bytes) = 0;
+
+ virtual uint8_t &write(size_t i = 0) = 0;
+ virtual const uint8_t &write(size_t i = 0) const = 0;
+
+ /*
+ * Sometime buffers need to grow with a little more control
+ * than with push and pop for more efficient calls.
+ * There is nothing you can do if read hasn't been filled, but at
+ * least write can be increased if it is demanded.
+ */
+ virtual error write_require_length(size_t bytes) = 0;
+
+ error push(const uint8_t &value);
+ error push(const uint8_t &buffer, size_t size);
+ error pop(uint8_t &value);
+ error pop(uint8_t &buffer, size_t size);
+
+};
+
+/**
+ * Converts a buffer to a string for convenience cases
+ */
+std::string convert_to_string(const buffer& buf);
+
+/**
+ *
+ */
+
+/*
+ * A viewer class for buffers.
+ * Working on the reference buffer invalidates the buffer view
+ */
+class buffer_view : public buffer {
+private:
+ buffer *buffer_;
+ size_t read_offset_;
+ size_t write_offset_;
+
+public:
+ buffer_view(buffer &);
+
+ size_t read_position() const override;
+ size_t read_composite_length() const override;
+ size_t read_segment_length(size_t offset = 0) const override;
+ void read_advance(size_t bytes) override;
+
+ uint8_t &read(size_t i = 0) override;
+ const uint8_t &read(size_t i = 0) const override;
+
+ size_t write_position() const override;
+ size_t write_composite_length() const override;
+ size_t write_segment_length(size_t offset = 0) const override;
+ void write_advance(size_t bytes) override;
+
+ uint8_t &write(size_t i = 0) override;
+ const uint8_t &write(size_t i = 0) const override;
+
+ error write_require_length(size_t bytes) override;
+
+ size_t read_offset() const;
+ size_t write_offset() const;
+};
+
+/*
+ * Buffer size meant for default allocation size of the ringbuffer since
+ * this class currently doesn't support proper resizing
+ */
+constexpr size_t RING_BUFFER_MAX_SIZE = 4096;
+/*
+ * Buffer wrapping around if read caught up
+ */
+class ring_buffer final : public buffer {
+private:
+ std::vector<uint8_t> buffer_;
+ size_t read_position_;
+ size_t write_position_;
+ bool write_reached_read_ = false;
+
+public:
+ ring_buffer();
+ ring_buffer(size_t size);
+
+ inline size_t size() const { return buffer_.size(); }
+
+ inline uint8_t &operator[](size_t i) { return buffer_[i]; }
+ inline const uint8_t &operator[](size_t i) const { return buffer_[i]; }
+
+ size_t read_position() const override;
+ size_t read_composite_length() const override;
+ size_t read_segment_length(size_t offset = 0) const override;
+ void read_advance(size_t bytes) override;
+
+ uint8_t &read(size_t i = 0) override;
+ const uint8_t &read(size_t i = 0) const override;
+
+ size_t write_position() const override;
+ size_t write_composite_length() const override;
+ size_t write_segment_length(size_t offset = 0) const override;
+ void write_advance(size_t bytes) override;
+
+ uint8_t &write(size_t i = 0) override;
+ const uint8_t &write(size_t i = 0) const override;
+
+ error write_require_length(size_t bytes) override;
+};
+
+/*
+ * One time buffer
+ */
+class array_buffer : public buffer {
+private:
+ std::vector<uint8_t> buffer_;
+
+ size_t read_position_;
+ size_t write_position_;
+
+public:
+ array_buffer(size_t size);
+
+ size_t read_position() const override;
+ size_t read_composite_length() const override;
+ size_t read_segment_length(size_t offset = 0) const override;
+ void read_advance(size_t bytes) override;
+
+ uint8_t &read(size_t i = 0) override;
+ const uint8_t &read(size_t i = 0) const override;
+
+ size_t write_position() const override;
+ size_t write_composite_length() const override;
+ size_t write_segment_length(size_t offset = 0) const override;
+ void write_advance(size_t bytes) override;
+
+ uint8_t &write(size_t i = 0) override;
+ const uint8_t &write(size_t i = 0) const override;
+
+ error write_require_length(size_t bytes) override;
+};
+
+class chain_array_buffer : public buffer {
+private:
+ std::deque<array_buffer> buffer_;
+
+ size_t read_position_;
+ size_t write_position_;
+
+public:
+ chain_array_buffer();
+
+ size_t read_position() const override;
+ size_t read_composite_length() const override;
+ size_t read_segment_length(size_t offset = 0) const override;
+ void read_advance(size_t bytes) override;
+
+ uint8_t &read(size_t i = 0) override;
+ const uint8_t &read(size_t i = 0) const override;
+
+ size_t write_position() const override;
+ size_t write_composite_length() const override;
+ size_t write_segment_length(size_t offset = 0) const override;
+ void write_advance(size_t bytes) override;
+
+ uint8_t &write(size_t i = 0) override;
+ const uint8_t &write(size_t i = 0) const override;
+
+ error write_require_length(size_t bytes) override;
+};
+} // namespace saw
diff --git a/modules/core/common.h b/modules/core/common.h
new file mode 100644
index 0000000..a06c238
--- /dev/null
+++ b/modules/core/common.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <utility>
+
+namespace saw {
+
+#define SAW_CONCAT_(x, y) x##y
+#define SAW_CONCAT(x, y) SAW_CONCAT_(x, y)
+#define SAW_UNIQUE_NAME(prefix) SAW_CONCAT(prefix, __LINE__)
+
+#define SAW_FORBID_COPY(classname) \
+ classname(const classname &) = delete; \
+ classname &operator=(const classname &) = delete
+
+#define SAW_FORBID_MOVE(classname) \
+ classname(classname &&) = delete; \
+ classname &operator=(classname &&) = delete
+
+#define SAW_DEFAULT_COPY(classname) \
+ classname(const classname &) = default; \
+ classname &operator=(const classname &) = default
+
+#define SAW_DEFAULT_MOVE(classname) \
+ classname(classname &&) = default; \
+ classname &operator=(classname &&) = default
+
+// In case of C++20
+#define SAW_ASSERT(expression) \
+ assert(expression); \
+ if (!(expression)) [[unlikely]]
+
+template <typename T> using maybe = std::optional<T>;
+
+template <typename T> using own = std::unique_ptr<T>;
+
+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 own<T>(new T(std::forward<Args>(args)...));
+}
+
+template <typename T, class... Args> our<T> share(Args &&...args) {
+ return std::make_shared<T>(std::forward<Args>(args)...);
+}
+
+template <typename T> T instance() noexcept;
+
+template <typename Func, typename T> struct return_type_helper {
+ typedef decltype(instance<Func>()(instance<T>())) Type;
+};
+template <typename Func> struct return_type_helper<Func, void> {
+ typedef decltype(instance<Func>()()) Type;
+};
+
+template <typename Func, typename T>
+using return_type = typename return_type_helper<Func, T>::Type;
+
+struct void_t {};
+
+template <typename T> struct void_fix { typedef T Type; };
+template <> struct void_fix<void> { typedef void_t Type; };
+template <typename T> using fix_void = typename void_fix<T>::Type;
+
+template <typename T> struct void_unfix { typedef T Type; };
+template <> struct void_unfix<void_t> { typedef void Type; };
+template <typename T> using unfix_void = typename void_unfix<T>::Type;
+
+template <typename... T> constexpr bool always_false = false;
+
+} // namespace saw
diff --git a/modules/core/error.cpp b/modules/core/error.cpp
new file mode 100644
index 0000000..360e628
--- /dev/null
+++ b/modules/core/error.cpp
@@ -0,0 +1,156 @@
+#include "error.h"
+
+namespace saw {
+error::error(error::code code_, bool is_critical__)
+ : error_code_{static_cast<error::code>(code_)}, is_critical_{is_critical__} {}
+
+error::error(error::code code_, bool is_critical__, const std::string_view &msg)
+ :
+ error_code_{static_cast<error::code>(code_)}
+ , is_critical_{is_critical__}, error_message_{msg}{}
+
+error::error(error &&error)
+ :
+ error_code_{std::move(error.error_code_)}
+ , is_critical_{std::move(error.is_critical_)}
+ , error_message_{std::move(error.error_message_)}{}
+
+const std::string_view error::get_category() const {
+ auto& reg = impl::get_error_registry();
+
+ auto eov = reg.search_category(error_code_);
+ SAW_ASSERT(eov.is_value()){
+ return "Error category not found. Report this error to the forstio maintainer";
+ }
+
+ return eov.get_value();
+}
+
+const std::string_view error::get_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 !this->is_type<err::no_error>();
+}
+
+bool error::is_critical() const {
+ return is_critical_;
+}
+
+bool error::is_recoverable() const {
+ return !is_critical_;
+}
+
+error error::copy_error() const {
+ auto copy_error_code = error_code_;
+ error error{copy_error_code, is_critical_};
+
+ 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::get_id() const { return error_code_; }
+
+namespace impl {
+error_registry& get_error_registry() {
+ static own<error_registry> reg = nullptr;
+ if(!reg){
+ reg = heap<error_registry>();
+ }
+
+ assert(reg);
+ return *reg;
+}
+}
+
+error no_error(){
+ return make_error<err::no_error>();
+}
+
+namespace impl {
+error_registry::error_registry():
+ infos_{
+ {
+ err::no_error::description,
+ err::no_error::is_critical
+ },
+ {
+ err::not_found::description,
+ err::not_found::is_critical
+ },
+ {
+ err::out_of_memory::description,
+ err::out_of_memory::is_critical
+ }
+ }
+{}
+
+error_or<const std::string_view> error_registry::search_category(const error::code& id) const {
+ if( id >= infos_.size()){
+ return make_error<err::not_found>();
+ }
+
+ return infos_.at(id).description;
+}
+
+error_or<error::code> error_registry::search_id(const std::string_view& desc)const{
+ /**
+ * Search the index in the vector
+ */
+ size_t i{};
+ size_t info_max_size = std::min<std::size_t>(infos_.size(), std::numeric_limits<error::code>::max());
+ for(i = 0; i < info_max_size; ++i){
+ if(infos_.at(i).description == desc){
+ break;
+ }
+ }
+
+ if(i == info_max_size){
+ return make_error<err::not_found>();
+ }
+
+ return static_cast<error::code>(i);
+}
+
+error_or<error::code> error_registry::search_or_register_id(const std::string_view& desc, bool is_critical){
+ auto err_or_id = search_id(desc);
+
+ if(err_or_id.is_value()){
+ return err_or_id.get_value();
+ }
+
+ auto& err = err_or_id.get_error();
+
+ if(err.is_type<err::not_found>()){
+ size_t new_index = infos_.size();
+ if(new_index == std::numeric_limits<error::code>::max()){
+ return make_error<err::out_of_memory>("Error registry ids are exhausted");
+ }
+ infos_.emplace_back(error_info{desc, is_critical});
+ return static_cast<error::code>(new_index);
+ }
+
+ return std::move(err);
+}
+}
+
+} // namespace saw
diff --git a/modules/core/error.h b/modules/core/error.h
new file mode 100644
index 0000000..e816734
--- /dev/null
+++ b/modules/core/error.h
@@ -0,0 +1,248 @@
+#pragma once
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <variant>
+#include <vector>
+
+#include <cassert>
+
+#include "common.h"
+
+namespace saw {
+/**
+ * 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:
+ using code = uint32_t;
+private:
+ code error_code_;
+ bool is_critical_;
+ std::variant<std::string_view, std::string> error_message_;
+
+public:
+ error(error::code id, bool is_critical);
+ error(error::code id, bool is_critical, const std::string_view &msg);
+ error(error &&error);
+
+ SAW_FORBID_COPY(error);
+
+ error &operator=(error &&) = default;
+
+ const std::string_view get_message() const;
+
+ const std::string_view get_category() const;
+
+ bool failed() const;
+
+ bool is_critical() const;
+ bool is_recoverable() const;
+
+ /**
+ * Replaces the copy constructor. We need this since we want to explicitly copy and not implicitly
+ */
+ error copy_error() const;
+
+ code get_id() const;
+
+ template<typename T>
+ bool is_type() const;
+};
+
+template<typename T>
+class error_or;
+
+namespace impl {
+
+class error_registry {
+private:
+ struct error_info {
+ error_info() = delete;
+ error_info(const std::string_view& d_, bool critical_):description{d_}, is_critical{critical_}{}
+
+ std::string_view description;
+ bool is_critical;
+ };
+
+ std::vector<error_info> infos_;
+public:
+ error_registry();
+
+ SAW_FORBID_COPY(error_registry);
+ SAW_FORBID_MOVE(error_registry);
+
+ error_or<const std::string_view> search_category(const error::code& id) const;
+
+ error_or<error::code> search_id(const std::string_view& desc) const;
+
+ error_or<error::code> search_or_register_id(const std::string_view& desc, bool is_critical);
+};
+
+error_registry& get_error_registry();
+
+template<typename T>
+error::code get_template_id(){
+ static error::code id = std::numeric_limits<error::code>::max();
+
+ if(id == std::numeric_limits<error::code>::max()){
+ auto& reg = get_error_registry();
+
+ auto err_or_id = reg.search_or_register_id(T::description, T::is_critical);
+ if(err_or_id.is_error()){
+ return std::numeric_limits<error::code>::max();
+ }
+
+ id = err_or_id.get_value();
+ }
+
+ return id;
+}
+}
+
+template<typename T> error make_error(const std::string_view& generic){
+ error::code id = impl::get_template_id<T>();
+
+ return error{id, T::is_critical, generic};
+}
+
+template<typename T> error make_error(){
+ error::code id = impl::get_template_id<T>();
+
+ return error{id, T::is_critical};
+}
+
+error make_error(error::code code, const std::string_view &generic);
+
+
+namespace err {
+struct no_error {
+ static constexpr std::string_view description = "No error has occured";
+ static constexpr bool is_critical = false;
+};
+
+struct recoverable {
+ static constexpr std::string_view description = "Generic recoverable error";
+ static constexpr bool is_critical = false;
+};
+
+struct critical {
+ static constexpr std::string_view description = "Generic critical error";
+ static constexpr bool is_critical = true;
+};
+
+struct buffer_exhausted {
+ static constexpr std::string_view description = "Buffer Exhausted";
+ static constexpr bool is_critical = false;
+};
+
+struct not_found {
+ static constexpr std::string_view description = "Not found";
+ static constexpr bool is_critical = false;
+};
+
+struct out_of_memory {
+ static constexpr std::string_view description = "Out of memory";
+ static constexpr bool is_critical = true;
+};
+
+struct invalid_state {
+ static constexpr std::string_view description = "Invalid state";
+ static constexpr bool is_critical = true;
+};
+
+struct not_supported {
+ static constexpr std::string_view description = "Not supported";
+ static constexpr bool is_critical = false;
+};
+
+struct not_implemented {
+ static constexpr std::string_view description = "Not implemented";
+ static constexpr bool is_critical = true;
+};
+
+struct already_exists {
+ static constexpr std::string_view description = "Already exists";
+ static constexpr bool is_critical = false;
+};
+}
+
+/**
+ * Shorthand for no error happened
+ */
+error no_error();
+
+/**
+ * Exception alternative. Since I code without exceptions this class is
+ * essentially a kind of exception replacement.
+ */
+template <typename T> class error_or;
+
+class error_or_value {
+public:
+ virtual ~error_or_value() = default;
+
+ template <typename T> error_or<unfix_void<T>> &as() {
+ return static_cast<error_or<unfix_void<T>> &>(*this);
+ }
+
+ template <typename T> const error_or<unfix_void<T>> &as() const {
+ return static_cast<const error_or<unfix_void<T>> &>(*this);
+ }
+};
+
+template <typename T> class error_or final : public error_or_value {
+private:
+ std::variant<error, fix_void<T>> value_or_error_;
+
+ static_assert(!std::is_same_v<T, void_t>,
+ "Don't use internal private types");
+
+public:
+ error_or():value_or_error_{fix_void<T>{}}{}
+ error_or(const fix_void<T> &value) : value_or_error_{value} {}
+
+ error_or(fix_void<T> &&value) : value_or_error_{std::move(value)} {}
+
+ error_or(const error &error) : value_or_error_{error} {}
+ error_or(error &&error) : value_or_error_{std::move(error)} {}
+
+ bool is_value() const {
+ return std::holds_alternative<fix_void<T>>(value_or_error_);
+ }
+
+ bool is_error() const {
+ return std::holds_alternative<class error>(value_or_error_);
+ }
+
+ class error &get_error() {
+ return std::get<class error>(value_or_error_);
+ }
+
+ const class error &get_error() const {
+ return std::get<class error>(value_or_error_);
+ }
+
+ fix_void<T> &get_value() { return std::get<fix_void<T>>(value_or_error_); }
+
+ const fix_void<T> &get_value() const {
+ return std::get<fix_void<T>>(value_or_error_);
+ }
+};
+
+template <typename T> class error_or<error_or<T>> {
+private:
+ error_or() = delete;
+};
+
+template<typename T>
+bool error::is_type() const {
+
+ return error_code_ == impl::get_template_id<T>();
+}
+
+} // namespace saw
diff --git a/modules/core/id.h b/modules/core/id.h
new file mode 100644
index 0000000..d836648
--- /dev/null
+++ b/modules/core/id.h
@@ -0,0 +1,54 @@
+#pragma once
+
+namespace saw {
+/**
+ * ID class which is tied to it's representing class
+ */
+template<typename T>
+class id {
+private:
+ /**
+ * Alias for the value type representing the ID
+ */
+ using type = uint64_t;
+
+ /**
+ * The low level value
+ */
+ type value_;
+public:
+ /**
+ * Basic constructor for the id class
+ */
+ id(type val):
+ value_{val}
+ {}
+
+ SAW_DEFAULT_COPY(id);
+ SAW_DEFAULT_MOVE(id);
+
+ /**
+ * Equal operator for the id.
+ * Returns true if equal, false otherwise.
+ */
+ bool operator==(const id<T>& rhs) const {
+ return value_ == rhs.value_;
+ }
+
+ /**
+ * Unequal operator for the id.
+ * Returns false if equal, true otherwise.
+ */
+ bool operator!=(const id<T>& rhs) const {
+ return !(*this == rhs);
+ }
+
+ /**
+ * Returns a const ref of the underlying base type.
+ * Mostly used for internal purposes.
+ */
+ const type& get_value() const {
+ return value_;
+ }
+};
+}
diff --git a/modules/core/id_map.h b/modules/core/id_map.h
new file mode 100644
index 0000000..d8329cf
--- /dev/null
+++ b/modules/core/id_map.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include "id.h"
+#include "error.h"
+
+#include <deque>
+
+namespace saw {
+/**
+ * Fast random access id based container.
+ *
+ * Access - O(1)
+ * Insert - O(1)
+ * Erase - O(n) ? Dunno
+ */
+template<typename T>
+class id_map final {
+private:
+ /**
+ * Container which stores the primary data
+ */
+ std::vector<T> data_;
+ /**
+ * Container which tracks free'd/fragmented elements within the
+ * main container
+ */
+ std::deque<id<T>> free_ids_;
+
+private:
+ /**
+ * Tries to reduce top ids
+ */
+ void reduce_free_ids() noexcept {
+ for(;;){
+ if(free_ids_.empty()){
+ break;
+ }
+
+ if((free_ids_.front().get_value() + 1) < data_.size()){
+ break;
+ }
+
+ /**
+ * Can this throw?
+ */
+ free_ids_.pop_front();
+ }
+ }
+public:
+ /**
+ * Default constructor
+ */
+ id_map() = default;
+
+ SAW_FORBID_COPY(id_map);
+ SAW_DEFAULT_MOVE(id_map);
+
+ /**
+ * Inserts an element into the container and returns either an id on success
+ * or an error on failure.
+ */
+ error_or<id<T>> insert(T val) noexcept {
+ /// @todo Fix size_t and id base type
+ if(free_ids_.empty()){
+ try {
+ size_t i = data_.size();
+ data_.emplace_back(std::move(val));
+ return saw::id<T>{i};
+ } catch(std::exception& e) {
+ return make_error<err::out_of_memory>();
+ }
+ } else {
+ auto f_id = std::move(free_ids_.back());
+ free_ids_.pop_back();
+ data_.at(f_id.get_value()) = std::move(val);
+ return f_id;
+ }
+
+ exit(-1);
+ // Dummy return since this is not reachable
+ return make_error<err::critical>();
+ }
+
+ /**
+ * Erase a value at this id.
+ */
+ error_or<void> erase(const id<T>& val) noexcept {
+ /**
+ * If id is bigger than the available vector then return an error.
+ */
+ if(val.get_value() >= data_.size()){
+ return make_error<err::not_found>("ID is too large");
+ }
+
+ /**
+ * Check if it's the highest ID. Then we can just try to reduce the highest
+ * IDs.
+ */
+ if((val.get_value() + 1) == data_.size()){
+ data_.pop_back();
+ this->reduce_free_ids();
+ if(data_.size()*2 <= data_.capacity()){
+ try {
+ data_.shrink_to_fit();
+ }catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+ }
+ return void_t{};
+ }
+
+ /**
+ * Check if ID already exists with the free IDs.
+ * This would mean that a double free has occured.
+ */
+ auto find_id = std::find(free_ids_.begin(), free_ids_.end(), val);
+ if(find_id != free_ids_.end()){
+ return make_error<err::not_found>("ID value has already been freed");
+ }
+
+ /**
+ * Insert id into deque and sort it.
+ */
+ try {
+ free_ids_.push_back(val);
+ } catch(std::exception& e){
+ return make_error<err::out_of_memory>();
+ }
+ std::stable_sort(free_ids_.begin(), free_ids_.end(),
+ [](const id<T>& left, const id<T>& right) -> bool {
+ return left.get_value() > right.get_value();
+ }
+ );
+ return void_t{};
+ }
+};
+}
diff --git a/modules/core/mcts.h b/modules/core/mcts.h
new file mode 100644
index 0000000..8a8f5ea
--- /dev/null
+++ b/modules/core/mcts.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "tree.h"
+
+namespace saw {
+template<typename T>
+class mcts_tree {
+private:
+ struct value {
+ uint64_t numerater;
+ uint64_t denominater;
+ T state;
+
+ value() = default;
+ value(T st):
+ numerater{0},
+ denominater{0},
+ state{std::move(st)}
+ {}
+ };
+
+ tree_container<value, mcts_tree<T>> data_;
+public:
+ mcts_tree() = default{
+ data_.add(value{});
+ }
+
+ mcts_tree(T state){
+ data_.add(value{std::move(state)});
+ }
+
+ size_t size() const {
+ return data_.size() - 1;
+ }
+
+ T& get_state(){
+ return data_.at(0).get_value().state;
+ }
+
+ const T& get_state() const {
+ return data_.at(0).get_value().state;
+ }
+
+ mcts_tree<T>& get_tree(size_t i){
+ return data_.at(i+1).get_tree();
+ }
+
+ const mcts_tree<T>& get_tree(size_t i) const {
+ return data_.at(i+1).get_tree();
+ }
+};
+}
diff --git a/modules/core/platonic.h b/modules/core/platonic.h
new file mode 100644
index 0000000..eefe99f
--- /dev/null
+++ b/modules/core/platonic.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "error.h"
+
+namespace saw {
+namespace impl {
+/**
+ *
+ */
+template<typename Prec, uint8_t N>
+struct platonic_helper {
+ static_assert(always_false<Prec,N>, "Unsupported platonic body. Alternatively it's not a platonic body");
+};
+
+template<typename Prec>
+struct platonic_helper<Prec,4u> {
+ static constexpr surface_edges = 3u;
+/*
+ static constexpr std::array<std::array<Prec,3u>, 4u> normals = {
+ {0.0, 0.0, -1.0}, // 1
+ {}, // 2
+ {}, // 3
+ {} // 4
+ };
+*/
+};
+
+template<typename Prec>
+struct platonic_helper<Prec,6u> {
+ static constexpr surface_edges = 4u;
+
+ static constexpr std::array<std::array<Prec,3u>, 6u> normals = {
+ { 1.0, 0.0, 0.0}, // 1
+ {-1.0, 0.0, 0.0}, // 2
+ { 0.0, 1.0, 0.0}, // 3
+ { 0.0,-1.0, 0.0}, // 4
+ { 0.0, 0.0, 1.0}, // 5
+ { 0.0, 0.0,-1.0} // 6
+ };
+};
+
+template<typename Prec>
+struct platonic_helper<Prec,20u> {
+ static constexpr uint8_t surface_edges = 3u;
+/*
+ static constexpr std::array<std::array<Prec,3u>, 20u> normals = {
+ {}, // 1
+ {}, // 2
+ {}, // 3
+ {}, // 4
+ {}, // 5
+ {}, // 6
+ {}, // 7
+ {}, // 8
+ {}, // 9
+ {}, // 10
+ {}, // 11
+ {}, // 12
+ {}, // 13
+ {}, // 14
+ {}, // 15
+ {}, // 16
+ {}, // 17
+ {}, // 18
+ {}, // 19
+ {} // 20
+ };
+*/
+};
+}
+/**
+ * Container for describing each platonic body with
+ * helpers describing the orientation of each body.
+ */
+template<typename T, typename Prec, uint8_t N>
+class platonic {
+private:
+ /**
+ * Storage for the surfaces
+ */
+ std::array<T,N> surfaces_;
+public:
+ constexpr uint8_t get_surface_edge_size() constexpr {
+ return platonic_helper<T,N>::surface_edges;
+ }
+
+ constexpr uint8_t get_surface_size() constexpr {
+ return N;
+ }
+
+ T& at(uint8_t i){
+ return surface_.at(i);
+ }
+
+ const T& at(uint8_t i) const {
+ return surface_.at(i);
+ }
+
+ constexpr std::array<Prec, 3>& get_surface_normal(size_t i) constexpr {
+
+ }
+};
+}
diff --git a/modules/core/string_literal.h b/modules/core/string_literal.h
new file mode 100644
index 0000000..ccc8f49
--- /dev/null
+++ b/modules/core/string_literal.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <array>
+#include <string_view>
+
+namespace saw {
+/**
+ * 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 string_literal {
+public:
+ static_assert(N > 0, "string_literal needs a null terminator");
+
+ constexpr string_literal(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 string_literal<CharT, N> &) const noexcept = default;
+
+ template <class CharTR, size_t NR>
+ constexpr bool
+ operator==(const string_literal<CharTR, NR> &) const noexcept {
+ return false;
+ }
+
+ template<size_t NR>
+ constexpr string_literal<CharT, N+NR-1> operator+(const string_literal<CharT, NR>& rhs) const noexcept {
+ CharT sum[N+NR-1];
+
+ // The weird i+1 happens due to needing to skip the '\0' terminator
+ for(size_t i = 0; (i+1) < N; ++i){
+ sum[i] = data[i];
+ }
+ for(size_t i = 0; i < NR; ++i){
+ sum[i+N-1] = rhs.data[i];
+ }
+
+ return string_literal<CharT, N+NR-1>{sum};
+ }
+};
+
+template <typename T, T... Chars>
+constexpr string_literal<T, sizeof...(Chars) + 1u> operator""_sl() {
+ return string_literal<T, sizeof...(Chars) + 1u>{{Chars..., '\0'}};
+}
+} // namespace saw
diff --git a/modules/core/templates.h b/modules/core/templates.h
new file mode 100644
index 0000000..2eb0f7e
--- /dev/null
+++ b/modules/core/templates.h
@@ -0,0 +1,150 @@
+#pragma once
+
+#include "string_literal.h"
+
+namespace saw {
+
+template <class T, class... TL> struct parameter_pack_index;
+
+template <class T, class... TL> struct parameter_pack_index<T, T, TL...> {
+ static constexpr size_t value = 0u;
+};
+
+template <class T, class TL0, class... TL>
+struct parameter_pack_index<T, TL0, TL...> {
+ static constexpr size_t value =
+ 1u + parameter_pack_index<T, TL...>::value;
+};
+
+template <size_t N, class... T> struct parameter_pack_type {
+ static_assert(always_false<T...>, "Should've been caught by the specializations");
+};
+
+template <class TN, class... T> struct parameter_pack_type<0, TN, T...> {
+ using type = TN;
+};
+
+template <size_t N, class TN, class... T>
+struct parameter_pack_type<N, TN, T...> {
+ static_assert(sizeof...(T) > 0, "Exhausted parameters");
+ static_assert(N > 0, "Invalid number. Should've been caught");
+ using type = typename parameter_pack_type<N - 1, T...>::type;
+};
+/*
+ * Nightmare inducing compiler problems found here. Somehow non-type
+ * string_literals cannot be resolved as non-type primitive template values can.
+ * This is the workaround
+ */
+template <string_literal V, string_literal Key0, string_literal... Keys>
+struct parameter_key_pack_index_helper {
+ static constexpr size_t value =
+ (V == Key0)
+ ? (0u)
+ : (1u + parameter_key_pack_index_helper<V, Keys...>::value);
+};
+
+template <string_literal V, string_literal Key0>
+struct parameter_key_pack_index_helper<V, Key0> {
+ static constexpr size_t value = (V == Key0) ? (0u) : (1u);
+};
+
+template <string_literal V, string_literal... Keys>
+struct parameter_key_pack_index {
+ static constexpr size_t value =
+ parameter_key_pack_index_helper<V, Keys...>::value;
+ static_assert(value < sizeof...(Keys),
+ "Provided string_literal doesn't exist in searched list");
+};
+
+template <size_t i, size_t s, string_literal Key0, string_literal... Keys>
+struct parameter_key_pack_type_helper {
+ static constexpr string_literal literal = parameter_key_pack_type_helper<i, s+1, Keys...>::literal;
+};
+
+template <size_t i, string_literal Key0, string_literal... Keys>
+struct parameter_key_pack_type_helper<i, i, Key0, Keys...> {
+ static constexpr string_literal literal = Key0;
+};
+
+template <size_t i, string_literal... Keys>
+struct parameter_key_pack_type {
+ static constexpr string_literal literal = parameter_key_pack_type_helper<i, 0, Keys...>::literal;
+
+ static_assert(i < sizeof...(Keys), "Provided index is too large for list");
+};
+
+template<std::size_t i, std::size_t s, typename T, T V0, T... VN>
+struct parameter_pack_value_helper {
+ static constexpr T value = parameter_pack_value_helper<i, s+1, T, VN...>::value;
+};
+
+template<std::size_t i, typename T, T V0, T... VN>
+struct parameter_pack_value_helper<i, i, T, V0, VN...> {
+ static constexpr T value = V0;
+};
+
+template<std::size_t i, typename T, T... Values>
+struct parameter_pack_value {
+ static constexpr T value = parameter_pack_value_helper<i, 0, T, Values...>::value;
+ static_assert(i < sizeof...(Values), "Provided index is too large for list");
+};
+
+template<typename T, T... V>
+struct ct_multiply;
+
+template<typename T>
+struct ct_multiply<T> {
+ static constexpr T value = 1;
+};
+
+template<typename T, T V0, T... VN>
+struct ct_multiply<T, V0, VN...> {
+ static constexpr T value = V0 * ct_multiply<T,VN...>::value;
+};
+
+namespace impl {
+template<typename T, size_t i>
+struct ct_convert_digits_table_helper {
+ static_assert(i <= 15, "Only conversion up to hex is supported");
+
+ static constexpr std::array<T, 16> table = {
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'A', 'B',
+ 'C', 'D', 'E', 'F'
+ };
+
+ static constexpr T value = table[i];
+};
+
+template<uint64_t Num, uint64_t Base, uint64_t... Digs>
+struct ct_convert_digits_helper {
+ static constexpr size_t size = ct_convert_digits_helper<Num / Base, Base, Num % Base, Digs...>::size;
+ static constexpr std::array<uint64_t, size> value = ct_convert_digits_helper<Num / Base, Base, Num % Base, Digs...>::value;
+ static constexpr string_literal literal = ct_convert_digits_helper<Num / Base, Base, Num % Base, Digs...>::literal;
+};
+
+template<uint64_t Base, uint64_t... Digs>
+struct ct_convert_digits_helper<0, Base, Digs...> {
+ static constexpr size_t size = sizeof...(Digs);
+ static constexpr std::array<uint64_t, size> value = {Digs...};
+ static constexpr string_literal literal = {{ct_convert_digits_table_helper<char, Digs>::value..., '\0'}};
+};
+
+template<uint64_t Base>
+struct ct_convert_digits_helper<0, Base> {
+ static constexpr size_t size = 0;
+ static constexpr std::array<uint64_t, 1> value = {0};
+ static constexpr string_literal literal = "0"_sl;
+};
+}
+
+template<uint64_t Num, uint64_t Base>
+struct ct_convert_to_digits {
+ static_assert(Base <= 16, "Only conversion up to hex is supported");
+
+ static constexpr size_t size = impl::ct_convert_digits_helper<Num, Base>::size;
+ static constexpr std::array<uint64_t, size> value = impl::ct_convert_digits_helper<Num, Base>::value;
+ static constexpr string_literal literal = impl::ct_convert_digits_helper<Num,Base>::literal;
+};
+}
diff --git a/modules/core/tree.h b/modules/core/tree.h
new file mode 100644
index 0000000..68fa20a
--- /dev/null
+++ b/modules/core/tree.h
@@ -0,0 +1,248 @@
+#pragma once
+
+#include <variant>
+#include <vector>
+
+#include "error.h"
+
+namespace saw {
+/**
+ * Container with a simplistic approach to a branch
+ */
+template<typename T, typename Tree>
+class branch;
+
+/**
+ * Tree object holding branches.
+ *
+ * The name comes from the fact a tree is acting as a node while the branch class is the
+ * edge to a leaf or other nodes. A tree holds the branches while the branch either has
+ * a leaf or another sub tree.
+ */
+template<typename T, typename Tree>
+class tree_container final {
+private:
+ /**
+ * Object holding the treeed branch instances
+ */
+ std::vector<branch<T,Tree>> children_;
+public:
+ /**
+ * Default constructor
+ */
+ tree_container() = default;
+
+ /**
+ * Destructor
+ */
+ ~tree_container() = default;
+
+ SAW_FORBID_COPY(tree_container);
+ SAW_DEFAULT_MOVE(tree_container);
+
+ /**
+ * Reserve space for siz elements
+ */
+ error_or<void> reserve(std::size_t siz){
+ try{
+ children_.reserve(siz);
+ }catch(const std::exception& e){
+ (void) e;
+
+ return make_error<err::out_of_memory>();
+ }
+
+ return void_t{};
+ }
+
+ /**
+ * Add a branch with a leaf attached to the tree
+ */
+ error_or<std::size_t> add(T leaf) {
+ std::size_t index = size();
+ try {
+ /**
+ * Technically we're adding a leaf on a branch
+ */
+ children_.emplace_back(std::move(leaf));
+ }catch(const std::exception& e){
+ (void)e;
+
+ return make_error<err::critical>();
+ }
+
+ return index;
+ }
+
+ /**
+ * Add a branch to the tree with a tree attached
+ */
+ error_or<std::size_t> add() {
+ std::size_t index = size();
+ try {
+
+ children_.emplace_back(Tree{});
+ }catch(const std::exception& e){
+ (void)e;
+
+ return make_error<err::critical>();
+ }
+
+ return index;
+ }
+
+ /**
+ * Returns the amount of branches contained within this tree level
+ */
+ std::size_t size() const {
+ return children_.size();
+ }
+
+ /**
+ * Returns the branch at i
+ */
+ branch<T,Tree>& at(std::size_t i){
+ return children_.at(i);
+ }
+
+ /**
+ * Returns the branch at i
+ */
+ const branch<T,Tree>& at(std::size_t i) const {
+ return children_.at(i);
+ }
+};
+
+template<typename T, typename Tree>
+class branch final {
+private:
+ using type = std::variant<Tree,T>;
+ type tov_;
+
+ /**
+ * We're friend classing the tree since it's way easier this way and the branch and tree
+ * class are intertwined heavily anyway.
+ */
+public:
+ /**
+ *
+ */
+ branch():tov_{Tree{}}{}
+
+ branch(Tree nd):tov_{std::move(nd)}{}
+
+ branch(T val):tov_{std::move(val)}{}
+
+ SAW_FORBID_COPY(branch);
+ SAW_DEFAULT_MOVE(branch);
+
+ template<typename NT>
+ bool is() const {
+ return std::holds_alternative<NT>(tov_);
+ }
+
+ bool is_tree() const {
+ return std::holds_alternative<Tree>(tov_);
+ }
+
+ bool is_value() const {
+ return std::holds_alternative<T>(tov_);
+ }
+
+ template<typename NT>
+ NT& get() {
+ return std::get<NT>(tov_);
+ }
+
+ template<typename NT>
+ const NT& get() const {
+ return std::get<NT>(tov_);
+ }
+
+ Tree& get_tree(){
+ return std::get<Tree>(tov_);
+ }
+
+ const Tree& get_tree() const {
+ return std::get<Tree>(tov_);
+ }
+
+ T& get_value(){
+ return std::get<T>(tov_);
+ }
+
+ const T& get_value() const {
+ return std::get<T>(tov_);
+ }
+
+ template<typename NT>
+ error_or<NT> extract(){
+ if(!is<NT>()){
+ return make_error<err::invalid_state>();
+ }
+
+ NT nd = std::move(std::get<NT>(tov_));
+ tov_ = Tree{};
+
+ return nd;
+ }
+
+ template<typename NT>
+ error_or<NT> replace(type nd){
+ auto eon = extract<NT>();
+ if(eon.is_error()){
+ return eon;
+ }
+
+ tov_ = std::move(nd);
+
+ return eon;
+ }
+
+ error_or<Tree> extract_tree() {
+ return extract<Tree>();
+ }
+
+ error_or<Tree> replace_tree(type nd){
+ return replace<Tree>(std::move(nd));
+ }
+
+ error_or<T> extract_value() {
+ return extract<T>();
+ }
+
+ error_or<T> replace_value(type nd){
+ return replace<T>(std::move(nd));
+ }
+};
+
+template<typename T>
+class tree {
+ private:
+ tree_container<T,tree<T>> data_;
+ public:
+ error_or<void> reserve(std::size_t size){
+ return data_.reserve(size);
+ }
+
+ size_t size() const {
+ return data_.size();
+ }
+
+ error_or<size_t> add() {
+ return data_.add();
+ }
+
+ error_or<size_t> add(T leaf){
+ return data_.add(std::move(leaf));
+ }
+
+ branch<T, tree<T>>& at(size_t i){
+ return data_.at(i);
+ }
+
+ const branch<T, tree<T>>& at(size_t i) const {
+ return data_.at(i);
+ }
+};
+}
diff --git a/modules/device-hip/.nix/derivation.nix b/modules/device-hip/.nix/derivation.nix
new file mode 100644
index 0000000..6849e6e
--- /dev/null
+++ b/modules/device-hip/.nix/derivation.nix
@@ -0,0 +1,30 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-io_codec";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.codec
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/device-hip/SConscript b/modules/device-hip/SConscript
new file mode 100644
index 0000000..4ab02d6
--- /dev/null
+++ b/modules/device-hip/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+dev_hip_env = env.Clone();
+
+dev_hip_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+dev_hip_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += dev_hip_env.sources;
+env.headers += dev_hip_env.headers;
+
+## Shared lib
+objects_shared = []
+dev_hip_env.add_source_files(objects_shared, dev_hip_env.sources, shared=True);
+dev_hip_env.library_shared = dev_hip_env.SharedLibrary('#build/forstio-device-hip', [objects_shared]);
+
+## Static lib
+objects_static = []
+dev_hip_env.add_source_files(objects_static, dev_hip_env.sources, shared=False);
+dev_hip_env.library_static = dev_hip_env.StaticLibrary('#build/forstio-device-hip', [objects_static]);
+
+# Set Alias
+env.Alias('library_device-hip', [dev_hip_env.library_shared, dev_hip_env.library_static]);
+
+env.targets += ['library_device-hip'];
+
+# Install
+env.Install('$prefix/lib/', [dev_hip_env.library_shared, dev_hip_env.library_static]);
+env.Install('$prefix/include/forstio/device/hip/', [dev_hip_env.headers]);
diff --git a/modules/device-hip/SConstruct b/modules/device-hip/SConstruct
new file mode 100644
index 0000000..4e6e150
--- /dev/null
+++ b/modules/device-hip/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-io'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/device-hip/rpc.h b/modules/device-hip/rpc.h
new file mode 100644
index 0000000..b6421ba
--- /dev/null
+++ b/modules/device-hip/rpc.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace saw {
+namespace rmt {
+struct DeviceHip {};
+}
+}
diff --git a/modules/io-fs/.nix/derivation.nix b/modules/io-fs/.nix/derivation.nix
new file mode 100644
index 0000000..c8a4962
--- /dev/null
+++ b/modules/io-fs/.nix/derivation.nix
@@ -0,0 +1,33 @@
+{ lib
+, stdenvNoCC
+, scons
+, clang
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenvNoCC.mkDerivation {
+ pname = "forstio-io-fs";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.codec
+ forstio.io
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/io-fs/SConscript b/modules/io-fs/SConscript
new file mode 100644
index 0000000..0d85f62
--- /dev/null
+++ b/modules/io-fs/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+io_env = env.Clone();
+
+io_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+io_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += io_env.sources;
+env.headers += io_env.headers;
+
+## Shared lib
+objects_shared = []
+io_env.add_source_files(objects_shared, io_env.sources, shared=True);
+io_env.library_shared = io_env.SharedLibrary('#build/forstio-io-fs', [objects_shared]);
+
+## Static lib
+objects_static = []
+io_env.add_source_files(objects_static, io_env.sources, shared=False);
+io_env.library_static = io_env.StaticLibrary('#build/forstio-io-fs', [objects_static]);
+
+# Set Alias
+env.Alias('library_io', [io_env.library_shared, io_env.library_static]);
+
+env.targets += ['library_io'];
+
+# Install
+env.Install('$prefix/lib/', [io_env.library_shared, io_env.library_static]);
+env.Install('$prefix/include/forstio/io/fs/', [io_env.headers]);
diff --git a/modules/io-fs/SConstruct b/modules/io-fs/SConstruct
new file mode 100644
index 0000000..4cccf82
--- /dev/null
+++ b/modules/io-fs/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-async'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/io-tls/.nix/derivation.nix b/modules/io-tls/.nix/derivation.nix
new file mode 100644
index 0000000..23d8ba6
--- /dev/null
+++ b/modules/io-tls/.nix/derivation.nix
@@ -0,0 +1,32 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+, gnutls
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-io-tls";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ gnutls
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/io-tls/SConscript b/modules/io-tls/SConscript
new file mode 100644
index 0000000..4f88f37
--- /dev/null
+++ b/modules/io-tls/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+io_tls_env = env.Clone();
+
+io_tls_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+io_tls_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += io_tls_env.sources;
+env.headers += io_tls_env.headers;
+
+## Shared lib
+objects_shared = []
+io_tls_env.add_source_files(objects_shared, io_tls_env.sources, shared=True);
+io_tls_env.library_shared = io_tls_env.SharedLibrary('#build/forstio-io-tls', [objects_shared]);
+
+## Static lib
+objects_static = []
+io_tls_env.add_source_files(objects_static, io_tls_env.sources, shared=False);
+io_tls_env.library_static = io_tls_env.StaticLibrary('#build/forstio-io-tls', [objects_static]);
+
+# Set Alias
+env.Alias('library_io_tls', [io_tls_env.library_shared, io_tls_env.library_static]);
+
+env.targets += ['library_io_tls'];
+
+# Install
+env.Install('$prefix/lib/', [io_tls_env.library_shared, io_tls_env.library_static]);
+env.Install('$prefix/include/forstio/io/tls/', [io_tls_env.headers]);
diff --git a/modules/io-tls/SConstruct b/modules/io-tls/SConstruct
new file mode 100644
index 0000000..fbd8657
--- /dev/null
+++ b/modules/io-tls/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['gnutls','forstio-io'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/io-tls/tls.cpp b/modules/io-tls/tls.cpp
new file mode 100644
index 0000000..9fa143c
--- /dev/null
+++ b/modules/io-tls/tls.cpp
@@ -0,0 +1,252 @@
+#include "tls.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#include <forstio/io/io_helpers.h>
+
+#include <cassert>
+
+#include <iostream>
+
+namespace saw {
+
+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 forst_tls_push_func(gnutls_transport_ptr_t p, const void *data,
+ size_t size);
+static ssize_t forst_tls_pull_func(gnutls_transport_ptr_t p, void *data, size_t size);
+
+tls::tls() : impl_{heap<tls::impl>()} {}
+
+tls::~tls() {}
+
+tls::impl &tls::get_impl() { return *impl_; }
+
+class tls_io_stream final : public io_stream {
+private:
+ own<io_stream> internal;
+ gnutls_session_t session_handle;
+
+public:
+ tls_io_stream(own<io_stream> internal_) : internal{std::move(internal_)} {}
+
+ ~tls_io_stream() { gnutls_bye(session_handle, GNUTLS_SHUT_RDWR); }
+
+ error_or<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 make_error<err::recoverable>("Recoverable error on read in gnutls. TODO better error msg handling");
+ // Leaving proper message handling done in previous error framework
+ //return recoverable_error([size](){return std::string{"Read recoverable Error "}+std::string{gnutls_strerror(size)};}, "Error read r");
+ }else{
+ return make_error<err::critical>("Fatal error on read in gnutls. TODO better error msg handling");
+ }
+ }else if(size == 0){
+ return make_error<err::disconnected>();
+ }
+
+ return static_cast<size_t>(length);
+ }
+
+ conveyor<void> read_ready() override { return internal->read_ready(); }
+
+ conveyor<void> on_read_disconnected() override {
+ return internal->on_read_disconnected();
+ }
+
+ error_or<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 make_error<err::recoverable>("Recoverable error on write in gnutls. TODO better error msg handling");
+ }else{
+ return make_error<err::critical>("Fatal error on write in gnutls. TODO better error msg handling");
+ }
+ }
+
+ return static_cast<size_t>(size);
+ }
+
+ conveyor<void> write_ready() override { return internal->write_ready(); }
+
+ gnutls_session_t &session() { return session_handle; }
+};
+
+tls_server::tls_server(own<server> srv) : internal{std::move(srv)} {}
+
+conveyor<own<io_stream>> tls_server::accept() {
+ SAW_ASSERT(internal) { return conveyor<own<io_stream>>{fix_void<own<io_stream>>{nullptr}}; }
+ return internal->accept().then([](own<io_stream> stream) -> own<io_stream> {
+ /// @todo handshake
+
+
+ return heap<tls_io_stream>(std::move(stream));
+ });
+}
+
+namespace {
+/*
+* Small helper for setting up the nonblocking connection handshake
+*/
+struct tls_client_stream_helper {
+public:
+ own<conveyor_feeder<own<io_stream>>> feeder;
+ conveyor_sink connection_sink;
+ conveyor_sink stream_reader;
+ conveyor_sink stream_writer;
+
+ own<tls_io_stream> stream = nullptr;
+public:
+ tls_client_stream_helper(own<conveyor_feeder<own<io_stream>>> f):
+ feeder{std::move(f)}
+ {}
+
+ void setupTurn(){
+ SAW_ASSERT(stream){
+ return;
+ }
+
+ stream_reader = stream->read_ready().then([this](){
+ turn();
+ }).sink();
+
+ stream_writer = stream->write_ready().then([this](){
+ turn();
+ }).sink();
+ }
+
+ void turn(){
+ if(stream){
+ // Guarantee that the receiving end is already setup
+ SAW_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(make_error<err::critical>("Couldn't create Tls connection"));
+ stream = nullptr;
+ }else if(ret == GNUTLS_E_SUCCESS){
+ feeder->feed(std::move(stream));
+ }
+ }
+ }
+};
+}
+
+own<server> tls_network::listen(network_address& address) {
+ return heap<tls_server>(internal.listen(address));
+}
+
+conveyor<own<io_stream>> tls_network::connect(network_address& address) {
+ // Helper setups
+ auto caf = new_conveyor_and_feeder<own<io_stream>>();
+ own<tls_client_stream_helper> helper = heap<tls_client_stream_helper>(std::move(caf.feeder));
+ tls_client_stream_helper* hlp_ptr = helper.get();
+
+ // Conveyor entangled structure
+ auto prim_conv = internal.connect(address).then([this, hlp_ptr, addr = address.address()](
+ own<io_stream> stream) -> error_or<void> {
+ io_stream* inner_stream = stream.get();
+ auto tls_stream = heap<tls_io_stream>(std::move(stream));
+
+ auto &session = tls_stream->session();
+
+ gnutls_init(&session, GNUTLS_CLIENT);
+
+ 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_.get_impl().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, forst_tls_push_func);
+ gnutls_transport_set_pull_function(session, forst_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_t{};
+ });
+
+ helper->connection_sink = prim_conv.sink();
+
+ return caf.conveyor.attach(std::move(helper));
+}
+
+own<datagram> tls_network::datagram(network_address& address){
+ ///@unimplemented
+ return nullptr;
+}
+
+static ssize_t forst_tls_push_func(gnutls_transport_ptr_t p, const void *data,
+ size_t size) {
+ io_stream *stream = reinterpret_cast<io_stream *>(p);
+ if (!stream) {
+ return -1;
+ }
+
+ error_or<size_t> length = stream->write(data, size);
+ if (length.is_error() || !length.is_value()) {
+ return -1;
+ }
+
+ return static_cast<ssize_t>(length.get_value());
+}
+
+static ssize_t forst_tls_pull_func(gnutls_transport_ptr_t p, void *data, size_t size) {
+ io_stream *stream = reinterpret_cast<io_stream *>(p);
+ if (!stream) {
+ return -1;
+ }
+
+ error_or<size_t> length = stream->read(data, size);
+ if (length.is_error() || !length.is_value()) {
+ return -1;
+ }
+
+ return static_cast<ssize_t>(length.get_value());
+}
+
+tls_network::tls_network(tls& tls_, network &network) : tls_{tls_},internal{network} {}
+
+conveyor<own<network_address>> tls_network::resolve_address(const std::string &addr,
+ uint16_t port) {
+ /// @todo tls server name needed. Check validity. Won't matter later on, because gnutls should fail anyway. But
+ /// it's better to find the error source sooner rather than later
+ return internal.resolve_address(addr, port);
+}
+
+std::optional<own<tls_network>> setup_tls_network(network &network) {
+ return std::nullopt;
+}
+} // namespace saw
diff --git a/modules/io-tls/tls.h b/modules/io-tls/tls.h
new file mode 100644
index 0000000..74b39ff
--- /dev/null
+++ b/modules/io-tls/tls.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <forstio/core/common.h>
+#include <forstio/io/io.h>
+
+#include <optional>
+#include <variant>
+
+namespace saw {
+class tls;
+
+class tls_server final : public server {
+private:
+ own<server> internal;
+
+public:
+ tls_server(own<server> srv);
+
+ conveyor<own<io_stream>> accept() override;
+};
+
+class tls_network final : public network {
+private:
+ tls& tls_;
+ network &internal;
+public:
+ tls_network(tls& tls_, network &network_);
+
+ conveyor<own<network_address>> resolve_address(const std::string &addr, uint16_t port = 0) override;
+
+ own<server> listen(network_address& address) override;
+
+ conveyor<own<io_stream>> connect(network_address& address) override;
+
+ own<class datagram> datagram(network_address& address) override;
+};
+
+/**
+* tls context class.
+* Provides tls network class which ensures the usage of tls encrypted connections
+*/
+class tls {
+private:
+ class impl;
+ own<impl> impl_;
+public:
+ tls();
+ ~tls();
+
+ struct version {
+ struct tls_1_0{};
+ struct tls_1_1{};
+ struct tls_1_2{};
+ };
+
+ struct options {
+ public:
+ version version;
+ };
+
+ impl &get_impl();
+private:
+ options options_;
+};
+
+std::optional<own<tls_network>> setup_tls_network(network &network);
+
+} // namespace saw
diff --git a/modules/io/.nix/derivation.nix b/modules/io/.nix/derivation.nix
new file mode 100644
index 0000000..a14bd34
--- /dev/null
+++ b/modules/io/.nix/derivation.nix
@@ -0,0 +1,29 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-io";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/io/SConscript b/modules/io/SConscript
new file mode 100644
index 0000000..62ad58a
--- /dev/null
+++ b/modules/io/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+io_env = env.Clone();
+
+io_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+io_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += io_env.sources;
+env.headers += io_env.headers;
+
+## Shared lib
+objects_shared = []
+io_env.add_source_files(objects_shared, io_env.sources, shared=True);
+io_env.library_shared = io_env.SharedLibrary('#build/forstio-io', [objects_shared]);
+
+## Static lib
+objects_static = []
+io_env.add_source_files(objects_static, io_env.sources, shared=False);
+io_env.library_static = io_env.StaticLibrary('#build/forstio-io', [objects_static]);
+
+# Set Alias
+env.Alias('library_io', [io_env.library_shared, io_env.library_static]);
+
+env.targets += ['library_io'];
+
+# Install
+env.Install('$prefix/lib/', [io_env.library_shared, io_env.library_static]);
+env.Install('$prefix/include/forstio/io/', [io_env.headers]);
diff --git a/modules/io/SConstruct b/modules/io/SConstruct
new file mode 100644
index 0000000..4cccf82
--- /dev/null
+++ b/modules/io/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-async'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/io/io.cpp b/modules/io/io.cpp
new file mode 100644
index 0000000..f0705d2
--- /dev/null
+++ b/modules/io/io.cpp
@@ -0,0 +1,70 @@
+#include "io.h"
+
+#include <cassert>
+
+namespace saw {
+
+async_io_stream::async_io_stream(own<io_stream> str)
+ : stream_{std::move(str)},
+ read_ready_{stream_->read_ready()
+ .then([this]() { read_stepper_.read_step(*stream_); })
+ .sink()},
+ write_ready_{stream_->write_ready()
+ .then([this]() { write_stepper_.write_step(*stream_); })
+ .sink()},
+ read_disconnected_{stream_->on_read_disconnected()
+ .then([this]() {
+ if (read_stepper_.on_read_disconnect) {
+ read_stepper_.on_read_disconnect->feed();
+ }
+ })
+ .sink()} {}
+
+void async_io_stream::read(void *buffer, size_t min_length, size_t max_length) {
+ SAW_ASSERT(buffer && max_length >= min_length && min_length > 0) { return; }
+
+ SAW_ASSERT(!read_stepper_.read_task.has_value()) { return; }
+
+ read_stepper_.read_task = read_task_and_step_helper::read_io_task{
+ buffer, min_length, max_length, 0};
+ read_stepper_.read_step(*stream_);
+}
+
+conveyor<size_t> async_io_stream::read_done() {
+ auto caf = new_conveyor_and_feeder<size_t>();
+ read_stepper_.read_done = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+conveyor<void> async_io_stream::on_read_disconnected() {
+ auto caf = new_conveyor_and_feeder<void>();
+ read_stepper_.on_read_disconnect = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void async_io_stream::write(const void *buffer, size_t length) {
+ SAW_ASSERT(buffer && length > 0) { return; }
+
+ SAW_ASSERT(!write_stepper_.write_task.has_value()) { return; }
+
+ write_stepper_.write_task =
+ write_task_and_step_helper::write_io_task{buffer, length, 0};
+ write_stepper_.write_step(*stream_);
+}
+
+conveyor<size_t> async_io_stream::write_done() {
+ auto caf = new_conveyor_and_feeder<size_t>();
+ write_stepper_.write_done = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+string_network_address::string_network_address(const std::string &address,
+ uint16_t port)
+ : address_value_{address}, port_value_{port} {}
+
+const std::string &string_network_address::address() const {
+ return address_value_;
+}
+
+uint16_t string_network_address::port() const { return port_value_; }
+} // namespace saw
diff --git a/modules/io/io.h b/modules/io/io.h
new file mode 100644
index 0000000..7653ace
--- /dev/null
+++ b/modules/io/io.h
@@ -0,0 +1,219 @@
+#pragma once
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+#include "io_helpers.h"
+
+#include <string>
+#include <variant>
+
+namespace saw {
+/**
+ * Set of error common in io
+ */
+namespace err {
+struct disconnected {
+ static constexpr std::string_view description = "Disconnected";
+ static constexpr bool is_critical = true;
+};
+
+struct resource_busy {
+ static constexpr std::string_view description = "Resource busy";
+ static constexpr bool is_critical = false;
+};
+}
+/*
+ * Input stream
+ */
+class input_stream {
+public:
+ virtual ~input_stream() = default;
+
+ virtual error_or<size_t> read(void *buffer, size_t length) = 0;
+
+ virtual conveyor<void> read_ready() = 0;
+
+ virtual conveyor<void> on_read_disconnected() = 0;
+};
+
+/*
+ * Output stream
+ */
+class output_stream {
+public:
+ virtual ~output_stream() = default;
+
+ virtual error_or<size_t> write(const void *buffer, size_t length) = 0;
+
+ virtual conveyor<void> write_ready() = 0;
+};
+
+/*
+ * Io stream
+ */
+class io_stream : public input_stream, public output_stream {
+public:
+ virtual ~io_stream() = default;
+};
+
+class async_input_stream {
+public:
+ virtual ~async_input_stream() = default;
+
+ virtual void read(void *buffer, size_t min_length, size_t max_length) = 0;
+
+ virtual conveyor<size_t> read_done() = 0;
+ virtual conveyor<void> on_read_disconnected() = 0;
+};
+
+class async_output_stream {
+public:
+ virtual ~async_output_stream() = default;
+
+ virtual void write(const void *buffer, size_t length) = 0;
+
+ virtual conveyor<size_t> write_done() = 0;
+};
+
+class async_io_stream final : public async_input_stream,
+ public async_output_stream {
+private:
+ own<io_stream> stream_;
+
+ conveyor_sink read_ready_;
+ conveyor_sink write_ready_;
+ conveyor_sink read_disconnected_;
+
+ read_task_and_step_helper read_stepper_;
+ write_task_and_step_helper write_stepper_;
+
+public:
+ async_io_stream(own<io_stream> str);
+
+ SAW_FORBID_COPY(async_io_stream);
+ SAW_FORBID_MOVE(async_io_stream);
+
+ void read(void *buffer, size_t length, size_t max_length) override;
+
+ conveyor<size_t> read_done() override;
+
+ conveyor<void> on_read_disconnected() override;
+
+ void write(const void *buffer, size_t length) override;
+
+ conveyor<size_t> write_done() override;
+};
+
+class server {
+public:
+ virtual ~server() = default;
+
+ virtual conveyor<own<io_stream>> accept() = 0;
+};
+
+class network_address;
+/**
+ * Datagram class. Bound to a local address it is able to receive inbound
+ * datagram messages and send them as well as long as an address is provided as
+ * well
+ */
+class datagram {
+public:
+ virtual ~datagram() = default;
+
+ virtual error_or<size_t> read(void *buffer, size_t length) = 0;
+ virtual conveyor<void> read_ready() = 0;
+
+ virtual error_or<size_t> write(const void *buffer, size_t length,
+ network_address &dest) = 0;
+ virtual conveyor<void> write_ready() = 0;
+};
+
+class os_network_address;
+class string_network_address;
+
+class network_address {
+public:
+ using child_variant =
+ std::variant<os_network_address *, string_network_address *>;
+
+ virtual ~network_address() = default;
+
+ virtual network_address::child_variant representation() = 0;
+
+ virtual const std::string &address() const = 0;
+ virtual uint16_t port() const = 0;
+};
+
+class os_network_address : public network_address {
+public:
+ virtual ~os_network_address() = default;
+
+ network_address::child_variant representation() override { return this; }
+};
+
+class string_network_address final : public network_address {
+private:
+ std::string address_value_;
+ uint16_t port_value_;
+
+public:
+ string_network_address(const std::string &address, uint16_t port);
+
+ const std::string &address() const override;
+ uint16_t port() const override;
+
+ network_address::child_variant representation() override { return this; }
+};
+
+class network {
+public:
+ virtual ~network() = default;
+
+ /**
+ * Resolve the provided string and uint16 to the preferred storage method
+ */
+ virtual conveyor<own<network_address>>
+ resolve_address(const std::string &addr, uint16_t port_hint = 0) = 0;
+
+ /**
+ * Parse the provided string and uint16 to the preferred storage method
+ * Since no dns request is made here, no async conveyors have to be used.
+ */
+ /// @todo implement
+ // virtual Own<NetworkAddress> parseAddress(const std::string& addr,
+ // uint16_t port_hint = 0) = 0;
+
+ /**
+ * Set up a listener on this address
+ */
+ virtual own<server> listen(network_address &bind_addr) = 0;
+
+ /**
+ * Connect to a remote address
+ */
+ virtual conveyor<own<io_stream>> connect(network_address &address) = 0;
+
+ /**
+ * Bind a datagram socket at this address.
+ */
+ virtual own<datagram> datagram(network_address &address) = 0;
+};
+
+class io_provider {
+public:
+ virtual ~io_provider() = default;
+
+ virtual own<input_stream> wrap_input_fd(int fd) = 0;
+
+ virtual network &get_network() = 0;
+};
+
+struct async_io_context {
+ own<io_provider> io;
+ event_loop &event_loop;
+ event_port &event_port;
+};
+
+error_or<async_io_context> setup_async_io();
+} // namespace saw
diff --git a/modules/io/io_helpers.cpp b/modules/io/io_helpers.cpp
new file mode 100644
index 0000000..c2cf2be
--- /dev/null
+++ b/modules/io/io_helpers.cpp
@@ -0,0 +1,85 @@
+#include "io_helpers.h"
+
+#include "io.h"
+
+#include <cassert>
+
+namespace saw {
+void read_task_and_step_helper::read_step(input_stream &reader) {
+ while (read_task.has_value()) {
+ read_io_task &task = *read_task;
+
+ error_or<size_t> n_err = reader.read(task.buffer, task.max_length);
+ if (n_err.is_error()) {
+ const error &error = n_err.get_error();
+ if (error.is_critical()) {
+ if (read_done) {
+ read_done->fail(error.copy_error());
+ }
+ read_task = std::nullopt;
+ }
+
+ break;
+ } else if (n_err.is_value()) {
+ size_t n = n_err.get_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(make_error<err::invalid_state>("Read failed"));
+ }
+ read_task = std::nullopt;
+ }
+ }
+}
+
+void write_task_and_step_helper::write_step(output_stream &writer) {
+ while (write_task.has_value()) {
+ write_io_task &task = *write_task;
+
+ error_or<size_t> n_err = writer.write(task.buffer, task.length);
+
+ if (n_err.is_value()) {
+
+ size_t n = n_err.get_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.is_error()) {
+ const error &error = n_err.get_error();
+ if (error.is_critical()) {
+ if (write_done) {
+ write_done->fail(error.copy_error());
+ }
+ write_task = std::nullopt;
+ }
+ break;
+ } else {
+ if (write_done) {
+ write_done->fail(make_error<err::invalid_state>("Write failed"));
+ }
+ write_task = std::nullopt;
+ }
+ }
+}
+
+} // namespace saw
diff --git a/modules/io/io_helpers.h b/modules/io/io_helpers.h
new file mode 100644
index 0000000..94e37f4
--- /dev/null
+++ b/modules/io/io_helpers.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+
+#include <cstdint>
+#include <optional>
+
+namespace saw {
+/*
+ * 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 input_stream;
+
+class read_task_and_step_helper {
+public:
+ struct read_io_task {
+ void *buffer;
+ size_t min_length;
+ size_t max_length;
+ size_t already_read = 0;
+ };
+ std::optional<read_io_task> read_task;
+ own<conveyor_feeder<size_t>> read_done = nullptr;
+
+ own<conveyor_feeder<void>> on_read_disconnect = nullptr;
+
+public:
+ void read_step(input_stream &reader);
+};
+
+class output_stream;
+
+class write_task_and_step_helper {
+public:
+ struct write_io_task {
+ const void *buffer;
+ size_t length;
+ size_t already_written = 0;
+ };
+ std::optional<write_io_task> write_task;
+ own<conveyor_feeder<size_t>> write_done = nullptr;
+
+public:
+ void write_step(output_stream &writer);
+};
+} // namespace saw
diff --git a/modules/io/io_unix.cpp b/modules/io/io_unix.cpp
new file mode 100644
index 0000000..c3b4f17
--- /dev/null
+++ b/modules/io/io_unix.cpp
@@ -0,0 +1,894 @@
+#ifdef SAW_UNIX
+
+#include <csignal>
+#include <sys/signalfd.h>
+
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <cassert>
+#include <cstring>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <queue>
+#include <sstream>
+#include <unordered_map>
+#include <vector>
+
+#include "io.h"
+
+namespace saw {
+namespace unix {
+constexpr int MAX_EPOLL_EVENTS = 256;
+
+class unix_event_port;
+class i_fd_owner {
+protected:
+ unix_event_port &event_port_;
+
+private:
+ int file_descriptor_;
+ int fd_flags_;
+ uint32_t event_mask_;
+
+public:
+ i_fd_owner(unix_event_port &event_port, int file_descriptor, int fd_flags,
+ uint32_t event_mask);
+
+ virtual ~i_fd_owner();
+
+ virtual void notify(uint32_t mask) = 0;
+
+ int fd() const { return file_descriptor_; }
+};
+
+class unix_event_port final : public event_port {
+private:
+ int epoll_fd_;
+ int signal_fd_;
+
+ sigset_t signal_fd_set_;
+
+ std::unordered_multimap<Signal, own<conveyor_feeder<void>>>
+ signal_conveyors_;
+
+ int pipefds_[2];
+
+ std::vector<int> to_unix_signal(Signal signal) const {
+ switch (signal) {
+ case Signal::User1:
+ return {SIGUSR1};
+ case Signal::Terminate:
+ default:
+ return {SIGTERM, SIGQUIT, SIGINT};
+ }
+ }
+
+ Signal from_unix_signal(int signal) const {
+ switch (signal) {
+ case SIGUSR1:
+ return Signal::User1;
+ case SIGTERM:
+ case SIGINT:
+ case SIGQUIT:
+ default:
+ return Signal::Terminate;
+ }
+ }
+
+ void notify_signal_listener(int sig) {
+ Signal signal = from_unix_signal(sig);
+
+ auto equal_range = signal_conveyors_.equal_range(signal);
+ for (auto iter = equal_range.first; iter != equal_range.second;
+ ++iter) {
+
+ if (iter->second) {
+ if (iter->second->space() > 0) {
+ iter->second->feed();
+ }
+ }
+ }
+ }
+
+ bool poll_impl(int time) {
+ epoll_event events[MAX_EPOLL_EVENTS];
+ int nfds = 0;
+ do {
+ nfds = epoll_wait(epoll_fd_, events, MAX_EPOLL_EVENTS, time);
+
+ if (nfds < 0) {
+ /// @todo error_handling
+ return false;
+ }
+
+ for (int i = 0; i < nfds; ++i) {
+ if (events[i].data.u64 == 0) {
+ while (1) {
+ struct ::signalfd_siginfo siginfo;
+ ssize_t n =
+ ::read(signal_fd_, &siginfo, sizeof(siginfo));
+ if (n < 0) {
+ break;
+ }
+ assert(n == sizeof(siginfo));
+
+ notify_signal_listener(siginfo.ssi_signo);
+ }
+ } else if (events[i].data.u64 == 1) {
+ uint8_t i;
+ if (pipefds_[0] < 0) {
+ continue;
+ }
+ while (1) {
+ ssize_t n = ::recv(pipefds_[0], &i, sizeof(i), 0);
+ if (n < 0) {
+ break;
+ }
+ }
+ } else {
+ i_fd_owner *owner =
+ reinterpret_cast<i_fd_owner *>(events[i].data.ptr);
+ if (owner) {
+ owner->notify(events[i].events);
+ }
+ }
+ }
+ } while (nfds == MAX_EPOLL_EVENTS);
+
+ return true;
+ }
+
+public:
+ unix_event_port() : epoll_fd_{-1}, signal_fd_{-1} {
+ ::signal(SIGPIPE, SIG_IGN);
+
+ epoll_fd_ = ::epoll_create1(EPOLL_CLOEXEC);
+ if (epoll_fd_ < 0) {
+ return;
+ }
+
+ ::sigemptyset(&signal_fd_set_);
+ signal_fd_ =
+ ::signalfd(-1, &signal_fd_set_, SFD_NONBLOCK | SFD_CLOEXEC);
+ if (signal_fd_ < 0) {
+ return;
+ }
+
+ struct epoll_event event;
+ memset(&event, 0, sizeof(event));
+ event.events = EPOLLIN;
+ event.data.u64 = 0;
+ ::epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, signal_fd_, &event);
+
+ int rc = ::pipe2(pipefds_, O_NONBLOCK | O_CLOEXEC);
+ if (rc < 0) {
+ return;
+ }
+ memset(&event, 0, sizeof(event));
+ event.events = EPOLLIN;
+ event.data.u64 = 1;
+ ::epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, pipefds_[0], &event);
+ }
+
+ ~unix_event_port() {
+ ::close(epoll_fd_);
+ ::close(signal_fd_);
+ ::close(pipefds_[0]);
+ ::close(pipefds_[1]);
+ }
+
+ conveyor<void> on_signal(Signal signal) override {
+ auto caf = new_conveyor_and_feeder<void>();
+
+ signal_conveyors_.insert(std::make_pair(signal, std::move(caf.feeder)));
+
+ std::vector<int> sig = to_unix_signal(signal);
+
+ for (auto iter = sig.begin(); iter != sig.end(); ++iter) {
+ ::sigaddset(&signal_fd_set_, *iter);
+ }
+ ::sigprocmask(SIG_BLOCK, &signal_fd_set_, nullptr);
+ ::signalfd(signal_fd_, &signal_fd_set_, SFD_NONBLOCK | SFD_CLOEXEC);
+
+ auto node = conveyor<void>::from_conveyor(std::move(caf.conveyor));
+ return conveyor<void>::to_conveyor(std::move(node));
+ }
+
+ void poll() override { poll_impl(0); }
+
+ void wait() override { poll_impl(-1); }
+
+ void wait(const std::chrono::steady_clock::duration &duration) override {
+ poll_impl(
+ std::chrono::duration_cast<std::chrono::milliseconds>(duration)
+ .count());
+ }
+ void
+ wait(const std::chrono::steady_clock::time_point &time_point) override {
+ auto now = std::chrono::steady_clock::now();
+ if (time_point <= now) {
+ poll();
+ } else {
+ poll_impl(std::chrono::duration_cast<std::chrono::milliseconds>(
+ time_point - now)
+ .count());
+ }
+ }
+
+ void wake() override {
+ /// @todo pipe() in the beginning and write something minor into it like
+ /// uint8_t or sth the value itself doesn't matter
+ if (pipefds_[1] < 0) {
+ return;
+ }
+ uint8_t i = 0;
+ ::send(pipefds_[1], &i, sizeof(i), MSG_DONTWAIT);
+ }
+
+ void subscribe(i_fd_owner &owner, int fd, uint32_t event_mask) {
+ if (epoll_fd_ < 0 || fd < 0) {
+ return;
+ }
+ ::epoll_event event;
+ memset(&event, 0, sizeof(event));
+ event.events = event_mask | EPOLLET;
+ event.data.ptr = &owner;
+
+ if (::epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event) < 0) {
+ /// @todo error_handling
+ return;
+ }
+ }
+
+ void unsubscribe(int fd) {
+ if (epoll_fd_ < 0 || fd < 0) {
+ return;
+ }
+ if (::epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr) < 0) {
+ /// @todo error_handling
+ return;
+ }
+ }
+};
+
+ssize_t unix_read(int fd, void *buffer, size_t length);
+ssize_t unix_write(int fd, const void *buffer, size_t length);
+
+class unix_io_stream final : public io_stream, public i_fd_owner {
+private:
+ own<conveyor_feeder<void>> read_ready_ = nullptr;
+ own<conveyor_feeder<void>> on_read_disconnect_ = nullptr;
+ own<conveyor_feeder<void>> write_ready_ = nullptr;
+
+public:
+ unix_io_stream(unix_event_port &event_port, int file_descriptor,
+ int fd_flags, uint32_t event_mask);
+
+ error_or<size_t> read(void *buffer, size_t length) override;
+
+ conveyor<void> read_ready() override;
+
+ conveyor<void> on_read_disconnected() override;
+
+ error_or<size_t> write(const void *buffer, size_t length) override;
+
+ conveyor<void> write_ready() 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;
+};
+
+class unix_server final : public server, public i_fd_owner {
+private:
+ own<conveyor_feeder<own<io_stream>>> accept_feeder_ = nullptr;
+
+public:
+ unix_server(unix_event_port &event_port, int file_descriptor, int fd_flags);
+
+ conveyor<own<io_stream>> accept() override;
+
+ void notify(uint32_t mask) override;
+};
+
+class unix_datagram final : public datagram, public i_fd_owner {
+private:
+ own<conveyor_feeder<void>> read_ready_ = nullptr;
+ own<conveyor_feeder<void>> write_ready_ = nullptr;
+
+public:
+ unix_datagram(unix_event_port &event_port, int file_descriptor,
+ int fd_flags);
+
+ error_or<size_t> read(void *buffer, size_t length) override;
+ conveyor<void> read_ready() override;
+
+ error_or<size_t> write(const void *buffer, size_t length,
+ network_address &dest) override;
+ conveyor<void> write_ready() override;
+
+ void notify(uint32_t mask) override;
+};
+
+/**
+ * Helper class which provides potential addresses to NetworkAddress
+ */
+class socket_address {
+private:
+ union {
+ struct sockaddr generic;
+ struct sockaddr_un unix;
+ struct sockaddr_in inet;
+ struct sockaddr_in6 inet6;
+ struct sockaddr_storage storage;
+ } address_;
+
+ socklen_t address_length_;
+ bool wildcard_;
+
+ socket_address() : wildcard_{false} {}
+
+public:
+ socket_address(const void *sockaddr, socklen_t len, bool wildcard)
+ : address_length_{len}, wildcard_{wildcard} {
+ assert(len <= sizeof(address_));
+ memcpy(&address_.generic, sockaddr, len);
+ }
+
+ int socket(int type) const {
+ type |= SOCK_NONBLOCK | SOCK_CLOEXEC;
+
+ int result = ::socket(address_.generic.sa_family, type, 0);
+ return result;
+ }
+
+ bool bind(int fd) const {
+ if (wildcard_) {
+ int value = 0;
+ ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
+ }
+ int error = ::bind(fd, &address_.generic, address_length_);
+ return error < 0;
+ }
+
+ struct ::sockaddr *get_raw() {
+ return &address_.generic;
+ }
+
+ const struct ::sockaddr *get_raw() const { return &address_.generic; }
+
+ socklen_t get_raw_length() const { return address_length_; }
+
+ static std::vector<socket_address> resolve(std::string_view str,
+ uint16_t port_hint) {
+ std::vector<socket_address> results;
+
+ struct ::addrinfo *head;
+ struct ::addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+
+ std::string port_string = std::to_string(port_hint);
+ bool wildcard = str == "*" || str == "::";
+ std::string address_string{str};
+
+ int error = ::getaddrinfo(address_string.c_str(), port_string.c_str(),
+ &hints, &head);
+
+ if (error) {
+ return {};
+ }
+
+ for (struct ::addrinfo *it = head; it != nullptr; it = it->ai_next) {
+ if (it->ai_addrlen > sizeof(socket_address::address_)) {
+ continue;
+ }
+ results.push_back({it->ai_addr, it->ai_addrlen, wildcard});
+ }
+ ::freeaddrinfo(head);
+ return results;
+ }
+};
+
+class unix_network_address final : public os_network_address {
+private:
+ const std::string path_;
+ uint16_t port_hint_;
+ std::vector<socket_address> addresses_;
+
+public:
+ unix_network_address(const std::string &path, uint16_t port_hint,
+ std::vector<socket_address> &&addr)
+ : path_{path}, port_hint_{port_hint}, addresses_{std::move(addr)} {}
+
+ const std::string &address() const override;
+
+ uint16_t port() const override;
+
+ // Custom address info
+ socket_address &unix_address(size_t i = 0);
+ size_t unix_address_size() const;
+};
+
+class unix_network final : public network {
+private:
+ unix_event_port &event_port_;
+
+public:
+ unix_network(unix_event_port &event_port);
+
+ conveyor<own<network_address>>
+ resolve_address(const std::string &address,
+ uint16_t port_hint = 0) override;
+
+ own<server> listen(network_address &addr) override;
+
+ conveyor<own<io_stream>> connect(network_address &addr) override;
+
+ own<class datagram> datagram(network_address &addr) override;
+};
+
+class unix_io_provider final : public io_provider {
+private:
+ unix_event_port &event_port_;
+ class event_loop event_loop_;
+
+ unix_network unix_network_;
+
+public:
+ unix_io_provider(unix_event_port &port_ref, own<event_port> port);
+
+ class network &get_network() override;
+
+ own<input_stream> wrap_input_fd(int fd) override;
+
+ class event_loop &event_loop();
+};
+
+i_fd_owner::i_fd_owner(unix_event_port &event_port, int file_descriptor,
+ int fd_flags, uint32_t event_mask)
+ : event_port_{event_port}, file_descriptor_{file_descriptor},
+ fd_flags_{fd_flags}, event_mask_{event_mask} {
+ event_port_.subscribe(*this, file_descriptor, event_mask);
+}
+
+i_fd_owner::~i_fd_owner() {
+ if (file_descriptor_ >= 0) {
+ event_port_.unsubscribe(file_descriptor_);
+ ::close(file_descriptor_);
+ }
+}
+
+ssize_t unix_read(int fd, void *buffer, size_t length) {
+ return ::recv(fd, buffer, length, 0);
+}
+
+ssize_t unix_write(int fd, const void *buffer, size_t length) {
+ return ::send(fd, buffer, length, 0);
+}
+
+unix_io_stream::unix_io_stream(unix_event_port &event_port, int file_descriptor,
+ int fd_flags, uint32_t event_mask)
+ : i_fd_owner{event_port, file_descriptor, fd_flags,
+ event_mask | EPOLLRDHUP} {}
+
+error_or<size_t> unix_io_stream::read(void *buffer, size_t length) {
+ ssize_t read_bytes = unix_read(fd(), buffer, length);
+ if (read_bytes > 0) {
+ return static_cast<size_t>(read_bytes);
+ } else if (read_bytes == 0) {
+ return make_error<err::disconnected>();
+ }
+
+ return make_error<err::resource_busy>();
+}
+
+conveyor<void> unix_io_stream::read_ready() {
+ auto caf = new_conveyor_and_feeder<void>();
+ read_ready_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+conveyor<void> unix_io_stream::on_read_disconnected() {
+ auto caf = new_conveyor_and_feeder<void>();
+ on_read_disconnect_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+error_or<size_t> unix_io_stream::write(const void *buffer, size_t length) {
+ ssize_t write_bytes = unix_write(fd(), buffer, length);
+ if (write_bytes > 0) {
+ return static_cast<size_t>(write_bytes);
+ }
+
+ int error = errno;
+
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return make_error<err::resource_busy>();
+ }
+
+ return make_error<err::disconnected>();
+}
+
+conveyor<void> unix_io_stream::write_ready() {
+ auto caf = new_conveyor_and_feeder<void>();
+ write_ready_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void unix_io_stream::notify(uint32_t mask) {
+ if (mask & EPOLLOUT) {
+ if (write_ready_) {
+ write_ready_->feed();
+ }
+ }
+
+ if (mask & EPOLLIN) {
+ if (read_ready_) {
+ read_ready_->feed();
+ }
+ }
+
+ if (mask & EPOLLRDHUP) {
+ if (on_read_disconnect_) {
+ on_read_disconnect_->feed();
+ }
+ }
+}
+
+unix_server::unix_server(unix_event_port &event_port, int file_descriptor,
+ int fd_flags)
+ : i_fd_owner{event_port, file_descriptor, fd_flags, EPOLLIN} {}
+
+conveyor<own<io_stream>> unix_server::accept() {
+ auto caf = new_conveyor_and_feeder<own<io_stream>>();
+ accept_feeder_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void unix_server::notify(uint32_t mask) {
+ if (mask & EPOLLIN) {
+ if (accept_feeder_) {
+ struct ::sockaddr_storage address;
+ socklen_t address_length = sizeof(address);
+
+ int accept_fd =
+ ::accept4(fd(), reinterpret_cast<struct ::sockaddr *>(&address),
+ &address_length, SOCK_NONBLOCK | SOCK_CLOEXEC);
+ if (accept_fd < 0) {
+ return;
+ }
+ auto fd_stream = heap<unix_io_stream>(event_port_, accept_fd, 0,
+ EPOLLIN | EPOLLOUT);
+ accept_feeder_->feed(std::move(fd_stream));
+ }
+ }
+}
+
+unix_datagram::unix_datagram(unix_event_port &event_port, int file_descriptor,
+ int fd_flags)
+ : i_fd_owner{event_port, file_descriptor, fd_flags, EPOLLIN | EPOLLOUT} {}
+
+namespace {
+ssize_t unix_read_msg(int fd, void *buffer, size_t length) {
+ struct ::sockaddr_storage their_addr;
+ socklen_t addr_len = sizeof(sockaddr_storage);
+ return ::recvfrom(fd, buffer, length, 0,
+ reinterpret_cast<struct ::sockaddr *>(&their_addr),
+ &addr_len);
+}
+
+ssize_t unix_write_msg(int fd, const void *buffer, size_t length,
+ ::sockaddr *dest_addr, socklen_t dest_addr_len) {
+
+ return ::sendto(fd, buffer, length, 0, dest_addr, dest_addr_len);
+}
+} // namespace
+
+error_or<size_t> unix_datagram::read(void *buffer, size_t length) {
+ ssize_t read_bytes = unix_read_msg(fd(), buffer, length);
+ if (read_bytes > 0) {
+ return static_cast<size_t>(read_bytes);
+ }
+ return make_error<err::resource_busy>();
+}
+
+conveyor<void> unix_datagram::read_ready() {
+ auto caf = new_conveyor_and_feeder<void>();
+ read_ready_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+error_or<size_t> unix_datagram::write(const void *buffer, size_t length,
+ network_address &dest) {
+ unix_network_address &unix_dest = static_cast<unix_network_address &>(dest);
+ socket_address &sock_addr = unix_dest.unix_address();
+ socklen_t sock_addr_length = sock_addr.get_raw_length();
+ ssize_t write_bytes = unix_write_msg(fd(), buffer, length,
+ sock_addr.get_raw(), sock_addr_length);
+ if (write_bytes > 0) {
+ return static_cast<size_t>(write_bytes);
+ }
+ return make_error<err::resource_busy>();
+}
+
+conveyor<void> unix_datagram::write_ready() {
+ auto caf = new_conveyor_and_feeder<void>();
+ write_ready_ = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void unix_datagram::notify(uint32_t mask) {
+ if (mask & EPOLLOUT) {
+ if (write_ready_) {
+ write_ready_->feed();
+ }
+ }
+
+ if (mask & EPOLLIN) {
+ if (read_ready_) {
+ read_ready_->feed();
+ }
+ }
+}
+
+namespace {
+bool begins_with(const std::string_view &viewed,
+ const std::string_view &begins) {
+ return viewed.size() >= begins.size() &&
+ viewed.compare(0, begins.size(), begins) == 0;
+}
+
+std::variant<unix_network_address, unix_network_address *>
+translate_network_address_to_unix_network_address(network_address &addr) {
+ auto addr_variant = addr.representation();
+ std::variant<unix_network_address, unix_network_address *> os_addr =
+ std::visit(
+ [](auto &arg)
+ -> std::variant<unix_network_address, unix_network_address *> {
+ using T = std::decay_t<decltype(arg)>;
+
+ if constexpr (std::is_same_v<T, os_network_address *>) {
+ return static_cast<unix_network_address *>(arg);
+ }
+
+ auto sock_addrs = socket_address::resolve(
+ std::string_view{arg->address()}, arg->port());
+
+ return unix_network_address{arg->address(), arg->port(),
+ std::move(sock_addrs)};
+ },
+ addr_variant);
+ return os_addr;
+}
+
+unix_network_address &translate_to_unix_address_ref(
+ std::variant<unix_network_address, unix_network_address *> &addr_variant) {
+ return std::visit(
+ [](auto &arg) -> unix_network_address & {
+ using T = std::decay_t<decltype(arg)>;
+
+ if constexpr (std::is_same_v<T, unix_network_address>) {
+ return arg;
+ } else if constexpr (std::is_same_v<T, unix_network_address *>) {
+ return *arg;
+ } else {
+ static_assert(true, "Cases exhausted");
+ }
+ },
+ addr_variant);
+}
+
+} // namespace
+
+own<server> unix_network::listen(network_address &addr) {
+ auto unix_addr_storage =
+ translate_network_address_to_unix_network_address(addr);
+ unix_network_address &address =
+ translate_to_unix_address_ref(unix_addr_storage);
+
+ assert(address.unix_address_size() > 0);
+ if (address.unix_address_size() == 0) {
+ return nullptr;
+ }
+
+ int fd = address.unix_address(0).socket(SOCK_STREAM);
+ if (fd < 0) {
+ return nullptr;
+ }
+
+ int val = 1;
+ int rc = ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+ if (rc < 0) {
+ ::close(fd);
+ return nullptr;
+ }
+
+ bool failed = address.unix_address(0).bind(fd);
+ if (failed) {
+ ::close(fd);
+ return nullptr;
+ }
+
+ ::listen(fd, SOMAXCONN);
+
+ return heap<unix_server>(event_port_, fd, 0);
+}
+
+conveyor<own<io_stream>> unix_network::connect(network_address &addr) {
+ auto unix_addr_storage =
+ translate_network_address_to_unix_network_address(addr);
+ unix_network_address &address =
+ translate_to_unix_address_ref(unix_addr_storage);
+
+ assert(address.unix_address_size() > 0);
+ if (address.unix_address_size() == 0) {
+ return conveyor<own<io_stream>>{make_error<err::critical>()};
+ }
+
+ int fd = address.unix_address(0).socket(SOCK_STREAM);
+ if (fd < 0) {
+ return conveyor<own<io_stream>>{make_error<err::disconnected>()};
+ }
+
+ own<unix_io_stream> io_str =
+ heap<unix_io_stream>(event_port_, fd, 0, EPOLLIN | EPOLLOUT);
+
+ bool success = false;
+ for (size_t i = 0; i < address.unix_address_size(); ++i) {
+ socket_address &addr_iter = address.unix_address(i);
+ int status =
+ ::connect(fd, addr_iter.get_raw(), addr_iter.get_raw_length());
+ if (status < 0) {
+ int error = errno;
+ /*
+ * It's not connected yet...
+ * 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();
+ return write_ready.then(
+ [ios{std::move(io_stream)}]() mutable {
+ ios->write_ready = nullptr;
+ return std::move(ios);
+ });
+ */
+ success = true;
+ break;
+ } else if (error != EINTR) {
+ /// @todo Push error message from
+ return conveyor<own<io_stream>>{make_error<err::disconnected>()};
+ }
+ } else {
+ success = true;
+ break;
+ }
+ }
+
+ if (!success) {
+ return conveyor<own<io_stream>>{make_error<err::disconnected>()};
+ }
+
+ return conveyor<own<io_stream>>{std::move(io_str)};
+}
+
+own<datagram> unix_network::datagram(network_address &addr) {
+ auto unix_addr_storage =
+ translate_network_address_to_unix_network_address(addr);
+ unix_network_address &address =
+ translate_to_unix_address_ref(unix_addr_storage);
+
+ SAW_ASSERT(address.unix_address_size() > 0) { return nullptr; }
+
+ int fd = address.unix_address(0).socket(SOCK_DGRAM);
+
+ int optval = 1;
+ int rc =
+ ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+ if (rc < 0) {
+ ::close(fd);
+ return nullptr;
+ }
+
+ bool failed = address.unix_address(0).bind(fd);
+ if (failed) {
+ ::close(fd);
+ return nullptr;
+ }
+ /// @todo
+ return heap<unix_datagram>(event_port_, fd, 0);
+}
+
+const std::string &unix_network_address::address() const { return path_; }
+
+uint16_t unix_network_address::port() const { return port_hint_; }
+
+socket_address &unix_network_address::unix_address(size_t i) {
+ assert(i < addresses_.size());
+ /// @todo change from list to vector?
+ return addresses_.at(i);
+}
+
+size_t unix_network_address::unix_address_size() const {
+ return addresses_.size();
+}
+
+unix_network::unix_network(unix_event_port &event) : event_port_{event} {}
+
+conveyor<own<network_address>>
+unix_network::resolve_address(const std::string &path, uint16_t port_hint) {
+ std::string_view addr_view{path};
+ {
+ std::string_view str_begins_with = "unix:";
+ if (begins_with(addr_view, str_begins_with)) {
+ addr_view.remove_prefix(str_begins_with.size());
+ }
+ }
+
+ std::vector<socket_address> addresses =
+ socket_address::resolve(addr_view, port_hint);
+
+ return conveyor<own<network_address>>{
+ heap<unix_network_address>(path, port_hint, std::move(addresses))};
+}
+
+unix_io_provider::unix_io_provider(unix_event_port &port_ref,
+ own<event_port> port)
+ : event_port_{port_ref}, event_loop_{std::move(port)}, unix_network_{
+ port_ref} {}
+
+own<input_stream> unix_io_provider::wrap_input_fd(int fd) {
+ return heap<unix_io_stream>(event_port_, fd, 0, EPOLLIN);
+}
+
+class network &unix_io_provider::get_network() {
+ return static_cast<class network &>(unix_network_);
+}
+
+class event_loop &unix_io_provider::event_loop() {
+ return event_loop_;
+}
+
+} // namespace unix
+
+error_or<async_io_context> setup_async_io() {
+ using namespace unix;
+ try {
+ own<unix_event_port> prt = heap<unix_event_port>();
+ unix_event_port &prt_ref = *prt;
+
+ own<unix_io_provider> io_provider =
+ heap<unix_io_provider>(prt_ref, std::move(prt));
+
+ event_loop &loop_ref = io_provider->event_loop();
+
+ return {{std::move(io_provider), loop_ref, prt_ref}};
+ } catch (std::bad_alloc &) {
+ return make_error<err::out_of_memory>();
+ }
+}
+} // namespace saw
+#endif
diff --git a/modules/io_codec/.nix/derivation.nix b/modules/io_codec/.nix/derivation.nix
new file mode 100644
index 0000000..7cd55a8
--- /dev/null
+++ b/modules/io_codec/.nix/derivation.nix
@@ -0,0 +1,31 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-io_codec";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ forstio.codec
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/io_codec/SConscript b/modules/io_codec/SConscript
new file mode 100644
index 0000000..0afd6d6
--- /dev/null
+++ b/modules/io_codec/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+io_env = env.Clone();
+
+io_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+io_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += io_env.sources;
+env.headers += io_env.headers;
+
+## Shared lib
+objects_shared = []
+io_env.add_source_files(objects_shared, io_env.sources, shared=True);
+io_env.library_shared = io_env.SharedLibrary('#build/forstio-io_codec', [objects_shared]);
+
+## Static lib
+objects_static = []
+io_env.add_source_files(objects_static, io_env.sources, shared=False);
+io_env.library_static = io_env.StaticLibrary('#build/forstio-io_codec', [objects_static]);
+
+# Set Alias
+env.Alias('library_io_codec', [io_env.library_shared, io_env.library_static]);
+
+env.targets += ['library_io_codec'];
+
+# Install
+env.Install('$prefix/lib/', [io_env.library_shared, io_env.library_static]);
+env.Install('$prefix/include/forstio/io_codec/', [io_env.headers]);
diff --git a/modules/io_codec/SConstruct b/modules/io_codec/SConstruct
new file mode 100644
index 0000000..4e6e150
--- /dev/null
+++ b/modules/io_codec/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-io'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/io_codec/io_peer.h b/modules/io_codec/io_peer.h
new file mode 100644
index 0000000..b9a4b34
--- /dev/null
+++ b/modules/io_codec/io_peer.h
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <forstio/async/async.h>
+#include <forstio/buffer.h>
+#include <forsto/io/io.h>
+#include <forstio/schema/message.h>
+
+namespace saw {
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer = message_container<Incoming>,
+ typename OutContainer = message_container<Outgoing>,
+ typename BufferT = ring_buffer>
+class streaming_io_peer {
+public:
+ /**
+ *
+ */
+ streaming_io_peer(
+ own<conveyor_feeder<heap_message_root<Incoming, InContainer>>> feed,
+ own<async_io_stream> stream, Codec codec, BufferT in, BufferT out);
+ /**
+ *
+ */
+ streaming_io_peer(
+ own<conveyor_feeder<heap_message_root<Incoming, InContainer>>> feed,
+ own<async_io_stream> stream);
+
+ /**
+ * Deleted copy and move constructors
+ */
+ SAW_FORBID_COPY(streaming_io_peer);
+ SAW_FORBID_MOVE(streaming_io_peer);
+
+ /**
+ * Send a message to the remote peer
+ */
+ error send(heap_message_root<Outgoing, OutContainer> builder);
+
+ /**
+ * A phantom conveyor feeder. Meant for interfacing with other components
+ */
+ conveyor_feeder<heap_message_root<Outgoing, OutContainer>> &feeder();
+
+ conveyor<void> on_read_disconnected();
+
+private:
+ /// @unimplemented
+ class peer_conveyor_feeder final
+ : public conveyor_feeder<heap_message_root<Outgoing, OutContainer>> {
+ public:
+ peer_conveyor_feeder(
+ streaming_io_peer<Codec, Incoming, Outgoing, InContainer,
+ OutContainer, BufferT> &peer_)
+ : peer_{peer_} {}
+
+ void feed(heap_message_root<Outgoing, OutContainer> &&data) override {
+ (void)data;
+ }
+
+ void fail(error &&error) override { (void)error; }
+
+ size_t space() const override { return 0; }
+
+ size_t queued() const override { return 0; }
+
+ private:
+ streaming_io_peer<Codec, Incoming, Outgoing, InContainer, OutContainer,
+ BufferT> &peer_;
+ };
+
+private:
+ own<conveyor_feeder<heap_message_root<Incoming, InContainer>>>
+ incoming_feeder_ = nullptr;
+
+ own<async_io_stream> io_stream_;
+
+ Codec codec_;
+
+ BufferT in_buffer_;
+ BufferT out_buffer_;
+
+ conveyor_sink sink_read_;
+ conveyor_sink sink_write_;
+
+ peer_conveyor_feeder conveyor_feeder_;
+};
+
+/**
+ * Setup new streaming io peer with the provided network protocols.
+ * This is a convenience wrapper intended for a faster setup of this class
+ */
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer = message_container<Incoming>,
+ typename OutContainer = message_container<Outgoing>,
+ typename BufferT = ring_buffer>
+std::pair<own<streaming_io_peer<Codec, Incoming, Outgoing, InContainer,
+ OutContainer, BufferT>>,
+ conveyor<heap_message_root<Incoming, InContainer>>>
+new_streaming_io_peer(own<async_io_stream> stream);
+
+} // namespace saw
+
+#include "io_peer.tmpl.h"
diff --git a/modules/io_codec/io_peer.tmpl.h b/modules/io_codec/io_peer.tmpl.h
new file mode 100644
index 0000000..880a58a
--- /dev/null
+++ b/modules/io_codec/io_peer.tmpl.h
@@ -0,0 +1,117 @@
+namespace saw {
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer, typename OutContainer, typename BufferT>
+streaming_io_peer<Codec, Incoming, Outgoing, InContainer, OutContainer,
+ BufferT>::
+ streaming_io_peer(
+ own<conveyor_feeder<heap_message_root<Incoming, InContainer>>> feed,
+ own<async_io_stream> str)
+ : streaming_io_peer{std::move(feed), std::move(str), {}, {}, {}} {}
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer, typename OutContainer, typename BufferT>
+streaming_io_peer<Codec, Incoming, Outgoing, InContainer, OutContainer,
+ BufferT>::
+ streaming_io_peer(
+ own<conveyor_feeder<heap_message_root<Incoming, InContainer>>> feed,
+ own<async_io_stream> stream, Codec codec, BufferT in, BufferT out)
+ : incoming_feeder_{std::move(feed)},
+ io_stream_{std::move(stream)}, codec_{std::move(codec)},
+ in_buffer_{std::move(in)}, out_buffer_{std::move(out)},
+ sink_read_{
+ io_stream_->read_done()
+ .then([this](size_t bytes) -> error_or<void> {
+ in_buffer_.write_advance(bytes);
+
+ if (in_buffer_.write_segment_length() == 0) {
+ return critical_error("Message too long");
+ }
+
+ io_stream_->read(&in_buffer_.write(), 1,
+ in_buffer_.write_segment_length());
+
+ while (true) {
+ auto root = heap_message_root<Incoming, InContainer>();
+ auto builder = root.build();
+
+ error err = codec_.template decode<Incoming, InContainer>(
+ builder, in_buffer_);
+ if (err.is_critical()) {
+ return err;
+ }
+
+ if (!err.failed()) {
+ incoming_feeder_->feed(std::move(root));
+ } else {
+ break;
+ }
+ }
+
+ return void_t{};
+ })
+ .sink([this](error err) {
+ incoming_feeder_->fail(err.copy_error());
+
+ return err;
+ })},
+ sink_write_{io_stream_->write_done()
+ .then([this](size_t bytes) -> error_or<void> {
+ out_buffer_.read_advance(bytes);
+ if (out_buffer_.readCompositeLength() > 0) {
+ io_stream_->write(
+ &out_buffer_.read(),
+ out_buffer_.read_segment_length());
+ }
+
+ return void_t{};
+ })
+ .sink()} {
+ io_stream_->read(&in_buffer_.write(), 1, in_buffer_.write_segment_length());
+}
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer, typename OutContainer, typename BufferT>
+error streaming_io_peer<Codec, Incoming, Outgoing, InContainer, OutContainer,
+ BufferT>::send(heap_message_root<Outgoing, OutContainer>
+ msg) {
+ bool restart_write = out_buffer_.read_segment_length() == 0;
+
+ error err =
+ codec_.template encode<Outgoing, OutContainer>(msg.read(), out_buffer_);
+ if (err.failed()) {
+ return err;
+ }
+
+ if (restart_write) {
+ io_stream_->write(&out_buffer_.read(),
+ out_buffer_.read_segment_length());
+ }
+
+ return no_error();
+}
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer, typename OutContainer, typename BufferT>
+conveyor<void>
+streaming_io_peer<Codec, Incoming, Outgoing, InContainer, OutContainer,
+ BufferT>::on_read_disconnected() {
+ return io_stream_->on_read_disconnected();
+}
+
+template <typename Codec, typename Incoming, typename Outgoing,
+ typename InContainer, typename OutContainer, typename BufferT>
+std::pair<own<streaming_io_peer<Codec, Incoming, Outgoing, InContainer,
+ OutContainer, BufferT>>,
+ conveyor<heap_message_root<Incoming, InContainer>>>
+newstreaming_io_peer(own<async_io_stream> stream) {
+ auto caf =
+ new_conveyor_and_feeder<heap_message_root<Incoming, InContainer>>();
+
+ return {heap<streaming_io_peer<Codec, Incoming, Outgoing, InContainer,
+ OutContainer, BufferT>>(
+ std::move(caf.feeder), std::move(stream)),
+ std::move(caf.conveyor)};
+}
+
+} // namespace saw
diff --git a/modules/io_codec/rpc.h b/modules/io_codec/rpc.h
new file mode 100644
index 0000000..020bf96
--- /dev/null
+++ b/modules/io_codec/rpc.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <forstio/codec/rpc.h>
+
+namespace saw {
+namespace rmt {
+struct Network {};
+}
+
+template<>
+class remote<rmt::Network> {
+private:
+ std::string addr_str_;
+public:
+ remote(std::string addr_str);
+
+ template<typename Interface>
+ conveyor<rpc_client<rmt::Network, Interface>> create_client();
+};
+
+template<template Interface>
+class rpc_client<rmt::Network, Interface> {
+private:
+ own<io_stream> stream_;
+public:
+ rpc_client(own<io_stream> stream);
+
+ template<typename T>
+ remote_result<typename response<T>::type> call(typename request<T>::type req);
+};
+}
diff --git a/modules/test/.nix/derivation.nix b/modules/test/.nix/derivation.nix
new file mode 100644
index 0000000..c15421d
--- /dev/null
+++ b/modules/test/.nix/derivation.nix
@@ -0,0 +1,28 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-test";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/test/SConscript b/modules/test/SConscript
new file mode 100644
index 0000000..6379b24
--- /dev/null
+++ b/modules/test/SConscript
@@ -0,0 +1,33 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+test_env = env.Clone();
+
+test_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+test_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += test_env.sources;
+env.headers += test_env.headers;
+
+## Shared lib
+objects = []
+test_env.add_source_files(objects, test_env.sources, shared=False);
+test_env.library = test_env.StaticLibrary('#build/forstio-test', [objects]);
+
+# Set Alias
+env.Alias('library_test', [test_env.library]);
+
+env.targets += ['library_test'];
+
+# Install
+env.Install('$prefix/lib/', [test_env.library]);
+env.Install('$prefix/include/forstio/test/', [test_env.headers]);
diff --git a/modules/test/SConstruct b/modules/test/SConstruct
new file mode 100644
index 0000000..0d7b7c6
--- /dev/null
+++ b/modules/test/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-core'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/test/suite.cpp b/modules/test/suite.cpp
new file mode 100644
index 0000000..0fca8f9
--- /dev/null
+++ b/modules/test/suite.cpp
@@ -0,0 +1,107 @@
+#include "suite.h"
+
+#include <map>
+#include <string>
+#include <chrono>
+#include <iostream>
+
+namespace saw {
+namespace test {
+ test_case* testCaseHead = nullptr;
+ test_case** testCaseTail = &testCaseHead;
+
+ test_case::test_case(const std::string& file_, uint line_, const std::string& description_):
+ file{file_},
+ line{line_},
+ description{description_},
+ matched_filter{false},
+ next{nullptr},
+ prev{testCaseTail}
+ {
+ *prev = this;
+ testCaseTail = &next;
+ }
+
+ test_case::~test_case(){
+ *prev = next;
+ if( next == nullptr ){
+ testCaseTail = prev;
+ }else{
+ next->prev = prev;
+ }
+ }
+
+ class test_runner {
+ private:
+ enum Colour {
+ RED,
+ GREEN,
+ BLUE,
+ WHITE
+ };
+
+ void write(Colour colour, const std::string& front, const std::string& message){
+ std::string start_col, end_col;
+ switch(colour){
+ case RED: start_col = "\033[0;1;31m"; break;
+ case GREEN: start_col = "\033[0;1;32m"; break;
+ case BLUE: start_col = "\033[0;1;34m"; break;
+ case WHITE: start_col = "\033[0m"; break;
+ }
+ end_col = "\033[0m";
+ std::cout<<start_col<<front<<end_col<<message<<'\n';
+ }
+ public:
+ void allowAll(){
+ for(test_case* testCase = testCaseHead; testCase != nullptr; testCase = testCase->next){
+ testCase->matched_filter = true;
+ }
+ }
+
+ int run() {
+ size_t passed_count = 0;
+ size_t failed_count = 0;
+
+ for(test_case* testCase = testCaseHead; testCase != nullptr; testCase =testCase->next){
+ if(testCase->matched_filter){
+ std::string name = testCase->file + std::string(":") + std::to_string(testCase->line) + std::string(": ") + testCase->description;
+ write(BLUE, "[ TEST ] ", name);
+ bool failed = true;
+ std::string fail_message;
+ auto start_clock = std::chrono::steady_clock::now();
+ try {
+ testCase->run();
+ failed = false;
+ }catch(std::exception& e){
+ fail_message = e.what();
+ failed = true;
+ }
+ auto stop_clock = std::chrono::steady_clock::now();
+
+ auto runtime_duration_intern = stop_clock - start_clock;
+ auto runtime_duration = std::chrono::duration_cast<std::chrono::microseconds>(runtime_duration_intern);
+ std::string message = name + std::string(" (") + std::to_string(runtime_duration.count()) + std::string(" µs)");
+ if(failed){
+ write(RED, "[ FAIL ] ", message + " " + fail_message);
+ ++failed_count;
+ }else{
+ write(GREEN, "[ PASS ] ", message);
+ ++passed_count;
+ }
+ }
+ }
+
+ if(passed_count>0) write(GREEN, std::to_string(passed_count) + std::string(" test(s) passed"), "");
+ if(failed_count>0) write(RED, std::to_string(failed_count) + std::string(" test(s) failed"), "");
+ return -failed_count;
+ }
+ };
+}
+}
+
+int main() {
+ saw::test::test_runner runner;
+ runner.allowAll();
+ int rv = runner.run();
+ return rv<0?-1:0;
+}
diff --git a/modules/test/suite.h b/modules/test/suite.h
new file mode 100644
index 0000000..34f29bf
--- /dev/null
+++ b/modules/test/suite.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <string>
+#include <memory>
+#include <stdexcept>
+#include <type_traits>
+
+#include <forstio/core/common.h>
+
+namespace saw {
+namespace test {
+class test_runner;
+class test_case {
+private:
+ std::string file;
+ uint line;
+ std::string description;
+ bool matched_filter;
+ test_case* next;
+ test_case** prev;
+
+ friend class test_runner;
+public:
+ test_case(const std::string& file_, uint line_, const std::string& description_);
+ ~test_case();
+
+ virtual void run() = 0;
+};
+}
+}
+#define SAW_TEST(description) \
+ class SAW_UNIQUE_NAME(test_case) : public ::saw::test::test_case { \
+ public: \
+ SAW_UNIQUE_NAME(test_case)(): ::saw::test::test_case(__FILE__,__LINE__,description) {} \
+ void run() override; \
+ }SAW_UNIQUE_NAME(test_case_); \
+ void SAW_UNIQUE_NAME(test_case)::run()
+
+#define SAW_EXPECT(expr, msg_split) \
+ if( ! (expr) ){ \
+ auto msg = msg_split; \
+ throw std::runtime_error{std::string{msg}};\
+ }
diff --git a/modules/tools/.nix/derivation.nix b/modules/tools/.nix/derivation.nix
new file mode 100644
index 0000000..6e3fbe1
--- /dev/null
+++ b/modules/tools/.nix/derivation.nix
@@ -0,0 +1,31 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-tools";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ forstio.codec
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/tools/SConscript b/modules/tools/SConscript
new file mode 100644
index 0000000..7c9efd7
--- /dev/null
+++ b/modules/tools/SConscript
@@ -0,0 +1,32 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+tools_env = env.Clone();
+
+tools_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+tools_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += tools_env.sources;
+env.headers += tools_env.headers;
+
+## Static lib
+objects_static = []
+tools_env.add_source_files(objects_static, tools_env.sources, shared=False);
+tools_env.c_array_example = tools_env.Program('#build/forstio-c-array-example', [objects_static]);
+
+# Set Alias
+env.Alias('tools_c_array_example', [tools_env.c_array_example]);
+
+env.targets += ['tools_c_array_example'];
+
+# Install
+env.Install('$prefix/bin/', [tools_env.c_array_example]);
diff --git a/modules/tools/SConstruct b/modules/tools/SConstruct
new file mode 100644
index 0000000..9a02291
--- /dev/null
+++ b/modules/tools/SConstruct
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=[
+ 'forstio-core',
+ 'forstio-async',
+ 'forstio-codec'
+ ]
+);
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/tools/c_gen_iface.cpp b/modules/tools/c_gen_iface.cpp
new file mode 100644
index 0000000..996f3c3
--- /dev/null
+++ b/modules/tools/c_gen_iface.cpp
@@ -0,0 +1,11 @@
+#include "c_gen_iface.hpp"
+
+int main(){
+ // saw::generate_array_example();
+
+ auto eov = saw::generate_iface_example();
+ if(eov.is_error()){
+ return -1;
+ }
+ return 0;
+}
diff --git a/modules/tools/c_gen_iface.hpp b/modules/tools/c_gen_iface.hpp
new file mode 100644
index 0000000..e79e27f
--- /dev/null
+++ b/modules/tools/c_gen_iface.hpp
@@ -0,0 +1,535 @@
+#include <forstio/core/error.h>
+#include <forstio/core/templates.h>
+#include <forstio/codec/schema.h>
+
+#include <string>
+#include <sstream>
+#include <map>
+
+#include <iostream>
+
+#include <forstio/codec/schema_stringify.h>
+
+namespace saw {
+
+namespace impl {
+/**
+ * Type meant to provide future help if I decide to introduce more maps
+ */
+struct c_types {
+ struct c_member {
+ std::string key;
+ std::string name;
+ bool is_primitive;
+ };
+
+ struct c_struct {
+ std::string kind;
+ std::string type;
+ std::string cpp_schema;
+ std::vector<c_member> members;
+ };
+
+ struct c_response {
+ std::string key;
+ bool is_primitive;
+ };
+
+ struct c_func {
+ std::string cpp_schema;
+ std::string cpp_name;
+ c_response response;
+ std::vector<c_member> requests;
+ };
+
+ using c_struct_map = std::map<std::string, c_struct>;
+ using c_func_map = std::map<std::string, c_func>;
+
+ struct state {
+ std::string interface_schema;
+ c_struct_map struct_map;
+ c_func_map func_map;
+ std::string prefix;
+ std::string postfix;
+ std::string encoding;
+ };
+};
+
+namespace schema {
+using namespace saw::schema;
+/**
+Pseudo flattened
+using Foo = Struct<
+ Member<Array<Int32>, "a">
+ Member<Array<Float32>, "b">
+>;
+
+Needs to be flattened to
+
+template<typename InnerSchema>
+using FlattenedSchemaElement = Struct<
+ Member<Array<String>, "path">,
+ Member<InnerSchema, "inner_schema">
+>;
+
+// Illegal, but doable with more lines of code
+// Just use typename... T and
+// "T..." for
+// "Member<FlattenedSchemaElement<Ele>,Names>...>"
+// and specialize somewhere else
+template<typename... Ele, string_literal... Names>
+using FlattenedSchema = Struct<
+ Member<String, "top_schema">,
+ Member<FlattenedSchemaElement<Ele>, Names>...
+>;
+ */
+template<typename T>
+struct schema_flattener {
+ static_assert(always_false<T>, "Not supported");
+};
+
+template<typename... T, string_literal... Names>
+struct schema_flattener<schema::Struct<schema::Member<T,Names>...>> {
+
+};
+
+>;
+
+using StructBindingSchema = Struct<
+ Member<String, "kind">,
+ Member<String, "key">,
+ Member<String, "cpp_schema">,
+ Member<Array<String>, "members">
+>;
+
+using FunctionBindingSchema = Struct<
+ Member<String, "cpp_schema">,
+ Member<String, "name">,
+ Member<String, "foo">
+>;
+
+using BindingSchema = Struct<
+ Member<String, "interface_schema">,
+ Member<String, "prefix">,
+ Member<String, "postfix">,
+ Member<String, "encoding">,
+ Member<Array<StructBindingSchema>, "structs">,
+ Member<Array<FunctionBindingSchema>, "functions">
+>;
+}
+
+/**
+ * Helper to determine if we are dealing with primitive types
+ */
+template<typename Schema>
+struct c_is_primitive {
+ static constexpr bool value = false;
+};
+
+template<typename T, size_t N>
+struct c_is_primitive<schema::Primitive<T,N>> {
+ static constexpr bool value = true;
+};
+
+template<typename Schema>
+struct c_primitive_string {
+ static_assert(always_false<Schema>, "Not supported");
+};
+
+template<>
+struct c_primitive_string<schema::Int8> {
+ static constexpr std::string_view value = "int8_t";
+};
+
+template<>
+struct c_primitive_string<schema::Int16> {
+ static constexpr std::string_view value = "int16_t";
+};
+
+template<>
+struct c_primitive_string<schema::Int32> {
+ static constexpr std::string_view value = "int32_t";
+};
+
+template<>
+struct c_primitive_string<schema::Int64> {
+ static constexpr std::string_view value = "int64_t";
+};
+
+template<>
+struct c_primitive_string<schema::UInt8> {
+ static constexpr std::string_view value = "uint8_t";
+};
+
+template<>
+struct c_primitive_string<schema::UInt16> {
+ static constexpr std::string_view value = "uint16_t";
+};
+
+template<>
+struct c_primitive_string<schema::UInt32> {
+ static constexpr std::string_view value = "uint32_t";
+};
+
+template<>
+struct c_primitive_string<schema::UInt64> {
+ static constexpr std::string_view value = "uint64_t";
+};
+
+template<>
+struct c_primitive_string<schema::Float32> {
+ static constexpr std::string_view value = "float";
+};
+
+template<>
+struct c_primitive_string<schema::Float64> {
+ static constexpr std::string_view value = "double";
+};
+
+template<typename Schema>
+struct c_data_translater {
+ static_assert(always_false<Schema>, "Not supported");
+};
+
+template<typename T, size_t N>
+struct c_data_translater<schema::Primitive<T,N>> {
+ using StructMap = typename c_types::c_struct_map;
+ using Schema = schema::Primitive<T,N>;
+
+ static error_or<void> generate(c_types::state& c_state, std::string& str){
+ (void) c_state;
+ str += c_primitive_string<Schema>::value;
+ return void_t{};
+ }
+};
+
+template<typename T, size_t Dim>
+struct c_data_translater<schema::Array<T,Dim>> {
+ using Schema = schema::Array<T,Dim>;
+ using StructMap = typename c_types::c_struct_map;
+
+ static error_or<void> generate(c_types::state& c_state, std::string& type_str){
+ type_str = "array_";
+
+ std::string inner_type_str;
+ auto eov = impl::c_data_translater<T>::generate(c_state, inner_type_str);
+ if(eov.is_error()){
+ return eov;
+ }
+
+ type_str += inner_type_str;
+
+ if(Dim > 1){
+ type_str += "_";
+ type_str += std::to_string(Dim);
+ type_str += "d";
+ }
+
+ if(c_state.struct_map.find(type_str) != c_state.struct_map.end()){
+ return void_t{};
+ }
+
+ c_types::c_struct str;
+
+ try {
+ std::stringstream iss;
+ schema_stringify<Schema>::apply(iss);
+ str.cpp_schema = iss.str();
+ }catch(const std::exception& e){
+ return make_error<err::critical>();
+ }
+
+ /**
+ * Adding heap alloc data ptr
+ */
+ {
+ c_types::c_member memb;
+ memb.key = inner_type_str;
+ memb.name = "data";
+ memb.is_primitive = c_is_primitive<T>::value;
+ // fn.members.emplace_back(std::move(memb));
+
+ str.members.emplace_back(std::move(memb));
+ }
+ {
+ c_types::c_member memb;
+ memb.key
+ }
+
+
+ str.def = "struct ";
+ str.def += type_str;
+
+ str.def += " {\n";
+
+ str.def += "\t" + inner_type_str + "* data;\n";
+ str.def += "\tsize_t size;\n";
+ if( Dim > 1 ){
+ str.def += "\tsize_t dims["+std::to_string(Dim)+"];\n";
+ }
+
+ str.def += "};\n";
+
+ c_state.struct_map.emplace(std::make_pair(type_str, std::move(str)));
+ return void_t{};
+ }
+};
+
+template<typename Interface>
+struct c_iface_translater {
+ static_assert(always_false<Interface>,"Not supported");
+};
+
+template<class... Request, class Response, string_literal... Lits>
+struct c_iface_translater<schema::Function<schema::Struct<schema::Member<Request, Lits>...>, Response>> {
+ using StructMap = typename c_types::c_struct_map;
+ using FuncMap = typename c_types::c_func_map;
+
+ template<size_t i>
+ static error_or<void> generate_request_member(c_types::state& c_state, std::array<std::string, sizeof...(Request)>& type_arr){
+ using Type = typename parameter_pack_type<i, Request...>::type;
+ // constexpr string_literal lit = parameter_key_pack_type<i, Lits...>::literal;
+
+ auto eov = c_data_translater<Type>::generate(c_state, type_arr.at(i));
+ if(eov.is_error()){
+ return eov;
+ }
+
+ if constexpr ((i+1) < sizeof...(Request)){
+ auto eov = generate_request_member<i+1>(c_state, type_arr);
+ return eov;
+ }
+
+ return void_t{};
+ }
+
+ template<size_t i>
+ static error_or<void> generate_parameter(std::string& param_use, const std::array<std::string, sizeof...(Request)>& req_type_arr){
+ using Type = typename parameter_pack_type<i, Request...>::type;
+ constexpr string_literal lit = parameter_key_pack_type<i, Lits...>::literal;
+
+ std::string used_param;
+ if constexpr (c_is_primitive<Type>::value){
+ used_param += req_type_arr.at(i);
+ } else {
+ used_param += "const " + req_type_arr.at(i) + "*";
+ }
+ param_use += used_param + " " + std::string{lit.view()};
+
+ if constexpr ( (i+1) < sizeof...(Request) ){
+ param_use += ", ";
+ return generate_parameter<i+1>(param_use, req_type_arr);
+ }
+
+ return void_t{};
+ }
+
+ static error_or<void> generate(c_types::state& c_state, const std::string& func_name){
+ std::array<std::string, sizeof...(Request)> request_type_arr;
+
+ if constexpr (sizeof...(Request) > 0){
+ auto eov = generate_request_member<0>(c_state, request_type_arr);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ std::string response_type_str;
+ {
+ auto eov = c_data_translater<Response>::generate(c_state, response_type_str);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ std::string c_func_name = c_state.prefix + "_" + func_name;
+
+ c_types::c_func fn;
+ fn.cpp_name = func_name;
+ fn.def = "int";
+ std::string iface_ctx = c_state.prefix + "_iface_context* ctx";
+ fn.def += " " + c_func_name + "(" + iface_ctx;
+ fn.def += ", " + response_type_str + "* out";
+ if constexpr ( sizeof...(Request) > 0 ){
+ fn.def += ", ";
+ auto eov = generate_parameter<0>(fn.def, request_type_arr);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ fn.def += ")";
+
+ fn.source = "{\n";
+ // Check necessary pointers
+ fn.source += "\tassert(ctx);\n";
+ fn.source += "\tif(!ctx){return -1;}\n\n";
+ fn.source += "\tauto rmt = reinterpret_cast<interface<IfaceSchema, ";
+ fn.source += c_state.encoding;
+ fn.source += ">*>(ctx);\n";
+
+ // translate the input data
+ if constexpr (sizeof...(Request) > 0){
+ // I need a recursive search for the types
+ }
+
+ // Call the remote interface
+ fn.source += "\tauto eov = rmt->template call<\"" + fn.cpp_name + "\">(std::move(input));";
+ fn.source += ");\n";
+
+ // translate the output data
+ fn.source += "";
+
+ // Close the brace
+
+ fn.source += "}\n";
+
+ c_state.func_map.emplace(std::make_pair(c_func_name, std::move(fn)));
+
+ return void_t{};
+ }
+};
+
+template<class... Requests, class... Responses, string_literal... Names>
+struct c_iface_translater<schema::Interface<schema::Member<schema::Function<Requests, Responses>, Names>...>> {
+ using Schema = schema::Interface<schema::Member<schema::Function<Requests, Responses>, Names>...>;
+
+ template<size_t i>
+ static error_or<void> generate_member(c_types::state& c_state){
+ using Req = typename parameter_pack_type<i,Requests...>::type;
+ using Resp = typename parameter_pack_type<i,Responses...>::type;
+ constexpr string_literal lit = parameter_key_pack_type<i, Names...>::literal;
+
+ try{
+ std::stringstream iss;
+ schema_stringify<Schema>::apply(iss);
+ c_state.interface_schema = iss.str();
+ }catch(const std::exception& e){
+ (void) e;
+ return make_error<err::critical>();
+ }
+
+ std::string c_func_name = c_state.prefix + "_" + std::string{lit.view()};
+ if(c_state.func_map.find(c_func_name) != c_state.func_map.end()){
+ return make_error<err::already_exists>();
+ }
+
+ {
+ std::string type_str;
+ auto eov = c_iface_translater<schema::Function<Req, Resp>>::generate(c_state, std::string{lit.view()});
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ if constexpr ((i+1) < sizeof...(Requests) ){
+ return generate_member<i+1>(c_state);
+ }
+ return void_t{};
+ }
+
+ static error_or<void> generate(c_types::state& c_state){
+ if constexpr ( sizeof...(Requests) > 0 ){
+ return generate_member<0>(c_state);
+ }
+
+ return void_t{};
+ }
+};
+}
+
+template<typename Interface>
+error_or<void> generate_c_interface(typename impl::c_types::state& state){
+ auto eov = impl::c_iface_translater<Interface>::generate(state);
+ return eov;
+}
+
+error_or<void> generate_iface_example(){
+ using Request1 = schema::Struct<
+ schema::Member<schema::Int32, "foo">,
+ schema::Member<schema::Float64, "bar">,
+ schema::Member<schema::Array<schema::Float32,1>, "baz">
+ >;
+ using Response1 = schema::Int32;
+
+ using Func1 = schema::Function<Request1, Response1>;
+
+ using Iface = schema::Interface<
+ schema::Member<Func1, "func_one">
+ >;
+
+ impl::c_types::state c_state;
+ c_state.prefix = "c_saw";
+ c_state.encoding = "saw::encode::Native";
+
+ typename impl::c_types::c_struct_map& type_map = c_state.struct_map;
+ typename impl::c_types::c_func_map& func_map = c_state.func_map;
+ auto eov = generate_c_interface<Iface>(c_state);
+ if(eov.is_error()){
+ return eov;
+ }
+ std::cout<<"Interface: "<<c_state.interface_schema<<std::endl;
+ std::cout<<"Prefix: "<<c_state.prefix<<std::endl;
+ std::cout<<"\nTypes: "<<std::endl;
+
+ for(auto& iter : type_map){
+ std::cout<<"\nType: \""<<iter.first<<"\""<<std::endl;
+ std::cout<<"Definition:\n\"\"\"\n";
+ std::cout<<iter.second.def;
+ std::cout<<"\"\"\""<<std::endl;
+ std::cout<<"Schema: "<<iter.second.cpp_schema<<std::endl;
+ }
+ std::cout<<"\nFunctions: "<<std::endl;
+
+ for(auto& iter : func_map){
+ std::cout<<"\nSymbol: \""<<iter.first<<"\""<<std::endl;
+ std::cout<<"Definition:\n\"\"\"\n";
+ std::cout<<iter.second.def<<";\n";
+ std::cout<<"\"\"\""<<std::endl;
+ std::cout<<"\nSource:\n"<<iter.second.source<<std::endl;
+ }
+
+ return eov;
+}
+
+error_or<void> generate_array_example(){
+ using Schema1 = schema::Array<schema::Int32,2>;
+
+ using Schema2 = schema::Array<schema::Float64,5>;
+
+ using Schema3 = schema::Array<schema::UInt16,1>;
+
+ std::string prefix = "c_saw";
+ impl::c_types::state c_state;
+ c_state.prefix = prefix;
+ {
+ std::string type_str;
+ auto eov = impl::c_data_translater<Schema1>::generate(c_state, type_str);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ std::string type_str;
+ auto eov = impl::c_data_translater<Schema2>::generate(c_state, type_str);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ std::string type_str;
+ auto eov = impl::c_data_translater<Schema3>::generate(c_state, type_str);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+
+ std::cout<<"Prefix: "<<c_state.prefix<<std::endl;
+
+ for(auto& iter : c_state.struct_map){
+ std::cout<<"\nType: \""<<iter.first<<"\""<<std::endl;
+ std::cout<<"Definition:\n\"\"\"\n";
+ std::cout<<iter.second.def;
+ std::cout<<"\"\"\""<<std::endl;
+ }
+
+ return void_t{};
+}
+}
diff --git a/modules/tools/cli_analyzer.hpp b/modules/tools/cli_analyzer.hpp
new file mode 100644
index 0000000..295ddf6
--- /dev/null
+++ b/modules/tools/cli_analyzer.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include <forstio/codec/schema.h>
+
+#include <iostream>
+
+namespace saw {
+namespace impl {
+struct cli_mode {
+ struct read {};
+ struct write {};
+};
+
+template<typename T, typename Encoding>
+struct cli_traverser {
+ static_assert(always_false<T,Encoding>, "Not supported");
+};
+
+template<typename Schema, typename Encoding>
+struct cli_modifier {
+ codec<Schema, encode::Json> json;
+ codec<Schema, Encoding> encoded;
+
+ error_or<void> read(data<Schema, Encoding>& enc_data, std::deque<std::string>& sch_path, std::string& json_data_str){
+ data<Schema, encode::Native> native;
+ {
+ auto eov = encoded.decode<encode::Native>(enc_data, native);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ data<Schema, encode::Json> json_data;
+ auto eov = json.encode<encode::Native>(native, json_data);
+ if(eov.is_error()){
+ return eov;
+ }
+
+ json_data_str = convert_to_string(json_data.get_buffer());
+ }
+
+ return void_t{};
+ }
+
+ error_or<void> write(data<Schema, Encoding>& enc_data, std::deque<std::string>& sch_path, std::string& json_data_str){
+ data<Schema, encode::Native> native;
+ {
+ /// @todo string to data
+ data<Schema, encode::Json> json_data{ std::string_view{json_data_str} };
+ auto eov = json.decode<encode::Native>(json_data, native);
+ if(eov.is_error()){
+ return eov;
+ }
+ }
+ {
+ auto eov = encoded.encode<encode::Native>(native, enc_data);
+ if(eov.is_error()){
+ return eov;
+ }
+ }i
+
+ return void_t{};
+
+ }
+};
+
+template<typename... T, string_literal... Lits, typename Encoding>
+struct cli_traverser<schema::Struct<schema::Member<T,Lits>...>, Encoding> {
+ using Schema = schema::Struct<schema::Member<T,Lits>...>;
+
+ template<typename Traversal>
+ static error_or<void> traverse(std::deque<std::string>& sch_path, std::string& json_data){
+ if(sch_path.empty()){
+ cli_modifier<Schema, Encoding> mod;
+ if constexpr (std::is_same_v<Traversal, cli_mode::read>){
+ return mod.read(sch_path, json_data);
+ } else if constexpr (std::is_same_v<Traversal, cli_mode::write>) {
+ return mod.write(sch_path, json_data);
+ } else {
+ return make_error<err::invalid_state>();
+ }
+ } else {
+
+ }
+
+ return void_t{};
+ }
+};
+}
+
+template<typename Schema, typename Encoding>
+int modify_data_on_cli(int argc, char** argv){
+
+ /// @todo parse cli data
+ bool read_mode = true;
+
+ std::deque<std::string> sch_path;
+ std::string json_data;
+
+ if (read_mode) {
+ auto eov = impl::cli_modifier<Schema, Encoding>::traverse<impl::cli_mode::read>(sch_path, json_data);
+ if(eov.is_error()){
+ return -1;
+ }
+ } else {
+ auto eov = impl::cli_modifier<Schema, Encoding>::traverse<impl::cli_mode::write>(sch_path, json_data);
+ if(eov.is_error()){
+ return -1;
+ }
+ }
+
+ return 0;
+}
+}
diff --git a/modules/window-opengl/.nix/derivation.nix b/modules/window-opengl/.nix/derivation.nix
new file mode 100644
index 0000000..40c25bb
--- /dev/null
+++ b/modules/window-opengl/.nix/derivation.nix
@@ -0,0 +1,37 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+, xorg
+, libGL
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-window-opengl";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ forstio.codec
+ forstio.window
+ xorg.libX11
+ xorg.libxcb
+ libGL
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/window-opengl/SConscript b/modules/window-opengl/SConscript
new file mode 100644
index 0000000..7beee1a
--- /dev/null
+++ b/modules/window-opengl/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+window_env = env.Clone();
+
+window_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+window_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += window_env.sources;
+env.headers += window_env.headers;
+
+## Shared lib
+objects_shared = []
+window_env.add_source_files(objects_shared, window_env.sources, shared=True);
+window_env.library_shared = window_env.SharedLibrary('#build/forstio-window-opengl', [objects_shared]);
+
+## Static lib
+objects_static = []
+window_env.add_source_files(objects_static, window_env.sources, shared=False);
+window_env.library_static = window_env.StaticLibrary('#build/forstio-window-opengl', [objects_static]);
+
+# Set Alias
+env.Alias('library_window_opengl', [window_env.library_shared, window_env.library_static]);
+
+env.targets += ['library_window_opengl'];
+
+# Install
+env.Install('$prefix/lib/', [window_env.library_shared, window_env.library_static]);
+env.Install('$prefix/include/forstio/window/opengl/', [window_env.headers]);
diff --git a/modules/window-opengl/SConstruct b/modules/window-opengl/SConstruct
new file mode 100644
index 0000000..fc2c398
--- /dev/null
+++ b/modules/window-opengl/SConstruct
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX', 'SAW_UNIX_XCB', 'SAW_OGL'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=[
+ 'forstio-core',
+ 'forstio-io',
+ 'forstio-async',
+ 'forstio-codec'
+ ]
+);
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/window-opengl/gl_backends.h b/modules/window-opengl/gl_backends.h
new file mode 100644
index 0000000..652954f
--- /dev/null
+++ b/modules/window-opengl/gl_backends.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace saw {
+namespace gfx {
+namespace backend {
+struct gl_linux_xcb {};
+struct gl_wasm {};
+}
+}
+}
diff --git a/modules/window-opengl/gl_context.h b/modules/window-opengl/gl_context.h
new file mode 100644
index 0000000..685e527
--- /dev/null
+++ b/modules/window-opengl/gl_context.h
@@ -0,0 +1,43 @@
+#pragma once
+
+namespace saw {
+namespace gfx {
+class gl_settings {
+public:
+ uint8_t gl_major = 3;
+ uint8_t gl_minor = 3;
+
+ enum class render_type : int32_t { rgba };
+
+ render_type render_t = render_type::rgba;
+
+ bool renderable = true;
+
+ bool window_type = true;
+
+ enum class drawable_type : int32_t {
+ window_bit = 0x01,
+ pixmap_bit = 0x02,
+ pbuffer_bit = 0x04
+ };
+
+ drawable_type drawable_t = drawable_type::window_bit;
+
+ bool double_buffer = true;
+ int red_bits = 8;
+ int green_bits = 8;
+ int blue_bits = 8;
+ int alpha_bits = 8;
+
+ int depth_bits = 24;
+ int stencil_bits = 8;
+
+ bool core_profile = true;
+};
+
+template<typename T>
+class gpu_context;
+
+
+}
+}
diff --git a/modules/window-opengl/gl_window.h b/modules/window-opengl/gl_window.h
new file mode 100644
index 0000000..3bb9f83
--- /dev/null
+++ b/modules/window-opengl/gl_window.h
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace saw {
+namespace gfx {
+template<typename T>
+class gpu_window;
+}
+}
diff --git a/modules/window-opengl/gl_xcb.cpp b/modules/window-opengl/gl_xcb.cpp
new file mode 100644
index 0000000..5ea03f3
--- /dev/null
+++ b/modules/window-opengl/gl_xcb.cpp
@@ -0,0 +1,269 @@
+#include "gl_xcb.h"
+
+#include <cassert>
+#include <cstdint>
+#include <set>
+
+namespace saw {
+namespace gfx {
+namespace {
+glx_library_extensions load_glx_library_extensions(const char* extension_string){
+ std::string_view extensions_view{extension_string};
+
+ std::set<std::string_view> extensions;
+
+ for(;;){
+ size_t n = extensions_view.find_first_of(' ');
+ if( n != extensions_view.npos && n < extensions_view.size()){
+ std::string_view sub_glx_ext = extensions_view.substr(0,n);
+ extensions.insert(sub_glx_ext);
+ extensions_view.remove_prefix(n+1);
+ } else {
+ break;
+ }
+ }
+
+ auto find = extensions.find("GLX_ARB_create_context");
+ GLXContext (*glXCreateContextAttribsARB)(Display*, GLXFBConfig, GLXContext, Bool, const int*) = nullptr;
+ if(find != extensions.end()){
+ glXCreateContextAttribsARB = reinterpret_cast<GLXContext(*)(
+ Display*, GLXFBConfig, GLXContext, Bool, const int*)>(
+ glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")));
+ }
+
+ return {extensions_view, glXCreateContextAttribsARB};
+}
+
+int translate_render_type_settings(gl_settings::render_type cmp){
+ switch(cmp){
+ case gl_settings::render_type::rgba:
+ return GLX_RGBA_BIT;
+ }
+ return 0;
+}
+
+int translate_drawable_type_settings(gl_settings::drawable_type cmp){
+ int i = 0;
+ if (static_cast<int32_t>(cmp) &
+ static_cast<int32_t>(gl_settings::drawable_type::window_bit)) {
+ i |= static_cast<int32_t>(GLX_WINDOW_BIT);
+ }
+ if (static_cast<int32_t>(cmp) &
+ static_cast<int32_t>(gl_settings::drawable_type::pixmap_bit)) {
+ i |= static_cast<int32_t>(GLX_PIXMAP_BIT);
+ }
+ if (static_cast<int32_t>(cmp) &
+ static_cast<int32_t>(gl_settings::drawable_type::pbuffer_bit)) {
+ i |= static_cast<int32_t>(GLX_PBUFFER_BIT);
+ }
+ return i;
+}
+}
+
+gpu_context<backend::gl_linux_xcb>::gpu_context(const glx_library_extensions& ext_lib,
+ own<device<backend::linux_xcb>> dev, int visual_id, GLXContext context, GLXFBConfig fb_config): ext_lib_{ext_lib}, device_{std::move(dev)}, visual_id_{visual_id}, context_{context}, fb_config_{fb_config}{}
+
+gpu_context<backend::gl_linux_xcb>::~gpu_context(){
+ assert(device_);
+ assert(device_->get_xcb_display());
+
+ if(context_){
+ /// @todo Check if this context is bound and only unbind if that is the case
+ // ::glXMakeContextCurrent(device_->get_xcb_display(), None, None, nullptr);
+ ::glXDestroyContext(device_->get_xcb_display(), context_);
+ }
+ device_->flush();
+}
+
+own<gpu_window<backend::gl_linux_xcb>> gpu_context<backend::gl_linux_xcb>::create_window(const video_mode& vid_mode, std::string_view title_view){
+
+ SAW_ASSERT(device_){
+ return nullptr;
+ }
+
+ own<window<backend::linux_xcb>> win = device_->create_xcb_window(vid_mode, title_view, visual_id_);
+ if(!win){
+ return nullptr;
+ }
+
+ ::GLXWindow glx_win = glXCreateWindow(device_->get_xcb_display(), fb_config_, win->get_xcb_window_handle(), nullptr);
+ return heap<gpu_window<backend::gl_linux_xcb>>(std::move(win), *this, glx_win);
+}
+
+void gpu_context<backend::gl_linux_xcb>::flush(){
+ assert(device_);
+ if(device_){
+ device_->flush();
+ }
+}
+
+own<gpu_context<backend::gl_linux_xcb> > create_gl_context(io_provider& prov, const gl_settings& settings){
+ auto eodev = create_xcb_device(prov);
+ if(eodev.is_error()){
+ return nullptr;
+ }
+ own<device<backend::linux_xcb>>& device = eodev.get_value();
+ if (!device) {
+ return nullptr;
+ }
+
+ /*
+ * Translate all attributes
+ */
+ std::vector<int> attributes;
+ attributes.reserve(33);
+
+ attributes.push_back(GLX_X_RENDERABLE);
+ attributes.push_back(settings.renderable ? True : False);
+
+ attributes.push_back(GLX_RENDER_TYPE);
+ attributes.push_back(translate_render_type_settings(settings.render_t));
+
+ attributes.push_back(GLX_RED_SIZE);
+ attributes.push_back(settings.red_bits);
+
+ attributes.push_back(GLX_GREEN_SIZE);
+ attributes.push_back(settings.green_bits);
+
+ attributes.push_back(GLX_BLUE_SIZE);
+ attributes.push_back(settings.blue_bits);
+
+ attributes.push_back(GLX_ALPHA_SIZE);
+ attributes.push_back(settings.alpha_bits);
+
+ attributes.push_back(GLX_DEPTH_SIZE);
+ attributes.push_back(settings.depth_bits);
+
+ attributes.push_back(GLX_STENCIL_SIZE);
+ attributes.push_back(settings.stencil_bits);
+
+ attributes.push_back(GLX_DOUBLEBUFFER);
+ attributes.push_back(settings.double_buffer ? True : False);
+
+ attributes.push_back(GLX_DRAWABLE_TYPE);
+ attributes.push_back(
+ translate_drawable_type_settings(settings.drawable_t));
+
+ attributes.push_back(GLX_X_VISUAL_TYPE);
+ attributes.push_back(GLX_TRUE_COLOR);
+
+ attributes.push_back(None);
+
+ int num_fb_configs = 0;
+
+ glx_library_extensions lib_ext = load_glx_library_extensions(
+ glXQueryExtensionsString(device->get_xcb_display(), device->get_xcb_screen()));
+
+ GLXFBConfig *fb_configs = glXChooseFBConfig(
+ device->get_xcb_display(), device->get_xcb_screen(), &attributes[0], &num_fb_configs);
+ if (!fb_configs || num_fb_configs == 0) {
+ /// @todo log errors
+ return nullptr;
+ }
+
+ if (lib_ext.glXCreateContextAttribsARB) {
+ ::GLXFBConfig fb_config = fb_configs[0];
+
+ ::GLXContext context;
+
+ std::vector<int> glx_attribs;
+ glx_attribs.reserve(11);
+
+ glx_attribs.push_back(GLX_CONTEXT_MAJOR_VERSION_ARB);
+ glx_attribs.push_back(settings.gl_major);
+ glx_attribs.push_back(GLX_CONTEXT_MINOR_VERSION_ARB);
+ glx_attribs.push_back(settings.gl_minor);
+ glx_attribs.push_back(GLX_CONTEXT_PROFILE_MASK_ARB);
+ glx_attribs.push_back(settings.core_profile
+ ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB
+ : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB);
+ glx_attribs.push_back(None);
+
+ context = lib_ext.glXCreateContextAttribsARB(
+ device->get_xcb_display(), fb_config, NULL, True, &glx_attribs[0]);
+ ::XFree(fb_configs);
+ if (!context) {
+ return nullptr;
+ }
+
+ int visual_id = 0;
+ glXGetFBConfigAttrib(device->get_xcb_display(), fb_config, GLX_VISUAL_ID,
+ &visual_id);
+ return heap<gpu_context<backend::gl_linux_xcb>>(lib_ext, std::move(device), visual_id,
+ context, fb_config);
+ }
+
+ return nullptr;
+}
+
+gpu_window<backend::gl_linux_xcb>::gpu_window(own<window<backend::linux_xcb>> win, gpu_context<backend::gl_linux_xcb> &ctx,
+ ::GLXWindow glx_win)
+ : window_{std::move(win)}, context_{&ctx}, glx_window_handle_{glx_win} {}
+
+gpu_window<backend::gl_linux_xcb>::~gpu_window() {
+ assert(context_->device_);
+ if (context_->device_) {
+ ::glXDestroyWindow(context_->device_->get_xcb_display(), glx_window_handle_);
+ }
+}
+
+void gpu_window<backend::gl_linux_xcb>::bind() {
+ assert(window_ && context_->device_ && context_->device_->get_xcb_display());
+ if (window_) {
+ if (context_->device_ && context_->device_->get_xcb_display()) {
+ ::glXMakeContextCurrent(context_->device_->get_xcb_display(), glx_window_handle_,
+ glx_window_handle_, context_->context_);
+ }
+ }
+}
+
+void gpu_window<backend::gl_linux_xcb>::show() {
+ assert(window_);
+ if (window_) {
+ window_->show();
+ }
+}
+
+void gpu_window<backend::gl_linux_xcb>::hide() {
+ assert(window_);
+ if (window_) {
+ window_->hide();
+ }
+}
+
+void gpu_window<backend::gl_linux_xcb>::swap() {
+ assert(context_->device_);
+ assert(context_->device_->get_xcb_display());
+ if (context_->device_ && context_->device_->get_xcb_display()) {
+ ::glXSwapBuffers(context_->device_->get_xcb_display(), glx_window_handle_);
+ }
+}
+
+const video_mode &gpu_window<backend::gl_linux_xcb>::get_video_mode() const {
+ assert(window_);
+ return window_->get_video_mode();
+}
+
+const std::string_view gpu_window<backend::gl_linux_xcb>::get_title() const {
+ assert(window_);
+ if (window_) {
+ return window_->get_title();
+ }
+ // Kinky
+ return "Bad window";
+}
+
+void gpu_window<backend::gl_linux_xcb>::resize(size_t height, size_t width) {
+ assert(window_);
+ if (window_) {
+ window_->resize(height, width);
+ }
+}
+
+conveyor<data<schema::WindowEvents>> gpu_window<backend::gl_linux_xcb>::on_event() {
+ assert(window_);
+ return window_->on_event();
+}
+
+}
+}
diff --git a/modules/window-opengl/gl_xcb.h b/modules/window-opengl/gl_xcb.h
new file mode 100644
index 0000000..0d84662
--- /dev/null
+++ b/modules/window-opengl/gl_xcb.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <forstio/window/xcb.h>
+
+#ifndef SAW_OGL
+#error "OpenGL is not supported"
+#endif
+
+#include "gl_backends.h"
+#include "gl_context.h"
+#include "gl_window.h"
+
+#include <GL/glx.h>
+
+namespace saw {
+namespace gfx {
+
+struct glx_library_extensions {
+public:
+ std::string_view raw_extension_string;
+ GLXContext (*glXCreateContextAttribsARB)(Display*, GLXFBConfig, GLXContext, Bool, const int*) = nullptr;
+};
+
+template<>
+class gpu_context<backend::gl_linux_xcb> final {
+private:
+ glx_library_extensions ext_lib_;
+ own<device<backend::linux_xcb>> device_;
+ int visual_id_;
+ GLXContext context_;
+ GLXFBConfig fb_config_;
+
+ friend class gpu_window<backend::gl_linux_xcb>;
+public:
+ gpu_context(const glx_library_extensions&, own<device<backend::linux_xcb>>, int, GLXContext, GLXFBConfig);
+ ~gpu_context();
+
+ own<gpu_window<backend::gl_linux_xcb>> create_window(const video_mode&, std::string_view);
+
+ void flush();
+};
+
+template<>
+class gpu_window<backend::gl_linux_xcb> final {
+private:
+ own<window<backend::linux_xcb>> window_;
+ gpu_context<backend::gl_linux_xcb>* context_;
+
+ ::GLXWindow glx_window_handle_;
+public:
+ gpu_window(own<window<backend::linux_xcb>> window, gpu_context<backend::gl_linux_xcb>& ctx,
+ ::GLXWindow);
+ ~gpu_window();
+
+ void bind();
+ void swap();
+ void show();
+ void hide();
+
+ const video_mode& get_video_mode() const;
+ const std::string_view get_title() const;
+
+ void resize(size_t height, size_t width);
+
+ conveyor<data<schema::WindowEvents>> on_event();
+};
+}
+}
diff --git a/modules/window/.nix/derivation.nix b/modules/window/.nix/derivation.nix
new file mode 100644
index 0000000..67a682c
--- /dev/null
+++ b/modules/window/.nix/derivation.nix
@@ -0,0 +1,34 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+, xorg
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-window";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ forstio.codec
+ xorg.libX11
+ xorg.libxcb
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/window/SConscript b/modules/window/SConscript
new file mode 100644
index 0000000..bd830b9
--- /dev/null
+++ b/modules/window/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+window_env = env.Clone();
+
+window_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+window_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += window_env.sources;
+env.headers += window_env.headers;
+
+## Shared lib
+objects_shared = []
+window_env.add_source_files(objects_shared, window_env.sources, shared=True);
+window_env.library_shared = window_env.SharedLibrary('#build/forstio-window', [objects_shared]);
+
+## Static lib
+objects_static = []
+window_env.add_source_files(objects_static, window_env.sources, shared=False);
+window_env.library_static = window_env.StaticLibrary('#build/forstio-window', [objects_static]);
+
+# Set Alias
+env.Alias('library_window', [window_env.library_shared, window_env.library_static]);
+
+env.targets += ['library_window'];
+
+# Install
+env.Install('$prefix/lib/', [window_env.library_shared, window_env.library_static]);
+env.Install('$prefix/include/forstio/window/', [window_env.headers]);
diff --git a/modules/window/SConstruct b/modules/window/SConstruct
new file mode 100644
index 0000000..05fc016
--- /dev/null
+++ b/modules/window/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX', 'SAW_UNIX_XCB'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-core', 'forstio-io', 'forstio-async', 'forstio-codec'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/window/backends.h b/modules/window/backends.h
new file mode 100644
index 0000000..e129037
--- /dev/null
+++ b/modules/window/backends.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace saw {
+namespace gfx {
+namespace backend {
+struct linux_xcb {};
+struct wasm {};
+}
+}
+}
diff --git a/modules/window/device.h b/modules/window/device.h
new file mode 100644
index 0000000..7d3cdb1
--- /dev/null
+++ b/modules/window/device.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "window.h"
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+#include <forstio/codec/data.h>
+#include <forstio/io/io.h>
+
+#include <string_view>
+#include <variant>
+
+namespace saw {
+namespace gfx {
+template<typename T>
+class device;
+}
+}
diff --git a/modules/window/linux_xcb.h b/modules/window/linux_xcb.h
new file mode 100644
index 0000000..65ff94d
--- /dev/null
+++ b/modules/window/linux_xcb.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#ifdef SAW_UNIX_XCB
+#include "xcb.h"
+#endif
diff --git a/modules/window/old.dummy b/modules/window/old.dummy
new file mode 100644
index 0000000..c762945
--- /dev/null
+++ b/modules/window/old.dummy
@@ -0,0 +1,399 @@
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+#include <xcb/xcb.h>
+#include <forstio/io/io.h>
+
+#include <map>
+#include <vector>
+
+#include "device.h"
+
+namespace saw {
+class xcb_window;
+class xcb_device final : public device {
+public:
+ ::Display *display;
+ int screen;
+
+ xcb_connection_t *xcb_connection;
+ xcb_screen_t *xcb_screen;
+
+ own<input_stream> async_notifier;
+ conveyor_sink async_conveyor;
+
+ std::map<xcb_window_t, xcb_window *> windows;
+
+ std::vector<xcb_generic_event_t *> pending_events;
+
+public:
+ xcb_device(::Display *display, int screen, xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> &&an);
+ ~xcb_device();
+
+ void window_destroyed(xcb_window_t window_id);
+ void handle_events();
+
+ own<xcb_window> create_xcb_window(const video_mode &mode,
+ std::string_view title_view,
+ int visual_id);
+ own<window> create_window(const video_mode &video_mode,
+ std::string_view title_view) override;
+
+ void flush() override;
+};
+
+own<xcb_device> create_xcb_device(io_provider &provider);
+
+class xcb_window final : public window {
+public:
+ xcb_device &device_;
+
+ xcb_window_t xcb_window_;
+ xcb_colormap_t xcb_colormap_;
+
+ video_mode video_mode_;
+ std::string window_title_;
+
+ own<conveyor_feeder<window::variant_event>> event_feeder = nullptr;
+
+public:
+ xcb_window(xcb_device &dev, xcb_window_t xcb_win,
+ xcb_colormap_t xcb_colormap_, const video_mode &video_mode_,
+ std::string_view title_view_);
+ ~xcb_window();
+
+ void show() override;
+ void hide() override;
+
+ const video_mode &get_video_mode() const override;
+ const std::string_view title() const override;
+
+ void resize(size_t width, size_t height) override;
+
+ conveyor<window::variant_event> on_event() override;
+
+ void resize_event(size_t x, size_t y, size_t width, size_t height);
+ void mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed);
+ void mouse_move_event(int16_t x, int16_t y);
+ void keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed,
+ bool repeat);
+};
+
+xcb_device::xcb_device(::Display *display, int screen,
+ xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> &&an)
+ : display{display}, screen{screen}, xcb_connection{xcb_connection},
+ xcb_screen{xcb_screen}, async_notifier{std::move(an)},
+ async_conveyor{async_notifier->read_ready()
+ .then([this]() { handle_events(); })
+ .sink()} {}
+
+xcb_device::~xcb_device() {
+ if (display) {
+ xcb_flush(xcb_connection);
+ ::XCloseDisplay(display);
+ }
+}
+
+void xcb_device::window_destroyed(xcb_window_t window_id) {
+ windows.erase(window_id);
+}
+
+void xcb_device::handle_events() {
+ while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection)) {
+ pending_events.push_back(event);
+ }
+ for (size_t i = 0; i < pending_events.size(); ++i) {
+ xcb_generic_event_t *event = pending_events.at(i);
+ switch (event->response_type & ~0x80) {
+ case XCB_MOTION_NOTIFY: {
+ xcb_motion_notify_event_t *motion =
+ reinterpret_cast<xcb_motion_notify_event_t *>(event);
+ auto find = windows.find(motion->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_move_event(motion->event_x,
+ motion->event_y);
+ }
+ } break;
+ case XCB_EXPOSE: {
+ xcb_expose_event_t *expose =
+ reinterpret_cast<xcb_expose_event_t *>(event);
+ auto find = windows.find(expose->window);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->resize_event(static_cast<size_t>(expose->x),
+ static_cast<size_t>(expose->y),
+ static_cast<size_t>(expose->width),
+ static_cast<size_t>(expose->height));
+ }
+ } break;
+ case XCB_BUTTON_RELEASE: {
+ xcb_button_release_event_t *button =
+ reinterpret_cast<xcb_button_release_event_t *>(event);
+ auto find = windows.find(button->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, false);
+ }
+ } break;
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t *button =
+ reinterpret_cast<xcb_button_press_event_t *>(event);
+ auto find = windows.find(button->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, true);
+ }
+ } break;
+ case XCB_KEY_RELEASE: {
+ xcb_key_release_event_t *key =
+ reinterpret_cast<xcb_key_release_event_t *>(event);
+
+ bool repeat = false;
+ /*
+ * Peek into future events
+ */
+ for (size_t j = i + 1; j < pending_events.size(); ++j) {
+ xcb_generic_event_t *f_ev = pending_events.at(j);
+
+ if ((f_ev->response_type & ~0x80) == XCB_KEY_PRESS) {
+ xcb_key_press_event_t *f_key =
+ reinterpret_cast<xcb_key_press_event_t *>(f_ev);
+
+ if (key->detail == f_key->detail &&
+ key->event == f_key->event) {
+ auto iterator = pending_events.begin() + j;
+ assert(iterator != pending_events.end());
+ free(*iterator);
+ pending_events.erase(iterator);
+ repeat = true;
+ break;
+ }
+ }
+ }
+
+ auto find = windows.find(key->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, repeat, repeat);
+ }
+ } break;
+ case XCB_KEY_PRESS: {
+ xcb_key_press_event_t *key =
+ reinterpret_cast<xcb_key_press_event_t *>(event);
+ auto find = windows.find(key->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, true, false);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ for (xcb_generic_event_t *event : pending_events) {
+ free(event);
+ }
+ pending_events.clear();
+}
+
+own<xcb_window> xcb_device::create_xcb_window(const video_mode &video_mode,
+ std::string_view title_view,
+ int visual_id) {
+ assert(xcb_screen);
+ assert(xcb_connection);
+
+ xcb_colormap_t xcb_colormap = xcb_generate_id(xcb_connection);
+ xcb_window_t xcb_window = xcb_generate_id(xcb_connection);
+
+ xcb_create_colormap(xcb_connection, XCB_COLORMAP_ALLOC_NONE, xcb_colormap,
+ xcb_screen->root, visual_id);
+
+ uint32_t eventmask =
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
+ XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
+ XCB_EVENT_MASK_BUTTON_MOTION;
+ uint32_t valuelist[] = {eventmask, xcb_colormap, 0};
+ uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+
+ xcb_create_window(xcb_connection, XCB_COPY_FROM_PARENT, xcb_window,
+ xcb_screen->root, 0, 0, video_mode.width,
+ video_mode.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ visual_id, valuemask, valuelist);
+
+ xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, xcb_window,
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title_view.size(),
+ title_view.data());
+
+ xcb_flush(xcb_connection);
+ auto window = heap<class xcb_window>(*this, xcb_window, xcb_colormap,
+ video_mode, title_view);
+ windows[xcb_window] = window.get();
+
+ return window;
+}
+
+own<window> xcb_device::create_window(const video_mode &video_mode,
+ std::string_view title_view) {
+ assert(xcb_screen);
+ return create_xcb_window(video_mode, title_view, xcb_screen->root_visual);
+}
+
+void xcb_device::flush() {
+ assert(xcb_connection);
+ xcb_flush(xcb_connection);
+}
+
+own<xcb_device> create_xcb_device(io_provider &provider) {
+ ::Display *display = ::XOpenDisplay(nullptr);
+ if (!display) {
+ /// @todo log errors
+ return nullptr;
+ }
+
+ int screen = ::XDefaultScreen(display);
+
+ xcb_connection_t *xcb_connection = ::XGetXCBConnection(display);
+ if (!xcb_connection) {
+ /// @todo log errors
+ ::XCloseDisplay(display);
+ return nullptr;
+ }
+
+ int fd = xcb_get_file_descriptor(xcb_connection);
+
+ own<input_stream> fd_wrapped = provider.wrap_input_fd(fd);
+ if (!fd_wrapped) {
+ ::XCloseDisplay(display);
+ return nullptr;
+ }
+
+ ::XSetEventQueueOwner(display, XCBOwnsEventQueue);
+
+ xcb_screen_iterator_t screen_iter =
+ xcb_setup_roots_iterator(xcb_get_setup(xcb_connection));
+ for (int screen_i = screen; screen_iter.rem && screen_i > 0;
+ --screen_i, xcb_screen_next(&screen_iter))
+ ;
+
+ xcb_screen_t *xcb_screen = screen_iter.data;
+
+ return heap<xcb_device>(display, screen, xcb_connection, xcb_screen,
+ std::move(fd_wrapped));
+}
+
+own<device> createdevice(io_provider &provider) {
+ return create_xcb_device(provider);
+}
+
+xcb_window::xcb_window(xcb_device &dev, xcb_window_t xcb_win,
+ xcb_colormap_t xcb_colmap, const video_mode &vid_mode,
+ std::string_view title_view_)
+ : device_{dev}, xcb_window_{xcb_win}, xcb_colormap_{xcb_colmap},
+ video_mode_{vid_mode}, window_title_{title_view_} {}
+
+xcb_window::~xcb_window() {
+ device_.window_destroyed(xcb_window_);
+ xcb_destroy_window(device_.xcb_connection, xcb_window_);
+ device_.flush();
+}
+
+void xcb_window::show() {
+ assert(device_.xcb_connection);
+ xcb_map_window(device_.xcb_connection, xcb_window_);
+}
+
+void xcb_window::hide() {
+ assert(device_.xcb_connection);
+ xcb_unmap_window(device_.xcb_connection, xcb_window_);
+}
+
+const video_mode &xcb_window::get_video_mode() const { return video_mode_; }
+
+const std::string_view xcb_window::title() const { return window_title_; }
+
+void xcb_window::resize(size_t width, size_t height) {
+ const uint32_t values[2] = {static_cast<uint32_t>(width),
+ static_cast<uint32_t>(height)};
+
+ xcb_configure_window(device_.xcb_connection, xcb_window_,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+ video_mode_.width = width;
+ video_mode_.height = height;
+}
+
+conveyor<window::variant_event> xcb_window::on_event() {
+ auto caf = new_conveyor_and_feeder<window::variant_event>();
+ event_feeder = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void xcb_window::resize_event(size_t x, size_t y, size_t width, size_t height) {
+ (void)x;
+ (void)y;
+ /// @todo maybe include x and y?
+ video_mode_.width = width;
+ video_mode_.height = height;
+
+ if (event_feeder) {
+ event_feeder->feed(
+ window::variant_event{window::event::resize{width, height}});
+ }
+}
+
+void xcb_window::mouse_event(int16_t x, int16_t y, uint16_t state,
+ bool pressed) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(window::variant_event{
+ window::event::mouse{state, pressed, ux, uy}});
+ }
+}
+
+void xcb_window::mouse_move_event(int16_t x, int16_t y) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(
+ window::variant_event{window::event::mouse_move{ux, uy}});
+ }
+}
+
+void xcb_window::keyboard_event(int16_t x, int16_t y, uint32_t keycode,
+ bool pressed, bool repeat) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(window::variant_event{
+ window::event::keyboard{keycode, keycode, pressed, repeat}});
+ }
+}
+
+} // namespace saw
diff --git a/modules/window/video_mode.h b/modules/window/video_mode.h
new file mode 100644
index 0000000..a8f1695
--- /dev/null
+++ b/modules/window/video_mode.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <cstddef>
+
+namespace saw {
+class video_mode {
+public:
+ size_t width = 64;
+ size_t height = 64;
+};
+} // namespace saw
diff --git a/modules/window/window.h b/modules/window/window.h
new file mode 100644
index 0000000..36786de
--- /dev/null
+++ b/modules/window/window.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "video_mode.h"
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+#include <forstio/codec/schema.h>
+
+#include <string_view>
+#include <variant>
+
+namespace saw {
+namespace gfx {
+namespace schema {
+using namespace saw::schema;
+using WindowResize = Struct<
+ Member<UInt32, "width">,
+ Member<UInt32, "height">
+>;
+using WindowEvents = Union<
+ Member<WindowResize, "resize">
+>;
+}
+
+template<typename T>
+class window;
+}
+}
+
+#include "linux_xcb.h"
+
+/**
+namespace saw {
+class window {
+public:
+ class event {
+ public:
+ struct resize {
+ size_t width;
+ size_t height;
+ };
+
+ struct keyboard {
+ uint32_t key;
+ uint32_t scan;
+ bool pressed;
+ bool repeat;
+ };
+
+ struct mouse {
+ uint16_t button_mask;
+ bool pressed;
+ uint32_t x;
+ uint32_t y;
+ };
+
+ struct mouse_move {
+ uint32_t x;
+ uint32_t y;
+ };
+ };
+
+ using variant_event = std::variant<event::resize, event::keyboard,
+ event::mouse, event::mouse_move>;
+
+ virtual ~window() = default;
+
+ virtual void show() = 0;
+ virtual void hide() = 0;
+
+ virtual const video_mode &get_video_mode() const = 0;
+ virtual const std::string_view title() const = 0;
+
+ virtual void resize(size_t width, size_t height) = 0;
+
+ virtual conveyor<variant_event> on_event() = 0;
+};
+} // namespace saw
+*/
diff --git a/modules/window/xcb.cpp b/modules/window/xcb.cpp
new file mode 100644
index 0000000..1b804ba
--- /dev/null
+++ b/modules/window/xcb.cpp
@@ -0,0 +1,290 @@
+#ifndef SAW_UNIX_XCB
+#error "XCB is not supported"
+#endif
+
+#include "xcb.h"
+
+namespace saw {
+namespace gfx {
+device<backend::linux_xcb>::device(::Display* disp, int screen, xcb_connection_t *xcb_connection, xcb_screen_t *xcb_screen, own<input_stream>&& an):
+ display_{disp}, screen_{screen}, xcb_connection_{xcb_connection}, xcb_screen_{xcb_screen}, async_notifier_{std::move(an)},
+ async_conveyor_{async_notifier_->read_ready()
+ .then([this]() { handle_events(); })
+ .sink()} {}
+
+device<backend::linux_xcb>::~device(){
+ if (display_) {
+ xcb_flush(xcb_connection_);
+ ::XCloseDisplay(display_);
+ }
+}
+
+void device<backend::linux_xcb>::xcb_window_was_destroyed(xcb_window_t window_id){
+ windows_.erase(window_id);
+}
+
+void device<backend::linux_xcb>::handle_events(){
+ while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection_)) {
+ pending_events_.push_back(event);
+ }
+ for (size_t i = 0; i < pending_events_.size(); ++i) {
+ xcb_generic_event_t *event = pending_events_.at(i);
+ switch (event->response_type & ~0x80) {
+ case XCB_MOTION_NOTIFY: {
+ xcb_motion_notify_event_t *motion =
+ reinterpret_cast<xcb_motion_notify_event_t *>(event);
+ auto find = windows_.find(motion->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_move_event(motion->event_x,
+ motion->event_y);
+ }
+ } break;
+ case XCB_EXPOSE: {
+ xcb_expose_event_t *expose =
+ reinterpret_cast<xcb_expose_event_t *>(event);
+ auto find = windows_.find(expose->window);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->resize_event(static_cast<size_t>(expose->x),
+ static_cast<size_t>(expose->y),
+ static_cast<size_t>(expose->width),
+ static_cast<size_t>(expose->height));
+ }
+ } break;
+ case XCB_BUTTON_RELEASE: {
+ xcb_button_release_event_t *button =
+ reinterpret_cast<xcb_button_release_event_t *>(event);
+ auto find = windows_.find(button->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, false);
+ }
+ } break;
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t *button =
+ reinterpret_cast<xcb_button_press_event_t *>(event);
+ auto find = windows_.find(button->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, true);
+ }
+ } break;
+ case XCB_KEY_RELEASE: {
+ xcb_key_release_event_t *key =
+ reinterpret_cast<xcb_key_release_event_t *>(event);
+
+ bool repeat = false;
+ /*
+ * Peek into future events
+ */
+ for (size_t j = i + 1; j < pending_events_.size(); ++j) {
+ xcb_generic_event_t *f_ev = pending_events_.at(j);
+
+ if ((f_ev->response_type & ~0x80) == XCB_KEY_PRESS) {
+ xcb_key_press_event_t *f_key =
+ reinterpret_cast<xcb_key_press_event_t *>(f_ev);
+
+ if (key->detail == f_key->detail &&
+ key->event == f_key->event) {
+ auto iterator = pending_events_.begin() + j;
+ assert(iterator != pending_events_.end());
+ free(*iterator);
+ pending_events_.erase(iterator);
+ repeat = true;
+ break;
+ }
+ }
+ }
+
+ auto find = windows_.find(key->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, repeat, repeat);
+ }
+ } break;
+ case XCB_KEY_PRESS: {
+ xcb_key_press_event_t *key =
+ reinterpret_cast<xcb_key_press_event_t *>(event);
+ auto find = windows_.find(key->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, true, false);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ for (xcb_generic_event_t *event : pending_events_) {
+ free(event);
+ }
+ pending_events_.clear();
+}
+
+own<window<backend::linux_xcb>> device<backend::linux_xcb>::create_xcb_window(const video_mode &vid_mode,
+ std::string_view title_view,
+ int visual_id) {
+ assert(xcb_screen_);
+ assert(xcb_connection_);
+
+ xcb_colormap_t xcb_colormap = xcb_generate_id(xcb_connection_);
+ xcb_window_t xcb_window = xcb_generate_id(xcb_connection_);
+
+ xcb_create_colormap(xcb_connection_, XCB_COLORMAP_ALLOC_NONE, xcb_colormap,
+ xcb_screen_->root, visual_id);
+
+ uint32_t eventmask =
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
+ XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
+ XCB_EVENT_MASK_BUTTON_MOTION;
+ uint32_t valuelist[] = {eventmask, xcb_colormap, 0};
+ uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+
+ xcb_create_window(xcb_connection_, XCB_COPY_FROM_PARENT, xcb_window,
+ xcb_screen_->root, 0, 0, vid_mode.width,
+ vid_mode.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ visual_id, valuemask, valuelist);
+
+ xcb_change_property(xcb_connection_, XCB_PROP_MODE_REPLACE, xcb_window,
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title_view.size(),
+ title_view.data());
+
+ xcb_flush(xcb_connection_);
+ auto win = heap<window<backend::linux_xcb>>(*this, xcb_window, xcb_colormap,
+ vid_mode, title_view);
+ // Insert into map
+ windows_[xcb_window] = win.get();
+
+ return win;
+}
+
+own<window<backend::linux_xcb>> device<backend::linux_xcb>::create_window(const video_mode& vid_mode, std::string_view title_view){
+ assert(xcb_screen_);
+ return create_xcb_window(vid_mode, title_view, xcb_screen_->root_visual);
+}
+
+void device<backend::linux_xcb>::flush(){
+ assert(xcb_connection_);
+ xcb_flush(xcb_connection_);
+}
+
+error_or<own<device<backend::linux_xcb>>> create_xcb_device(io_provider& provider){
+ ::Display *display = ::XOpenDisplay(nullptr);
+ if (!display) {
+ /// @todo log errors
+ return make_error<err::critical>();
+ }
+
+ int screen = ::XDefaultScreen(display);
+
+ xcb_connection_t *xcb_connection = ::XGetXCBConnection(display);
+ if (!xcb_connection) {
+ /// @todo log errors
+ ::XCloseDisplay(display);
+ return make_error<err::critical>();
+ }
+
+ int fd = xcb_get_file_descriptor(xcb_connection);
+
+ own<input_stream> fd_wrapped = provider.wrap_input_fd(fd);
+ if (!fd_wrapped) {
+ ::XCloseDisplay(display);
+ return make_error<err::critical>();
+ }
+
+ ::XSetEventQueueOwner(display, XCBOwnsEventQueue);
+
+ xcb_screen_iterator_t screen_iter =
+ xcb_setup_roots_iterator(xcb_get_setup(xcb_connection));
+ for (int screen_i = screen; screen_iter.rem && screen_i > 0;
+ --screen_i, xcb_screen_next(&screen_iter))
+ ;
+
+ xcb_screen_t *xcb_screen = screen_iter.data;
+
+ return heap<device<backend::linux_xcb>>(display, screen, xcb_connection, xcb_screen, std::move(fd_wrapped));
+}
+
+window<backend::linux_xcb>::window(device<backend::linux_xcb>& dev_, xcb_window_t xcb_win, xcb_colormap_t xcb_colormap_, const video_mode& vid_mode_, const std::string_view& title_view_):
+ device_{&dev_},
+ xcb_window_{xcb_win},
+ xcb_colormap_{xcb_colormap_},
+ video_mode_{vid_mode_},
+ window_title_{title_view_}
+{}
+
+window<backend::linux_xcb>::~window(){
+ assert(device_);
+ device_->xcb_window_was_destroyed(xcb_window_);
+ xcb_destroy_window(device_->xcb_connection_, xcb_window_);
+ device_->flush();
+}
+
+void window<backend::linux_xcb>::show(){
+ assert(device_->xcb_connection_);
+ xcb_map_window(device_->xcb_connection_, xcb_window_);
+}
+
+void window<backend::linux_xcb>::hide(){
+ assert(device_->xcb_connection_);
+ xcb_unmap_window(device_->xcb_connection_, xcb_window_);
+}
+
+const video_mode& window<backend::linux_xcb>::get_video_mode() const {
+ return video_mode_;
+}
+
+const std::string_view window<backend::linux_xcb>::get_title() const {
+ return window_title_;
+}
+
+void window<backend::linux_xcb>::resize(uint64_t w, uint64_t h){
+ const uint32_t values[2] = {
+ static_cast<uint32_t>(w),
+ static_cast<uint32_t>(h)
+ };
+
+ xcb_configure_window(device_->xcb_connection_, xcb_window_,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+
+ video_mode_.width = w;
+ video_mode_.height = h;
+}
+
+conveyor<data<schema::WindowEvents>> window<backend::linux_xcb>::on_event() {
+ auto caf = new_conveyor_and_feeder<data<schema::WindowEvents>>();
+ event_feeder = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void window<backend::linux_xcb>::resize_event(uint64_t x, uint64_t y, uint64_t width, uint64_t height){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::mouse_move_event(int16_t x, int16_t y){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat){
+ /// @todo implement
+ assert(false);
+}
+
+xcb_window_t window<backend::linux_xcb>::get_xcb_window_handle() const{
+ return xcb_window_;
+}
+
+}
+}
diff --git a/modules/window/xcb.h b/modules/window/xcb.h
new file mode 100644
index 0000000..a2a9b0b
--- /dev/null
+++ b/modules/window/xcb.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#ifndef SAW_UNIX_XCB
+#error "XCB is not supported"
+#endif
+
+#include "backends.h"
+#include "device.h"
+#include "window.h"
+
+#include <map>
+
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+
+namespace saw {
+namespace gfx {
+template<typename T>
+class window;
+
+template<typename T>
+class device;
+
+template<>
+class device<backend::linux_xcb> final {
+private:
+ ::Display *display_;
+ int screen_;
+
+ xcb_connection_t *xcb_connection_;
+ xcb_screen_t *xcb_screen_;
+
+ own<input_stream> async_notifier_;
+ conveyor_sink async_conveyor_;
+
+ std::map<xcb_window_t, window<backend::linux_xcb> *> windows_;
+
+ std::vector<xcb_generic_event_t *> pending_events_;
+
+ friend class window<backend::linux_xcb>;
+public:
+ own<window<backend::linux_xcb>> create_xcb_window(const video_mode& vid_mod, std::string_view title_view, int visual_id);
+ void xcb_window_was_destroyed(xcb_window_t window_id);
+public:
+ device(::Display *display, int screen, xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> && an);
+
+ ~device();
+
+ void handle_events();
+
+ own<window<backend::linux_xcb>> create_window(const video_mode& vid_mod, std::string_view title_view);
+
+ void flush();
+
+ // XCB specific info for other classes
+ ::Display* get_xcb_display() {
+ return display_;
+ }
+
+ int get_xcb_screen() const {
+ return screen_;
+ }
+};
+
+error_or<own<device<backend::linux_xcb>>> create_xcb_device(io_provider& provider);
+
+template<>
+class window<backend::linux_xcb> final {
+private:
+ device<backend::linux_xcb> *device_;
+
+ xcb_window_t xcb_window_;
+ xcb_colormap_t xcb_colormap_;
+
+ video_mode video_mode_;
+ std::string window_title_;
+
+ own<conveyor_feeder<data<schema::WindowEvents>>> event_feeder = nullptr;
+public:
+ window(device<backend::linux_xcb>& dev_, xcb_window_t xcb_win, xcb_colormap_t xcb_colormap_, const video_mode& vid_mode_, const std::string_view& title_view_);
+
+ ~window();
+
+ void show();
+ void hide();
+
+ const video_mode& get_video_mode() const;
+
+ const std::string_view get_title() const;
+
+ void resize(uint64_t width, uint64_t height);
+
+ conveyor<data<schema::WindowEvents>> on_event();
+
+ void resize_event(uint64_t x, uint64_t y, uint64_t width, uint64_t height);
+ void mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed);
+ void mouse_move_event(int16_t x, int16_t y);
+ void keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat);
+
+ // XCB specific things
+ xcb_window_t get_xcb_window_handle() const;
+};
+}
+}