Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor|feat(tools): UniqueList, UniqueVariantList, and List #143

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ target_link_libraries(

add_executable(
test-tools
src/tools/core/UniqueOrderedList.specs.cpp
src/tools/core/UniqueList.specs.cpp
src/tools/core/Variant.specs.cpp
src/tools/core/UniqueOrderedVariantList.specs.cpp
src/tools/core/UniqueVariantList.specs.cpp
src/tools/core/Delegate.specs.cpp
src/tools/core/reflection/reflection.specs.cpp
src/tools/core/reflection/Type.specs.cpp
Expand Down
6 changes: 3 additions & 3 deletions src/ndbl/gui/GraphView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ void GraphView::_on_graph_change()
m_physics_dirty = true;
}

void GraphView::_on_selection_change(Selection::EventType type, Selection::ElemType elem)
void GraphView::_on_selection_change(Selection::EventType type, Selection::Element elem)
{
bool selected = type == Selection::EventType::Append;

Expand Down Expand Up @@ -732,7 +732,7 @@ void GraphView::draw_create_node_context_menu(CreateNodeCtxMenu& menu, SlotView*

void GraphView::drag_state_enter()
{
for( const Selectable& elem : m_selection.data() )
for( const Selectable& elem : m_selection )
{
if ( auto* nodeview = elem.get_if<NodeView*>() )
nodeview->state().set_pinned();
Expand All @@ -746,7 +746,7 @@ void GraphView::drag_state_tick()
const Vec2 delta = ImGui::GetMouseDragDelta();
ImGui::ResetMouseDragDelta();

for ( const Selectable& elem : m_selection.data() )
for ( const Selectable& elem : m_selection )
{
if ( auto* nodeview = elem.get_if<NodeView*>() )
nodeview->spatial_node().translate(delta);
Expand Down
6 changes: 3 additions & 3 deletions src/ndbl/gui/GraphView.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#include "tools/core/reflection/reflection"
#include "tools/core/Variant.h"
#include "tools/core/UniqueOrderedVariantList.h"
#include "tools/core/UniqueVariantList.h"
#include "tools/gui/ViewState.h"
#include "tools/gui/geometry/Pivots.h"

Expand Down Expand Up @@ -38,7 +38,7 @@ namespace ndbl
};

using Selectable = tools::Variant<NodeView*, ScopeView*, SlotView*, EdgeView> ;
using Selection = tools::UniqueOrderedVariantList<Selectable> ;
using Selection = tools::UniqueVariantList<Selectable> ;

class GraphView
{
Expand Down Expand Up @@ -80,7 +80,7 @@ namespace ndbl
void _update(float dt, u16_t iterations);
void _update(float dt);
void _on_graph_change();
void _on_selection_change(Selection::EventType, Selection::ElemType );
void _on_selection_change(Selection::EventType, Selection::Element );
void frame_views(const std::vector<NodeView*>&, const Vec2& pivot );
void draw_create_node_context_menu(CreateNodeCtxMenu& menu, SlotView* dragged_slotview = nullptr );
void create_constraints__align_top_recursively(const std::vector<Node*>& unfiltered_follower, ndbl::Node *leader);
Expand Down
4 changes: 2 additions & 2 deletions src/ndbl/gui/Nodable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ void Nodable::update()
{
if ( graph_view )
{
for(const Selectable& elem : graph_view->selection().data() )
for(const Selectable& elem : graph_view->selection() )
{
if ( auto nodeview = elem.get_if<NodeView*>() )
graph_view->graph()->destroy_next_frame( nodeview->node() );
Expand All @@ -217,7 +217,7 @@ void Nodable::update()
{
if ( graph_view )
{
for(const Selectable& elem : graph_view->selection().data() )
for( const Selectable& elem : graph_view->selection() )
{
switch ( elem.index() )
{
Expand Down
40 changes: 28 additions & 12 deletions src/tools/core/UniqueOrderedList.h → src/tools/core/UniqueList.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,39 @@ namespace tools
// Ordered list of unique elements
// Uses a hashset (rely on std::hash<ElementT> ) and a list to guarantee uniqueness and order.
//
template<typename T>
struct UniqueOrderedList
template<typename ElementT>
struct UniqueList
{
using ElemType = T;
static_assert( std::is_default_constructible_v<std::hash<T>>, "std::hash<T> must be implemented");
static_assert( std::is_default_constructible_v<std::hash<ElementT>>, "std::hash<ElementT> must be implemented");

using Element = ElementT;
using Set = std::unordered_set<u64_t>; // std::unordered, because we need uniqueness, furthermore unordered_set::contains is faster than std::find on a list
using List = std::list<Element>; // std::list, because it is ordered, and it supports constant time insertion and removal of elements from anywhere in the container." (see https://devdocs.io/cpp/container/list)
using Iterator = typename List::iterator;
using ConstIterator = typename List::const_iterator;

Iterator begin() { return _ordered_elem.begin(); }
Iterator end() { return _ordered_elem.end(); }
ConstIterator cbegin() const { return _ordered_elem.cbegin(); }
ConstIterator cend() const { return _ordered_elem.cend(); }

Element& front()
{ return _ordered_elem.front(); }

Element& back()
{ return _ordered_elem.back(); }

bool empty() const
{
return _unique_elem.empty();
}

bool contains(const ElemType& elem) const // Constant on average, worst case linear in the size of the container.
bool contains(const Element& elem) const // Constant on average, worst case linear in the size of the container.
{
return _unique_elem.contains( _hash(elem) );
}

const std::list<ElemType>& data() const
const std::list<Element>& data() const
{
return _ordered_elem;
}
Expand Down Expand Up @@ -58,7 +74,7 @@ namespace tools
return _unique_elem.size();
}

bool append(ElemType& elem) // Constant on average, worst case linear in the size of the container.
bool append(Element& elem) // Constant on average, worst case linear in the size of the container.
{
const auto& [_, inserted] = _unique_elem.insert( _hash(elem) );
if ( inserted )
Expand All @@ -69,7 +85,7 @@ namespace tools
return false;
}

bool remove(const ElemType& elem)// Constant on average, worst case linear in the size of the container.
bool remove(const Element& elem)// Constant on average, worst case linear in the size of the container.
{
if ( _unique_elem.erase( _hash(elem) ) )
{
Expand All @@ -81,10 +97,10 @@ namespace tools
}

private:
u64_t _hash(const ElemType& elem) const
{ return std::hash<ElemType>{}(elem); }
u64_t _hash(const Element& elem) const
{ return std::hash<Element>{}(elem); }

std::unordered_set<u64_t> _unique_elem{}; // unordered_set, because we need uniqueness, furthermore unordered_set::contains is faster than std::find on a list
std::list<ElemType> _ordered_elem{}; // list, because it " supports constant time insertion and removal of elements from anywhere in the container." (see https://devdocs.io/cpp/container/list)
Set _unique_elem{};
List _ordered_elem{};
};
}
10 changes: 10 additions & 0 deletions src/tools/core/UniqueList.specs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include "UniqueList.h"
#include <gtest/gtest.h>

using namespace tools;

TEST(UniqueList, is_constructible )
{
EXPECT_TRUE( std::is_constructible_v<UniqueList<u64_t>> );
}

12 changes: 0 additions & 12 deletions src/tools/core/UniqueOrderedList.specs.cpp

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,65 @@
#include "Signals.h"
#include "Hash.h"
#include "Variant.h"
#include "UniqueOrderedList.h"
#include "UniqueList.h"

namespace tools
{
template<typename VariantT>
struct UniqueOrderedVariantList;
struct UniqueVariantList;

//
// Extend UniqueOrderedList specifically for Variant<Args...>
// Also provide a signal on_change to listen to append/remove events
//
template<typename ...Args>
struct UniqueOrderedVariantList<Variant<Args...>>
struct UniqueVariantList<Variant<Args...>>
{
using ElemType = Variant<Args...>;
using Element = Variant<Args...>;
using WrappedList = UniqueList<Element>;
using Iterator = typename WrappedList::Iterator ;
using ConstIterator = typename WrappedList::ConstIterator;

Iterator begin() { return _wrapped_list.begin(); }
Iterator end() { return _wrapped_list.end(); }
ConstIterator cbegin() const { return _wrapped_list.cbegin(); }
ConstIterator cend() const { return _wrapped_list.cend(); }

enum class EventType
{
Append,
Remove,
};

SIGNAL(on_change, EventType, ElemType);
SIGNAL(on_change, EventType, Element);

Element& front()
{ return _wrapped_list.front(); }

Element& back()
{ return _wrapped_list.back(); }

bool empty() const
{ return _wrapped_list.empty(); }

void clear()
{
for( const ElemType& elem : _wrapped_list.data() )
for( const Element& elem : _wrapped_list.data() )
{
on_change.emit( EventType::Remove, elem );
}
_wrapped_list.clear();
_count_by_index.clear();
}

bool contains(const ElemType& elem ) const
{ return _wrapped_list.contains(elem); }

template<typename T>
bool append(T* ptr)
{ ElemType elem{ptr}; return append(elem); }
bool contains(const Element& elem ) const
{ return _wrapped_list.contains(elem); }

template<typename AlternativeT>
bool append(AlternativeT& data)
{ return append(ElemType{data}); }
bool append(AlternativeT data)
{ return append(Element{data}); } // we use add_lvalue_reference to handle pointers

bool append(ElemType& elem )
bool append(Element& elem )
{
const bool ok = _wrapped_list.append(elem);
if ( ok )
Expand All @@ -77,7 +87,7 @@ namespace tools
return count;
}

bool remove(const ElemType& elem)
bool remove(const Element& elem)
{
const bool ok = _wrapped_list.remove(elem);
{
Expand All @@ -89,13 +99,13 @@ namespace tools

template<class AlternativeT> bool contains() const // O(1), read from cache.
{
constexpr size_t index = ElemType::template index_of<AlternativeT>();
constexpr size_t index = Element::template index_of<AlternativeT>();
return _count_by_index.contains(index );
}

template<class AlternativeT> size_t count() const // O(1), read from cache.
{
constexpr size_t index = ElemType::template index_of<AlternativeT>();
constexpr size_t index = Element::template index_of<AlternativeT>();
if ( _count_by_index.contains(index ) )
{
return _count_by_index.at(index );
Expand All @@ -110,7 +120,7 @@ namespace tools
if ( _count == 0 )
return {};

for ( const ElemType& elem : _wrapped_list.data() )
for ( const Element& elem : _wrapped_list.data() )
if ( AlternativeT ptr = elem.template get_if<AlternativeT>() )
return ptr;

Expand All @@ -129,18 +139,15 @@ namespace tools
result.reserve( _count ); // 1 allocation max :)

// OPTIM: we could use a cache per type_index if necessary ( type_index => list<T*> )
for ( const ElemType& elem : _wrapped_list.data() )
for ( const Element& elem : _wrapped_list.data() )
if ( AlternativeT data = elem.template get_if<AlternativeT>() )
result.push_back( data );

return result;
}

const std::list<ElemType>& data() const
{ return _wrapped_list.data(); }

private:
UniqueOrderedList<Variant<Args...>> _wrapped_list{};
WrappedList _wrapped_list{};
std::unordered_map<size_t, size_t> _count_by_index{};
};
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#include "UniqueOrderedVariantList.h"
#include "Variant.h"
#include "UniqueVariantList.h"
#include "tools/core/log.h"
#include <gtest/gtest.h>

using namespace tools;

TEST(UniqueOrderedVectorList, with_Variant )
TEST(UniqueVectorList, with_Variant )
{
using MyVariant = Variant<u64_t, const char*, std::string>;
using MyVec = UniqueOrderedVariantList<MyVariant>;
using MyVec = UniqueVariantList<MyVariant>;

MyVec vec;

Expand All @@ -24,8 +25,8 @@ TEST(UniqueOrderedVectorList, with_Variant )
EXPECT_TRUE( vec.contains(a) );
EXPECT_TRUE( vec.contains(b) );

EXPECT_TRUE( vec.data().front() == a );
EXPECT_TRUE( vec.data().back() == b );
EXPECT_TRUE( vec.front() == a );
EXPECT_TRUE( vec.back() == b );

vec.clear();

Expand Down
3 changes: 3 additions & 0 deletions src/tools/core/Variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ namespace tools
static constexpr size_t index_null = 0; // mono state's

Variant() = default;
Variant(const Variant&) = default;
Variant(Variant&&) = default;

template<class T>
Variant(T data)
Expand All @@ -47,6 +49,7 @@ namespace tools
}

Variant& operator=(const Variant& other) = default;
Variant& operator=(Variant&& other) = default;

template<class T>
T get() const
Expand Down
Loading