-
-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add signal implementations from Atria and their tests
- Loading branch information
1 parent
8499ec0
commit 0cc7add
Showing
8 changed files
with
1,204 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// lager - library for functional interactive c++ programs | ||
// Copyright (C) 2017 Juan Pedro Bolivar Puente | ||
// | ||
// This file is part of lager. | ||
// | ||
// lager is free software: you can redistribute it and/or modify | ||
// it under the terms of the MIT License, as detailed in the LICENSE | ||
// file located at the root of this source code distribution, | ||
// or here: <https://github.com/arximboldi/lager/blob/master/LICENSE> | ||
// | ||
|
||
#pragma once | ||
|
||
#include <zug/meta/util.hpp> | ||
|
||
#include <exception> | ||
|
||
namespace lager { | ||
|
||
/*! | ||
* Raised by the view when it produces no value yet. This can happen | ||
* when the reducing function that it uses is filtering some values. | ||
*/ | ||
struct no_value_error : std::exception | ||
{ | ||
const char* what() const noexcept override; | ||
}; | ||
|
||
namespace detail { | ||
|
||
struct no_value : zug::meta::bottom | ||
{ | ||
template <typename T> | ||
operator T() | ||
{ | ||
throw no_value_error{}; | ||
} | ||
}; | ||
|
||
} // namespace detail | ||
|
||
} // namespace lager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// | ||
// lager - library for functional interactive c++ programs | ||
// Copyright (C) 2017 Juan Pedro Bolivar Puente | ||
// | ||
// This file is part of lager. | ||
// | ||
// lager is free software: you can redistribute it and/or modify | ||
// it under the terms of the MIT License, as detailed in the LICENSE | ||
// file located at the root of this source code distribution, | ||
// or here: <https://github.com/arximboldi/lager/blob/master/LICENSE> | ||
// | ||
|
||
#pragma once | ||
|
||
#include <lager/detail/signals.hpp> | ||
|
||
namespace lager { | ||
|
||
namespace detail { | ||
|
||
/*! | ||
* Add signal that will always send the current value returned by a | ||
* given function. | ||
*/ | ||
template <typename SensorFnT> | ||
class sensor_down_signal | ||
: public down_signal<std::decay_t<std::result_of_t<SensorFnT()>>> | ||
{ | ||
using base_t = down_signal<std::decay_t<std::result_of_t<SensorFnT()>>>; | ||
|
||
public: | ||
sensor_down_signal(SensorFnT sensor) | ||
: base_t(sensor()) | ||
, sensor_(std::move(sensor)) | ||
{} | ||
|
||
void recompute() final { this->push_down(sensor_()); } | ||
|
||
private: | ||
SensorFnT sensor_; | ||
}; | ||
|
||
/*! | ||
* Make a StateUpDownSignal with deduced types. | ||
*/ | ||
template <typename SensorFnT> | ||
auto make_sensor_signal(SensorFnT&& fn) | ||
-> std::shared_ptr<sensor_down_signal<std::decay_t<SensorFnT>>> | ||
{ | ||
return std::make_shared<sensor_down_signal<std::decay_t<SensorFnT>>>( | ||
std::forward<SensorFnT>(fn)); | ||
} | ||
|
||
/*! | ||
* Root signal that serves as state. | ||
*/ | ||
template <typename T> | ||
class state_up_down_signal : public up_down_signal<T> | ||
{ | ||
public: | ||
using value_type = T; | ||
|
||
using up_down_signal<T>::up_down_signal; | ||
|
||
void send_up(const value_type& value) final { this->push_down(value); } | ||
|
||
void send_up(value_type&& value) final | ||
{ | ||
this->push_down(std::move(value)); | ||
} | ||
}; | ||
|
||
/*! | ||
* Make a StateUpDownSignal with deduced types. | ||
*/ | ||
template <typename T> | ||
auto make_state_signal(T&& value) | ||
-> std::shared_ptr<state_up_down_signal<std::decay_t<T>>> | ||
{ | ||
return std::make_shared<state_up_down_signal<std::decay_t<T>>>( | ||
std::forward<T>(value)); | ||
} | ||
|
||
} // namespace detail | ||
|
||
} // namespace lager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// | ||
// lager - library for functional interactive c++ programs | ||
// Copyright (C) 2017 Juan Pedro Bolivar Puente | ||
// | ||
// This file is part of lager. | ||
// | ||
// lager is free software: you can redistribute it and/or modify | ||
// it under the terms of the MIT License, as detailed in the LICENSE | ||
// file located at the root of this source code distribution, | ||
// or here: <https://github.com/arximboldi/lager/blob/master/LICENSE> | ||
// | ||
|
||
/*! | ||
* @file | ||
* | ||
* This module implements the cursor signal flow in Lager. Signals can be | ||
* connected forming two superinposed directed acyclical graphs, where | ||
* signals flow *down* or *up*. Signals are derived from eachother | ||
* using transducers. | ||
* | ||
* The APIs for flowing up and down are asymmetric because of the way | ||
* the graph is constructed and the semantics of information flow. | ||
* | ||
* - An up-down-signal can be constructed from a up-down-signal. | ||
* - A down-signal can be constructed from a up-down-signa or | ||
* a down-signal. | ||
* - A signal can be appended children, but can not be appended | ||
* parents. | ||
* - Information propagates upwardes immediately, but propagates | ||
* upwards in two fases. | ||
* | ||
* In general, sucessors know a lot about their predecessors, but | ||
* sucessors need to know very little or nothing from their sucessors. | ||
* | ||
* @todo We could eventually flatten signals when the sucessors knows | ||
* the transducer of its predecessor, this could be done heuristically. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <boost/signals2/signal.hpp> | ||
|
||
#include <functional> | ||
#include <memory> | ||
#include <vector> | ||
|
||
namespace lager { | ||
|
||
namespace detail { | ||
|
||
/*! | ||
* Allows comparing shared and weak pointers based on their owner. | ||
*/ | ||
constexpr struct | ||
{ | ||
template <typename T1, typename T2> | ||
bool operator()(const T1& a, const T2& b) | ||
{ | ||
return !a.owner_before(b) && !b.owner_before(a); | ||
} | ||
} owner_equals{}; | ||
|
||
/*! | ||
* Interface for children of a signal and is used to propagate | ||
* notifications. The notifications are propagated in two steps, | ||
* `send_down()` and `notify()`, not ensure that the outside world sees a | ||
* consistent state when it receives notifications. | ||
*/ | ||
struct down_signal_base | ||
{ | ||
virtual ~down_signal_base() = default; | ||
virtual void send_down() = 0; | ||
virtual void notify() = 0; | ||
}; | ||
|
||
/*! | ||
* Interface for signals that can send values back to their parents. | ||
*/ | ||
template <typename T> | ||
struct up_signal_base | ||
{ | ||
virtual ~up_signal_base() = default; | ||
virtual void send_up(const T&) = 0; | ||
virtual void send_up(T&&) = 0; | ||
}; | ||
|
||
/*! | ||
* Base class for the various signal types. Provides basic | ||
* functionality for setting values and propagating them to children. | ||
*/ | ||
template <typename T> | ||
class down_signal | ||
: public std::enable_shared_from_this<down_signal<T>> | ||
, public down_signal_base | ||
{ | ||
public: | ||
using value_type = T; | ||
|
||
down_signal(down_signal&&) = default; | ||
down_signal(const down_signal&) = delete; | ||
down_signal& operator=(down_signal&&) = default; | ||
down_signal& operator=(const down_signal&) = delete; | ||
|
||
down_signal(T value) | ||
: current_(std::move(value)) | ||
, last_(current_) | ||
, last_notified_(current_) | ||
{} | ||
|
||
virtual void recompute() {} | ||
virtual void recompute_deep() {} | ||
|
||
const value_type& current() const { return current_; } | ||
const value_type& last() const { return last_; } | ||
|
||
void link(std::weak_ptr<down_signal_base> child) | ||
{ | ||
using namespace std; | ||
using std::placeholders::_1; | ||
assert(find_if(begin(children_), | ||
end(children_), | ||
bind(owner_equals, child, _1)) == end(children_) && | ||
"Child signal must not be linked twice"); | ||
children_.push_back(child); | ||
} | ||
|
||
template <typename U> | ||
void push_down(U&& value) | ||
{ | ||
if (value != current_) { | ||
current_ = std::forward<U>(value); | ||
needs_send_down_ = true; | ||
} | ||
} | ||
|
||
void send_down() final | ||
{ | ||
recompute(); | ||
if (needs_send_down_) { | ||
last_ = current_; | ||
needs_send_down_ = false; | ||
needs_notify_ = true; | ||
|
||
for (auto& wchild : children_) { | ||
if (auto child = wchild.lock()) { | ||
child->send_down(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
void notify() final | ||
{ | ||
using namespace std; | ||
if (!needs_send_down_ && needs_notify_) { | ||
needs_notify_ = false; | ||
observers_(last_notified_, last_); | ||
last_notified_ = last_; | ||
|
||
auto garbage = false; | ||
for (std::size_t i = 0, size = children_.size(); i < size; ++i) { | ||
if (auto child = children_[i].lock()) { | ||
child->notify(); | ||
} else { | ||
garbage = true; | ||
} | ||
} | ||
|
||
if (garbage) { | ||
collect(); | ||
} | ||
} | ||
} | ||
|
||
template <typename Fn> | ||
auto observe(Fn&& f) -> boost::signals2::connection | ||
{ | ||
return observers_.connect(std::forward<Fn>(f)); | ||
} | ||
|
||
auto observers() | ||
-> boost::signals2::signal<void(const value_type&, const value_type&)>& | ||
{ | ||
return observers_; | ||
} | ||
|
||
private: | ||
void collect() | ||
{ | ||
using namespace std; | ||
children_.erase(remove_if(begin(children_), | ||
end(children_), | ||
mem_fn(&weak_ptr<down_signal_base>::expired)), | ||
end(children_)); | ||
} | ||
|
||
bool needs_send_down_ = false; | ||
bool needs_notify_ = false; | ||
value_type current_; | ||
value_type last_; | ||
value_type last_notified_; | ||
std::vector<std::weak_ptr<down_signal_base>> children_; | ||
boost::signals2::signal<void(const value_type&, const value_type&)> | ||
observers_; | ||
}; | ||
|
||
/*! | ||
* Base class for signals that can send values up the signal chain. | ||
*/ | ||
template <typename T> | ||
class up_down_signal | ||
: public down_signal<T> | ||
, public up_signal_base<T> | ||
{ | ||
using down_signal<T>::down_signal; | ||
}; | ||
|
||
} // namespace detail | ||
|
||
} // namespace lager |
Oops, something went wrong.