diff options
author | Claudius "keldu" Holeksa <mail@keldu.de> | 2023-07-20 17:02:05 +0200 |
---|---|---|
committer | Claudius "keldu" Holeksa <mail@keldu.de> | 2023-07-20 17:02:05 +0200 |
commit | fac9e8bec1983fa9dff8f447fef106e427dfec26 (patch) | |
tree | 2221d4216873fa8250dd5ff45f00d0d6b46eab26 /c++ | |
parent | 398164432abcf599eaa51ebc4088024b7f46b97f (diff) |
c++: Renamed src to c++
Diffstat (limited to 'c++')
67 files changed, 9387 insertions, 0 deletions
diff --git a/c++/SConscript b/c++/SConscript new file mode 100644 index 0000000..8da5a3d --- /dev/null +++ b/c++/SConscript @@ -0,0 +1,8 @@ +#!/bin/false + +Import('env') + +# Export to other libs +Export('env'); +SConscript('core/SConscript'); +SConscript('async/SConscript'); diff --git a/c++/async/.nix/derivation.nix b/c++/async/.nix/derivation.nix new file mode 100644 index 0000000..b1c6ecc --- /dev/null +++ b/c++/async/.nix/derivation.nix @@ -0,0 +1,30 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-async"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/async/SConscript b/c++/async/SConscript new file mode 100644 index 0000000..69f8950 --- /dev/null +++ b/c++/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/c++/async/SConstruct b/c++/async/SConstruct new file mode 100644 index 0000000..0d7b7c6 --- /dev/null +++ b/c++/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/c++/async/async.cpp b/c++/async/async.cpp new file mode 100644 index 0000000..360e455 --- /dev/null +++ b/c++/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 ¤t_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/c++/async/async.h b/c++/async/async.h new file mode 100644 index 0000000..8190be0 --- /dev/null +++ b/c++/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/c++/async/async.tmpl.h b/c++/async/async.tmpl.h new file mode 100644 index 0000000..9569f60 --- /dev/null +++ b/c++/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/c++/codec-json/.nix/derivation.nix b/c++/codec-json/.nix/derivation.nix new file mode 100644 index 0000000..0f701c9 --- /dev/null +++ b/c++/codec-json/.nix/derivation.nix @@ -0,0 +1,32 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-codec-json"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + forstio.codec + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/codec-json/SConscript b/c++/codec-json/SConscript new file mode 100644 index 0000000..772ac0b --- /dev/null +++ b/c++/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/c++/codec-json/SConstruct b/c++/codec-json/SConstruct new file mode 100644 index 0000000..edd5f57 --- /dev/null +++ b/c++/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/c++/codec-json/json.h b/c++/codec-json/json.h new file mode 100644 index 0000000..1fe6bb5 --- /dev/null +++ b/c++/codec-json/json.h @@ -0,0 +1,116 @@ +#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(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/c++/codec-json/json.tmpl.h b/c++/codec-json/json.tmpl.h new file mode 100644 index 0000000..4b3a1a2 --- /dev/null +++ b/c++/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/c++/codec/.nix/derivation.nix b/c++/codec/.nix/derivation.nix new file mode 100644 index 0000000..768dc6e --- /dev/null +++ b/c++/codec/.nix/derivation.nix @@ -0,0 +1,30 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-codec"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + buildInputs = [ + forstio.core + ]; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/codec/SConscript b/c++/codec/SConscript new file mode 100644 index 0000000..c038d42 --- /dev/null +++ b/c++/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/c++/codec/SConstruct b/c++/codec/SConstruct new file mode 100644 index 0000000..0d7b7c6 --- /dev/null +++ b/c++/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/c++/codec/data.h b/c++/codec/data.h new file mode 100644 index 0000000..8ff06dc --- /dev/null +++ b/c++/codec/data.h @@ -0,0 +1,317 @@ +#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){ + 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/c++/codec/forst.h b/c++/codec/forst.h new file mode 100644 index 0000000..cadf78e --- /dev/null +++ b/c++/codec/forst.h @@ -0,0 +1,13 @@ +#pragma once + +#include "data.h" + +namespace saw { +namespace encode { +struct KelForst {}; +} + +class data<schema::String, encode::KelForst> { + +}; +} diff --git a/c++/codec/schema.h b/c++/codec/schema.h new file mode 100644 index 0000000..2f63fe9 --- /dev/null +++ b/c++/codec/schema.h @@ -0,0 +1,93 @@ +#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 + */ +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 enabling Rpc calls + */ +template <class Request, class Response, string_literal Literal> +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... Request, class... Response, string_literal... Literal> +struct Interface<Function<Request, Response, Literal>...> {}; + +// NOLINTEND +} // namespace schema +} // namespace saw diff --git a/c++/codec/simple.h b/c++/codec/simple.h new file mode 100644 index 0000000..8760754 --- /dev/null +++ b/c++/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/c++/codec/stream_value.h b/c++/codec/stream_value.h new file mode 100644 index 0000000..09203cb --- /dev/null +++ b/c++/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/c++/core/.nix/derivation.nix b/c++/core/.nix/derivation.nix new file mode 100644 index 0000000..26acd3d --- /dev/null +++ b/c++/core/.nix/derivation.nix @@ -0,0 +1,23 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +}: + +stdenvNoCC.mkDerivation { + pname = "forstio-core"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/core/SConscript b/c++/core/SConscript new file mode 100644 index 0000000..04eb4c3 --- /dev/null +++ b/c++/core/SConscript @@ -0,0 +1,38 @@ +#!/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]); diff --git a/c++/core/SConstruct b/c++/core/SConstruct new file mode 100644 index 0000000..865d131 --- /dev/null +++ b/c++/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/c++/core/buffer.cpp b/c++/core/buffer.cpp new file mode 100644 index 0000000..15f4cae --- /dev/null +++ b/c++/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/c++/core/buffer.h b/c++/core/buffer.h new file mode 100644 index 0000000..f0cf76e --- /dev/null +++ b/c++/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/c++/core/common.h b/c++/core/common.h new file mode 100644 index 0000000..a06c238 --- /dev/null +++ b/c++/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/c++/core/error.cpp b/c++/core/error.cpp new file mode 100644 index 0000000..360e628 --- /dev/null +++ b/c++/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/c++/core/error.h b/c++/core/error.h new file mode 100644 index 0000000..e816734 --- /dev/null +++ b/c++/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/c++/core/string_literal.h b/c++/core/string_literal.h new file mode 100644 index 0000000..d530a54 --- /dev/null +++ b/c++/core/string_literal.h @@ -0,0 +1,40 @@ +#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: + 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 <typename T, T... Chars> +constexpr string_literal<T, sizeof...(Chars)> operator""_key() { + return string_literal<T, sizeof...(Chars) + 1u>{Chars..., '\0'}; +} +} // namespace saw diff --git a/c++/core/templates.h b/c++/core/templates.h new file mode 100644 index 0000000..39befc1 --- /dev/null +++ b/c++/core/templates.h @@ -0,0 +1,76 @@ +#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 in list"); +}; + +} diff --git a/c++/io-tls/.nix/derivation.nix b/c++/io-tls/.nix/derivation.nix new file mode 100644 index 0000000..a08b195 --- /dev/null +++ b/c++/io-tls/.nix/derivation.nix @@ -0,0 +1,34 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +, gnutls +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-io-tls"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + forstio.io + gnutls + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/io-tls/SConscript b/c++/io-tls/SConscript new file mode 100644 index 0000000..4f88f37 --- /dev/null +++ b/c++/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/c++/io-tls/SConstruct b/c++/io-tls/SConstruct new file mode 100644 index 0000000..fbd8657 --- /dev/null +++ b/c++/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/c++/io-tls/tls.cpp b/c++/io-tls/tls.cpp new file mode 100644 index 0000000..9fa143c --- /dev/null +++ b/c++/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/c++/io-tls/tls.h b/c++/io-tls/tls.h new file mode 100644 index 0000000..74b39ff --- /dev/null +++ b/c++/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/c++/io/.nix/derivation.nix b/c++/io/.nix/derivation.nix new file mode 100644 index 0000000..39d7568 --- /dev/null +++ b/c++/io/.nix/derivation.nix @@ -0,0 +1,31 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-io"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/io/SConscript b/c++/io/SConscript new file mode 100644 index 0000000..62ad58a --- /dev/null +++ b/c++/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/c++/io/SConstruct b/c++/io/SConstruct new file mode 100644 index 0000000..4cccf82 --- /dev/null +++ b/c++/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/c++/io/io.cpp b/c++/io/io.cpp new file mode 100644 index 0000000..f0705d2 --- /dev/null +++ b/c++/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/c++/io/io.h b/c++/io/io.h new file mode 100644 index 0000000..7653ace --- /dev/null +++ b/c++/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/c++/io/io_helpers.cpp b/c++/io/io_helpers.cpp new file mode 100644 index 0000000..c2cf2be --- /dev/null +++ b/c++/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/c++/io/io_helpers.h b/c++/io/io_helpers.h new file mode 100644 index 0000000..94e37f4 --- /dev/null +++ b/c++/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/c++/io/io_unix.cpp b/c++/io/io_unix.cpp new file mode 100644 index 0000000..c3b4f17 --- /dev/null +++ b/c++/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/c++/io_codec/.nix/derivation.nix b/c++/io_codec/.nix/derivation.nix new file mode 100644 index 0000000..2ea0913 --- /dev/null +++ b/c++/io_codec/.nix/derivation.nix @@ -0,0 +1,33 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-io_codec"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + forstio.io + forstio.codec + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/io_codec/SConscript b/c++/io_codec/SConscript new file mode 100644 index 0000000..0afd6d6 --- /dev/null +++ b/c++/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/c++/io_codec/SConstruct b/c++/io_codec/SConstruct new file mode 100644 index 0000000..4e6e150 --- /dev/null +++ b/c++/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/c++/io_codec/io_peer.h b/c++/io_codec/io_peer.h new file mode 100644 index 0000000..b9a4b34 --- /dev/null +++ b/c++/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/c++/io_codec/io_peer.tmpl.h b/c++/io_codec/io_peer.tmpl.h new file mode 100644 index 0000000..880a58a --- /dev/null +++ b/c++/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/c++/test/.nix/derivation.nix b/c++/test/.nix/derivation.nix new file mode 100644 index 0000000..750d8ed --- /dev/null +++ b/c++/test/.nix/derivation.nix @@ -0,0 +1,30 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-test"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/test/SConscript b/c++/test/SConscript new file mode 100644 index 0000000..6379b24 --- /dev/null +++ b/c++/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/c++/test/SConstruct b/c++/test/SConstruct new file mode 100644 index 0000000..0d7b7c6 --- /dev/null +++ b/c++/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/c++/test/suite.cpp b/c++/test/suite.cpp new file mode 100644 index 0000000..0fca8f9 --- /dev/null +++ b/c++/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/c++/test/suite.h b/c++/test/suite.h new file mode 100644 index 0000000..34f29bf --- /dev/null +++ b/c++/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/c++/window-opengl/.nix/derivation.nix b/c++/window-opengl/.nix/derivation.nix new file mode 100644 index 0000000..95cd318 --- /dev/null +++ b/c++/window-opengl/.nix/derivation.nix @@ -0,0 +1,37 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +, xorg +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-window-opengl"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + forstio.io + forstio.codec + forstio.window + xorg.libX11 + xorg.libxcb + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/window-opengl/SConscript b/c++/window-opengl/SConscript new file mode 100644 index 0000000..bd830b9 --- /dev/null +++ b/c++/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', [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/c++/window-opengl/SConstruct b/c++/window-opengl/SConstruct new file mode 100644 index 0000000..05fc016 --- /dev/null +++ b/c++/window-opengl/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/c++/window/.nix/derivation.nix b/c++/window/.nix/derivation.nix new file mode 100644 index 0000000..9974564 --- /dev/null +++ b/c++/window/.nix/derivation.nix @@ -0,0 +1,36 @@ +{ lib +, stdenvNoCC +, scons +, clang +, clang-tools +, version +, forstio +, xorg +}: + +let + +in stdenvNoCC.mkDerivation { + pname = "forstio-window"; + inherit version; + src = ./..; + + enableParallelBuilding = true; + + nativeBuildInputs = [ + scons + clang + clang-tools + ]; + + buildInputs = [ + forstio.core + forstio.async + forstio.io + forstio.codec + xorg.libX11 + xorg.libxcb + ]; + + outputs = ["out" "dev"]; +} diff --git a/c++/window/SConscript b/c++/window/SConscript new file mode 100644 index 0000000..bd830b9 --- /dev/null +++ b/c++/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/c++/window/SConstruct b/c++/window/SConstruct new file mode 100644 index 0000000..05fc016 --- /dev/null +++ b/c++/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/c++/window/backends.h b/c++/window/backends.h new file mode 100644 index 0000000..e129037 --- /dev/null +++ b/c++/window/backends.h @@ -0,0 +1,10 @@ +#pragma once + +namespace saw { +namespace gfx { +namespace backend { +struct linux_xcb {}; +struct wasm {}; +} +} +} diff --git a/c++/window/device.h b/c++/window/device.h new file mode 100644 index 0000000..7d3cdb1 --- /dev/null +++ b/c++/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/c++/window/linux_xcb.h b/c++/window/linux_xcb.h new file mode 100644 index 0000000..65ff94d --- /dev/null +++ b/c++/window/linux_xcb.h @@ -0,0 +1,5 @@ +#pragma once + +#ifdef SAW_UNIX_XCB +#include "xcb.h" +#endif diff --git a/c++/window/old.dummy b/c++/window/old.dummy new file mode 100644 index 0000000..c762945 --- /dev/null +++ b/c++/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/c++/window/video_mode.h b/c++/window/video_mode.h new file mode 100644 index 0000000..a8f1695 --- /dev/null +++ b/c++/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/c++/window/window.h b/c++/window/window.h new file mode 100644 index 0000000..36786de --- /dev/null +++ b/c++/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/c++/window/xcb.cpp b/c++/window/xcb.cpp new file mode 100644 index 0000000..0a27643 --- /dev/null +++ b/c++/window/xcb.cpp @@ -0,0 +1,238 @@ +#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_} +{ + // TODO +} + +window<backend::linux_xcb>::~window(){ + // TODO +} + +void window<backend::linux_xcb>::show(){ + // TODO +} + +void window<backend::linux_xcb>::hide(){ + // TODO +} + +} +} diff --git a/c++/window/xcb.h b/c++/window/xcb.h new file mode 100644 index 0000000..f4e9b9a --- /dev/null +++ b/c++/window/xcb.h @@ -0,0 +1,91 @@ +#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_; +private: + own<window<backend::linux_xcb>> create_xcb_window(const video_mode& vid_mod, std::string_view title_view, int visual_id); +public: + device(::Display *display, int screen, xcb_connection_t *xcb_connection, + xcb_screen_t *xcb_screen, own<input_stream> && an); + + ~device(); + + void xcb_window_was_destroyed(xcb_window_t window_id); + void handle_events(); + + own<window<backend::linux_xcb>> create_window(const video_mode& vid_mod, std::string_view title_view); + + void flush(); +}; + +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); +}; +} +} |