.. _program_listing_file_falcon_sharedstate.hpp: Program Listing for File sharedstate.hpp ======================================== |exhale_lsh| :ref:`Return to documentation for file ` (``falcon/sharedstate.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp // --------------------------------------------------------------------- // This file is part of falcon-core. // // Copyright (C) 2015, 2016, 2017 Neuro-Electronics Research Flanders // // Falcon-server is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Falcon-server is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with falcon-core. If not, see . // --------------------------------------------------------------------- #pragma once #include #include #include #include #include #include #include #include #include #include #include "logging/log.hpp" #include "yaml-cpp/yaml.h" // a state wraps a value that is possibly shared between threads // access to the value should be protected (atomic or lock) // a state has a non-shared cached value that is synchronized with every set/get // operation (optional) on the state the cache is useful for checking if the // value was changed outside of the state since the last set/get operation // TODO: take state retrieval/update out of IProcessor and make it // responsibility of ProcessorGraph IProcessors should only be allowed to // set/get state value (and not change permissions, (un)share, etc.) // ProcessorGraph is responsible for moderating external access (including // permission check) ProcessorGraph is responsible for (un)linking states // ProcessorGraph owns a LinkedStateMap that manages LinkedStateGroups // shared states are linked to each other (managed by a LinkedStateGroup) // processor.state can be added/removed (by name) to/from LinkedStateGroup // ProcessorGraph::Update/Retrieve // global-state: value // processor: {state: value} enum class Permission { NONE = 0, READ, WRITE }; // in order of least permissive to most permissive Permission permission_from_string(std::string s); std::string permission_to_string(Permission p, bool shorthand = false); class ExternalPermissionTracker { public: ExternalPermissionTracker() : none_(0), read_(0), write_(0) {} ExternalPermissionTracker(Permission permission) : ExternalPermissionTracker() { add(permission); } Permission permission() { if (none_.load() > 0) { return Permission::NONE; } if (read_.load() > 0) { return Permission::READ; } if (write_.load() > 0) { return Permission::WRITE; } return Permission::NONE; } void add(Permission permission) { if (permission == Permission::NONE) { ++none_; } else if (permission == Permission::READ) { ++read_; } else if (permission == Permission::WRITE) { ++write_; } } void subtract(Permission permission) { if (permission == Permission::NONE) { --none_; } else if (permission == Permission::READ) { --read_; } else if (permission == Permission::WRITE) { --write_; } } protected: std::atomic none_; std::atomic read_; std::atomic write_; }; class Permissions { public: Permissions(Permission self = Permission::WRITE, Permission others = Permission::READ, Permission external = Permission::NONE); const Permission self() const; const Permission others() const; const Permission external() const; void set_self(const Permission p); void set_others(const Permission p); void set_external(const Permission p); std::string to_string(bool shorthand = true) const; bool IsCompatible(const Permissions &p); protected: Permission self_; Permission others_; Permission external_; }; class SharedStateAlias; namespace graph { class ProcessorGraph; } class IState { friend class SharedStateAlias; friend class graph::ProcessorGraph; public: IState(const Permissions &permissions, std::string description = ""); IState(const IState &other); virtual ~IState() {} virtual IState *clone() const = 0; bool IsCompatible(const Permissions &permissions); const Permissions &permissions() const; Permission external_permission(); std::string description(); virtual std::string get_string(bool cache = true) = 0; protected: // for friends only virtual bool IsLikeMe(const std::shared_ptr &other) = 0; virtual void Share(const std::shared_ptr &other) = 0; virtual void UnShare() = 0; virtual bool IsShared(); void set_description(std::string value); virtual bool set_string(const std::string &value, bool cache = true) = 0; void set_external_permission(Permission permission); protected: void lock(); void unlock(); protected: Permissions permissions_; std::string description_; bool shared_; std::shared_ptr external_permission_; private: std::atomic_flag lock_ = ATOMIC_FLAG_INIT; }; template class StateCloneable : public Base { public: using Base::Base; virtual Base *clone() const { return new Derived(static_cast(*this)); } }; template class ReadableState : public StateCloneable> { public: ReadableState(T default_value, std::string description = "", Permission peers = Permission::WRITE, Permission external = Permission::NONE) : StateCloneable>( Permissions(Permission::READ, peers, external), description), default_(default_value), cache_(default_value), state_(std::make_shared>(default_value)) {} ReadableState(const ReadableState &other) : StateCloneable>(other.permissions_, other.description_), default_(other.default_), cache_(other.cache_), state_(std::make_shared>(other.state_->load())) { // note that we are creating our own (unshared) state // and that we do not share other's state } T get(bool cache = true) { T val; this->lock(); val = state_->load(); if (cache) { cache_ = val; } this->unlock(); return val; } bool changed_get(T &val, bool cache = true) { bool ret; this->lock(); val = state_->load(); ret = cache_ == val; if (cache) { cache_ = val; } this->unlock(); return ret; } std::string get_string(bool cache = true) override { T value = get(cache); if (std::is_same_v) { return value ? "true" : "false"; } return std::to_string(value); } protected: // for friends only void set(T value, bool cache = true) { this->lock(); state_->store(value); if (cache) { cache_ = value; } this->unlock(); } T exchange(T value, bool cache = true) { this->lock(); value = state_->exchange(value); if (cache) { cache_ = value; } this->unlock(); return value; } bool set_string(const std::string &value, bool cache = true) override { std::stringstream ss(value); T result; if ((std::is_same_v and ss >> std::boolalpha >> result) or ss >> result) { std::cout << result; set(result, cache); return true; } return false; } void reset() { set(default_); } void Share(const std::shared_ptr &other) override { if (other.get() == this) { return; } auto cast = dynamic_cast *>(other.get()); if (cast) { this->lock(); this->state_ = cast->state_; this->external_permission_ = cast->external_permission_; this->external_permission_->add(this->permissions_.external()); this->shared_ = true; this->unlock(); } else { throw std::runtime_error("Cannot delegate to incompatible state."); } } void UnShare() override { this->lock(); this->state_ = std::make_shared>(this->state_->load()); this->external_permission_->subtract(this->permissions_.external()); this->external_permission_ = std::make_shared( this->permissions_.external()); this->shared_ = false; this->unlock(); } bool IsLikeMe(const std::shared_ptr &other) override { try { auto cast = dynamic_cast *>(other.get()); if (cast) { return true; } else { return false; } } catch (const std::bad_cast &e) { return false; } } private: T default_; T cache_; std::shared_ptr> state_; // our own state, that may be shared with others }; template class WritableState : public ReadableState { public: WritableState(T default_value, std::string description = "", Permission peers = Permission::READ, Permission external = Permission::NONE) : ReadableState(default_value, description, peers, external) { this->permissions_.set_self(Permission::WRITE); } // make set methods publicly available void set(T value, bool cache = true) { ReadableState::set(value, cache); } T exchange(T value, bool cache = true) { return ReadableState::exchange(value, cache); } bool set_string(const std::string &value, bool cache = true) override { return ReadableState::set_string(value, cache); } void reset() { ReadableState::reset(); } }; class SharedStateAlias { public: SharedStateAlias(Permission external = Permission::WRITE, std::string description = ""); ~SharedStateAlias(); void AddState(std::string name, const std::shared_ptr &dependent); void RemoveState(std::string name); void RemoveAllStates(); bool Update(std::string value); std::string Retrieve(); YAML::Node ExportYAML(); private: Permission external_; std::string description_; std::shared_ptr master_; std::map> dependents_; }; template using StaticState = ReadableState; template using FollowerState = ReadableState; template using ProducerState = WritableState; template using BroadcasterState = WritableState; class SharedStateMap { public: SharedStateMap() {} ~SharedStateMap(); void AddAlias(std::string alias, Permission permission = Permission::WRITE, std::string description = ""); void RemoveAlias(std::string alias); void ShareState(std::string alias, std::string name, std::shared_ptr state); void UnShareState(std::string name); void UnShareAll(); void clear(); bool IsShared(std::string name); std::vector ListSharedStates(std::string alias); bool UpdateAlias(std::string alias, std::string value); std::string RetrieveAlias(std::string alias); YAML::Node ExportYAML(); protected: std::map aliases_; std::map shared_states_; };