Program Listing for File sharedstate.hpp¶
↰ Return to documentation for file (falcon/sharedstate.hpp)
// ---------------------------------------------------------------------
// 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 <http://www.gnu.org/licenses/>.
// ---------------------------------------------------------------------
#pragma once
#include <algorithm>
#include <atomic>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <typeinfo>
#include <string>
#include <vector>
#include <iostream>
#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<int> none_;
std::atomic<int> read_;
std::atomic<int> 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<IState> &other) = 0;
virtual void Share(const std::shared_ptr<IState> &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<ExternalPermissionTracker> external_permission_;
private:
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
};
template <typename Base, typename Derived> class StateCloneable : public Base {
public:
using Base::Base;
virtual Base *clone() const {
return new Derived(static_cast<Derived const &>(*this));
}
};
template <typename T>
class ReadableState : public StateCloneable<IState, ReadableState<T>> {
public:
ReadableState(T default_value, std::string description = "",
Permission peers = Permission::WRITE,
Permission external = Permission::NONE)
: StateCloneable<IState, ReadableState<T>>(
Permissions(Permission::READ, peers, external), description),
default_(default_value), cache_(default_value),
state_(std::make_shared<std::atomic<T>>(default_value)) {}
ReadableState(const ReadableState &other)
: StateCloneable<IState, ReadableState<T>>(other.permissions_,
other.description_),
default_(other.default_), cache_(other.cache_),
state_(std::make_shared<std::atomic<T>>(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 {
if constexpr(std::is_convertible_v<T, std::string>){
T value = get(cache);
if (std::is_same_v<T, bool>) {
return value ? "true" : "false";
}
return std::to_string(value);
}
throw std::runtime_error("This state should not have external permission to be read "
"because it cannot be serialized via string transformation.");
}
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 {
if constexpr(std::is_convertible_v<T, std::string>){
std::stringstream ss(value);
T result;
if ((std::is_same_v<T, bool> and ss >> std::boolalpha >> result)
or ss >> result)
{
set(result, cache);
return true;
}
}
return false;
}
void reset() { set(default_); }
void Share(const std::shared_ptr<IState> &other) override {
if (other.get() == this) {
return;
}
auto cast = dynamic_cast<ReadableState<T> *>(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<std::atomic<T>>(this->state_->load());
this->external_permission_->subtract(this->permissions_.external());
this->external_permission_ = std::make_shared<ExternalPermissionTracker>(
this->permissions_.external());
this->shared_ = false;
this->unlock();
}
bool IsLikeMe(const std::shared_ptr<IState> &other) override {
try {
auto cast = dynamic_cast<const ReadableState<T> *>(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<std::atomic<T>>
state_; // our own state, that may be shared with others
};
template <typename T> class WritableState : public ReadableState<T> {
public:
WritableState(T default_value, std::string description = "",
Permission peers = Permission::READ,
Permission external = Permission::NONE)
: ReadableState<T>(default_value, description, peers, external) {
this->permissions_.set_self(Permission::WRITE);
}
// make set methods publicly available
void set(T value, bool cache = true) { ReadableState<T>::set(value, cache); }
T exchange(T value, bool cache = true) {
return ReadableState<T>::exchange(value, cache);
}
bool set_string(const std::string &value, bool cache = true) override {
return ReadableState<T>::set_string(value, cache);
}
void reset() { ReadableState<T>::reset(); }
};
class SharedStateAlias {
public:
SharedStateAlias(Permission external = Permission::WRITE,
std::string description = "");
~SharedStateAlias();
void AddState(std::string name, const std::shared_ptr<IState> &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<IState> master_;
std::map<std::string, std::shared_ptr<IState>> dependents_;
};
template <typename T> using StaticState = ReadableState<T>;
template <typename T> using FollowerState = ReadableState<T>;
template <typename T> using ProducerState = WritableState<T>;
template <typename T> using BroadcasterState = WritableState<T>;
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<IState> state);
void UnShareState(std::string name);
void UnShareAll();
void clear();
bool IsShared(std::string name);
std::vector<std::string> ListSharedStates(std::string alias);
bool UpdateAlias(std::string alias, std::string value);
std::string RetrieveAlias(std::string alias);
YAML::Node ExportYAML();
protected:
std::map<std::string, SharedStateAlias> aliases_;
std::map<std::string, std::string> shared_states_;
};