Skip to content

Commit

Permalink
Fix ddinu#8: Add observable collection
Browse files Browse the repository at this point in the history
Add wrapper around std::unordered_set to implement insertion and removal
of items from a collection. The collection inherits from a value, and
can be subscribed to as usual to yield notifications on assignment. The
collection also provides subscribe_changes which notifies observers on
successful insertion into and deletion from the collection
  • Loading branch information
johnmwilson82 committed Jun 9, 2022
1 parent 58f126d commit 25905c2
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 4 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
project(observable)
cmake_minimum_required(VERSION 3.5)
enable_testing()
set (CPP_STANDARD 17)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "cmake")
Expand Down
2 changes: 1 addition & 1 deletion cmake/compile_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function(set_cpp_standard target_name)
get_property(cpp_standard GLOBAL PROPERTY cpp_standard)

if(NOT CPP_STANDARD AND NOT cpp_standard)
set(cpp_standard 14)
set(cpp_standard 17)
message(STATUS "You can set the C++ standard by defining CPP_STANDARD")
elseif(NOT cpp_standard)
set(cpp_standard ${CPP_STANDARD})
Expand Down
1 change: 1 addition & 0 deletions observable/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_library(observable INTERFACE)

add_custom_target(observable_headers # Just to generate a project in IDEs.
SOURCES
include/observable/collection.hpp
include/observable/observable.hpp
include/observable/observe.hpp
include/observable/subject.hpp
Expand Down
167 changes: 167 additions & 0 deletions observable/include/observable/collection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#pragma once
#include <unordered_set>

#include <observable/value.hpp>

#include <observable/detail/compiler_config.hpp>
OBSERVABLE_BEGIN_CONFIGURE_WARNINGS


namespace observable {

template <
typename ValueType,
template<typename ValueType, typename... ContainerArgs> typename ContainerType = std::unordered_set,
typename... ContainerArgs
>
class collection : public value<ContainerType<ValueType, ContainerArgs...>>
{
using change_subject = subject<void(ValueType const &, bool)>;
using base_class = value<ContainerType<ValueType, ContainerArgs...>>;

public:
using container_type = ContainerType<ValueType, ContainerArgs...>;

//! Create a default-constructed observable collection.
//!
//! The backing container will be default constructed and empty
constexpr collection() =default;

//! Create an initialized observable collection.
//!
//! \param initial_value The observable collection's initial value.
constexpr collection(std::initializer_list<ValueType> initial_value) :
base_class(container_type(initial_value))
{}

template <typename Callable>
auto subscribe_changes(Callable && observer) const
{
static_assert(detail::is_compatible_with_subject<Callable, change_subject>::value,
"Observer is not valid. Please provide an observer that takes ValueType as"
"its first argument and a boolean as its second argument");

return subscribe_changes_impl(std::forward<Callable>(observer));
}

//! Insert a new value into collection, possibly notifying any subscribed observers.
//!
//! The new value is inserted respecting the rules of the underlying container, if
//! it is not possible to add no observers will be notified
//!
//! \param new_value The new value to add.
//! \throw readonly_value if the value has an associated updater.
//! \see subject<void(Args ...)>::notify()
bool insert(ValueType new_value)
{
return insert_impl(std::move(new_value));
}

//! Emplace a new value into collection, possibly notifying any subscribed observers.
//!
//! The new value is emplaced respecting the rules of the underlying container, if
//! it is not possible to add no observers will be notified
//!
//! \param new_value The new value to add.
//! \throw readonly_value if the value has an associated updater.
//! \see subject<void(Args ...)>::notify()
bool emplace(ValueType && new_value)
{
return emplace_impl(std::forward(new_value));
}

//! Remove a value from collection, possibly notifying any subscribed observers.
//!
//! The new value is removed respecting the rules of the underlying container, if it
//! is not found in the container no observers will be notified
//!
//! \param new_value The new value to add.
//! \throw readonly_value if the value has an associated updater.
//! \see subject<void(Args ...)>::notify()
bool remove(ValueType value)
{
return remove_impl(std::move(value));
}

private:
template <typename Callable>
auto subscribe_changes_impl(Callable && observer) const
{
return change_observers_.subscribe(std::forward<Callable>(observer));
}

bool insert_impl(ValueType new_value)
{
const auto [it, inserted] = value_.insert(new_value);

if (inserted)
{
change_observers_.notify(*it, true);
void_observers_.notify();
value_observers_.notify(value_);
}

return inserted;
}

bool emplace_impl(ValueType new_value)
{
const auto [it, inserted] = value_.emplace(new_value);

if (inserted)
{
change_observers_.notify(*it, true);
void_observers_.notify();
value_observers_.notify(value_);
}

return inserted;
}

#if __cplusplus > 201703L
bool remove_impl(ValueType value)
{
auto nh = value_.extract(value);
bool removed = false;

if (nh)
{
removed = true;
change_observers_.notify(nh.value(), false);
void_observers_.notify();
value_observers_.notify(value_);
}

return removed;
}
#else
bool remove_impl(ValueType value)
{
// Without the C++17 extract function, we have to call the
// change_observer with the reference to the item in the collection
// before we actually remove it
bool removed = false;

auto found_it = value_.find(value);

if (found_it != value_.end())
{
change_observers_.notify(*found_it, false);

value_.erase(found_it);
void_observers_.notify();
value_observers_.notify(value_);

removed = true;
}

return removed;
}
#endif

mutable change_subject change_observers_;
};

}

OBSERVABLE_END_CONFIGURE_WARNINGS
1 change: 1 addition & 0 deletions observable/include/observable/observable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <observable/subject.hpp>
#include <observable/value.hpp>
#include <observable/observe.hpp>
#include <observable/collection.hpp>
#include <observable/expressions/filters.hpp>
#include <observable/expressions/math.hpp>

Expand Down
6 changes: 3 additions & 3 deletions observable/include/observable/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ class value<ValueType>
subject<void(), value<ValueType>> destroyed;

//! Destructor.
~value() { destroyed.notify(); }
virtual ~value() { destroyed.notify(); }

public:
//! Observable values are **not** copy-constructible.
Expand Down Expand Up @@ -287,7 +287,7 @@ class value<ValueType>
return *this;
}

private:
protected:
template <typename Callable>
auto subscribe_impl(Callable && observer) const ->
std::enable_if_t<detail::is_compatible_with_subject<Callable, void_subject>::value &&
Expand Down Expand Up @@ -332,7 +332,7 @@ class value<ValueType>
value_observers_.notify(value_);
}

private:
protected:
ValueType value_;

std::function<bool(ValueType const &, ValueType const &)> eq_ {
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_executable(tests
src/expressions/math.cpp
src/expressions/operators.cpp
src/expressions/tree.cpp
src/collection.cpp
src/infinite_subscription.cpp
src/observe.cpp
src/shared_subscription.cpp
Expand Down
Loading

0 comments on commit 25905c2

Please sign in to comment.