1 /*
2 Copyright (c) 2008-2020, Benoit AUTHEMAN All rights reserved.
3
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6 * Redistributions of source code must retain the above copyright
7 notice, this list of conditions and the following disclaimer.
8 * Redistributions in binary form must reproduce the above copyright
9 notice, this list of conditions and the following disclaimer in the
10 documentation and/or other materials provided with the distribution.
11 * Neither the name of the author or Destrat.io nor the
12 names of its contributors may be used to endorse or promote products
13 derived from this software without specific prior written permission.
14
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
19 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 //-----------------------------------------------------------------------------
28 // This file is a part of the GTpo software library.
29 //
30 // \file graph.hpp
31 // \author benoit@destrat.io
32 // \date 2016 01 22
33 //-----------------------------------------------------------------------------
34
35 namespace gtpo { // ::gtpo
36
37 /* Graph Node Management *///--------------------------------------------------
38 template <class config_t>
~graph()39 graph<config_t>::~graph()
40 {
41 clear();
42 }
43
44 template <class config_t>
clear()45 void graph<config_t>::clear() noexcept
46 {
47 // Note 20160104: First edges, then nodes (it helps maintaining topology if
48 // womething went wrong during destruction
49 for ( auto& node: _nodes ) // Do not maintain topology during node deletion
50 node->_graph = nullptr;
51 _root_nodes.clear(); // Remove weak_ptr containers first
52 _nodes_search.clear();
53 _nodes.clear();
54
55 for ( auto& edge: _edges ) // Do not maintain topology during edge deletion
56 edge->_graph = nullptr;
57 _edges_search.clear();
58 _edges.clear();
59
60 // Clearing groups and behaviours (Not: group->_graph is resetted with nodes)
61 _groups.clear();
62 behaviourable_base::clear();
63 }
64 //-----------------------------------------------------------------------------
65
66 /* Graph Node Management *///--------------------------------------------------
67 template < class config_t >
create_node()68 auto graph<config_t>::create_node( ) -> weak_node_t
69 {
70 weak_node_t node;
71 try {
72 node = insert_node( std::make_shared< typename config_t::final_node_t >() );
73 } catch (...) { gtpo::assert_throw( false, "graph<>::create_node(): Error: can't insert node in graph." ); }
74 return node;
75 }
76
77 template < class config_t >
insert_node(shared_node_t node)78 auto graph<config_t>::insert_node( shared_node_t node ) -> weak_node_t
79 {
80 assert_throw(node != nullptr, "gtpo::graph<>::insert_node(): Error: Trying to insert a nullptr node in graph.");
81 weak_node_t weak_node;
82 try {
83 weak_node = node;
84 node->set_graph(this);
85 config_t::template container_adapter< shared_nodes_t >::insert( node, _nodes );
86 config_t::template container_adapter< weak_nodes_t_search >::insert( weak_node, _nodes_search );
87 config_t::template container_adapter< weak_nodes_t >::insert( weak_node, _root_nodes );
88 behaviourable_base::notify_node_inserted( weak_node );
89 } catch (...) { gtpo::assert_throw( false, "gtpo::graph<>::insert_node(): Error: can't insert node in graph." ); }
90 return weak_node;
91 }
92
93 template < class config_t >
remove_node(weak_node_t weak_node)94 auto graph<config_t>::remove_node( weak_node_t weak_node ) -> void
95 {
96 auto node = weak_node.lock();
97 if ( !node )
98 gtpo::assert_throw( false, "gtpo::graph<>::remove_node(): Error: node is expired." );
99
100 // Eventually, ungroup node
101 auto group = node->get_group().lock();
102 if ( group )
103 ungroup_node(weak_node, group);
104
105 behaviourable_base::notify_node_removed( weak_node );
106
107 // Removing all orphant edges pointing to node.
108 weak_edges_t nodeInEdges; std::copy( node->get_in_edges().cbegin(),
109 node->get_in_edges().cend(),
110 std::back_inserter( nodeInEdges ) );
111 for ( auto inEdge: nodeInEdges )
112 node->remove_in_edge( inEdge );
113 for ( auto inEdge: nodeInEdges )
114 remove_edge( inEdge );
115
116 weak_edges_t nodeOutEdges; std::copy( node->get_out_edges().cbegin(),
117 node->get_out_edges().cend(),
118 std::back_inserter( nodeOutEdges ) );
119 for ( auto outEdge: nodeOutEdges )
120 node->remove_out_edge( outEdge );
121 for ( auto outEdge: nodeOutEdges )
122 remove_edge( outEdge );
123
124 // Remove node from main graph containers (it will generate node destruction)
125 config_t::template container_adapter<weak_nodes_t_search>::remove( weak_node, _nodes_search );
126 config_t::template container_adapter<weak_nodes_t>::remove( weak_node, _root_nodes );
127 node->set_graph( nullptr );
128 config_t::template container_adapter<shared_nodes_t>::remove( node, _nodes );
129 }
130
131 template < class config_t >
install_root_node(weak_node_t node)132 auto graph<config_t>::install_root_node( weak_node_t node ) -> void
133 {
134 assert_throw( !node.expired(), "gtpo::graph<>::setRootNode(): Error: node is expired." );
135 shared_node_t sharedNode = node.lock();
136 assert_throw( sharedNode->get_in_degree() == 0, "gtpo::graph<>::setRootNode(): Error: trying to set a node with non 0 in degree as a root node." );
137
138 config_t::template container_adapter<weak_nodes_t>::insert( node, _root_nodes );
139 }
140
141 template < class config_t >
is_root_node(weak_node_t node) const142 auto graph<config_t>::is_root_node( weak_node_t node ) const -> bool
143 {
144 assert_throw( !node.expired(), "gtpo::graph<>::is_root_node(): Error: node is expired." );
145 shared_node_t sharedNode = node.lock();
146 if ( sharedNode->get_in_degree() != 0 ) // Fast exit when node in degree != 0, it can't be a root node
147 return false;
148 return gtpo::find_weak_ptr( _root_nodes, node );
149 }
150
151 template < class config_t >
contains(weak_node_t node) const152 auto graph<config_t>::contains( weak_node_t node ) const noexcept -> bool
153 {
154 if ( node.expired() ) // Fast exit.
155 return false;
156 auto nodeIter = std::find_if( _nodes_search.cbegin(), _nodes_search.cend(),
157 [=](const weak_node_t& n ) { return ( compare_weak_ptr<>( node, n ) ); } );
158 return nodeIter != _nodes_search.cend();
159 }
160 //-----------------------------------------------------------------------------
161
162 /* Graph Edge Management *///--------------------------------------------------
163 template < class config_t >
164 //template < class Edge_t >
create_edge(weak_node_t source,weak_node_t destination)165 auto graph< config_t >::create_edge( weak_node_t source, weak_node_t destination ) -> weak_edge_t
166 {
167 auto source_ptr = source.lock();
168 auto destination_ptr = destination.lock();
169 if ( !source_ptr ||
170 !destination_ptr )
171 throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(Node,Node): Insertion of edge failed, either source or destination nodes are expired." );
172
173 auto edge = std::make_shared<typename config_t::final_edge_t>();
174 edge->set_graph( this );
175 config_t::template container_adapter< shared_edges_t >::insert( edge, _edges );
176 config_t::template container_adapter< weak_edges_search_t >::insert( edge, _edges_search );
177 edge->set_src( source );
178 edge->set_dst( destination );
179 try {
180 source_ptr->add_out_edge( edge );
181 destination_ptr->add_in_edge( edge );
182 if ( source_ptr.get() != destination_ptr.get() ) // If edge define is a trivial circuit, do not remove destination from root nodes
183 config_t::template container_adapter<weak_nodes_t>::remove( destination, _root_nodes ); // Otherwise destination is no longer a root node
184 auto weak_edge = weak_edge_t{edge};
185 behaviourable_base::notify_edge_inserted( weak_edge );
186 } catch ( ... ) {
187 throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(Node,Node): Insertion of edge failed, source or destination nodes topology can't be modified." );
188 }
189 return edge;
190 }
191
192 template < class config_t >
insert_edge(shared_edge_t edge)193 auto graph<config_t>::insert_edge( shared_edge_t edge ) -> weak_edge_t
194 {
195 assert_throw( edge != nullptr );
196 auto source = edge->get_src().lock();
197 if ( source == nullptr ||
198 edge->get_dst().expired() )
199 throw gtpo::bad_topology_error( "gtpo::graph<>::insert_edge(): Error: Either source and/or destination nodes are expired." );
200 edge->set_graph( this );
201 weak_edge_t weak_edge = edge;
202 config_t::template container_adapter<shared_edges_t>::insert( edge, _edges );
203 config_t::template container_adapter<weak_edges_search_t>::insert( weak_edge, _edges_search );
204 try {
205 source->add_out_edge( edge );
206 auto destination = edge->get_dst().lock();
207 if ( destination != nullptr ) {
208 destination->add_in_edge( edge );
209 if ( source.get() != destination.get() ) // If edge define is a trivial circuit, do not remove destination from root nodes
210 config_t::template container_adapter<weak_nodes_t>::remove( destination, _root_nodes ); // Otherwise destination is no longer a root node
211 }
212 auto weak_edge = weak_edge_t(edge);
213 behaviourable_base::notify_edge_inserted( weak_edge );
214 } catch ( ... ) {
215 throw gtpo::bad_topology_error( "gtpo::graph<>::create_edge(): Insertion of edge failed, source or destination nodes topology can't be modified." );
216 }
217 return edge;
218 }
219
220 template < class config_t >
remove_edge(weak_node_t source,weak_node_t destination)221 void graph<config_t>::remove_edge( weak_node_t source, weak_node_t destination )
222 {
223 if ( source.expired() ||
224 destination.expired() )
225 throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Topology error." );
226
227 // Find the edge associed with source / destination
228 if ( _edges.size() == 0 )
229 return; // Fast exit
230 auto edgeIter = std::find_if( _edges.begin(), _edges.end(),
231 [=](const shared_edge_t& e ){
232 return ( compare_weak_ptr<>( e->get_src(), source ) &&
233 compare_weak_ptr<>( e->get_dst(), destination ) );
234 }
235 ); // std::find_if
236 if ( edgeIter != _edges.end() )
237 remove_edge( *edgeIter );
238 }
239
240 template < class config_t >
remove_all_edges(weak_node_t source,weak_node_t destination)241 void graph<config_t>::remove_all_edges( weak_node_t source, weak_node_t destination )
242 {
243 if ( source.expired() ||
244 destination.expired() )
245 throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Topology error." );
246
247 while ( get_edge_count( source, destination ) > 0 )
248 remove_edge( source, destination );
249 }
250
251 template < class config_t >
remove_edge(weak_edge_t weak_edge)252 void graph<config_t>::remove_edge( weak_edge_t weak_edge )
253 {
254 shared_edge_t edge = weak_edge.lock();
255 if ( !edge )
256 throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Error: Edge to be removed is already expired." );
257 auto source = edge->get_src().lock();
258 auto destination = edge->get_dst().lock();
259 if ( source == nullptr || // Expecting a non null source and either a destination or an hyper destination
260 destination == nullptr )
261 throw gtpo::bad_topology_error( "gtpo::graph<>::remove_edge(): Error: Edge source or destination are expired." );
262 behaviourable_base::notify_edge_removed( weak_edge );
263 source->remove_out_edge( weak_edge );
264 if ( destination ) // Remove edge from destination in edges
265 destination->remove_in_edge( weak_edge );
266
267 edge->set_graph( nullptr );
268 config_t::template container_adapter<shared_edges_t>::remove( edge, _edges );
269 config_t::template container_adapter<weak_edges_search_t>::remove( weak_edge, _edges_search );
270 }
271
272 template < class config_t >
find_edge(weak_node_t source,weak_node_t destination) const273 auto graph<config_t>::find_edge( weak_node_t source, weak_node_t destination ) const noexcept -> weak_edge_t
274 {
275 // Find the edge associed with source / destination
276 auto edgeIter = std::find_if( _edges.begin(), _edges.end(),
277 [=](const shared_edge_t& e ) noexcept {
278 return ( compare_weak_ptr<>( e->get_src(), source ) &&
279 compare_weak_ptr<>( e->get_dst(), destination ) );
280 } );
281 return ( edgeIter != _edges.end() ? *edgeIter : weak_edge_t{} ); // std::shared_ptr::operator* is noexcept
282 }
283
284 template < class config_t >
has_edge(weak_node_t source,weak_node_t destination) const285 auto graph<config_t>::has_edge( weak_node_t source, weak_node_t destination ) const noexcept -> bool
286 {
287 return ( find_edge( source, destination).use_count() != 0 );
288 }
289
290 template < class config_t >
get_edge_count(weak_node_t source,weak_node_t destination) const291 auto graph<config_t>::get_edge_count( weak_node_t source, weak_node_t destination ) const -> unsigned int
292 {
293 unsigned int edgeCount = 0;
294 std::for_each( _edges.begin(), _edges.end(), [&](const shared_edge_t& e ) {
295 if ( compare_weak_ptr<>( e->get_src(), source ) &&
296 compare_weak_ptr<>( e->get_dst(), destination ) )
297 ++edgeCount;
298 } );
299 return edgeCount;
300 }
301
302 template < class config_t >
contains(weak_edge_t edge) const303 auto graph<config_t>::contains( weak_edge_t edge ) const noexcept -> bool
304 {
305 if ( edge.expired() ) // Fast exit.
306 return false;
307 auto edgeIter = std::find_if( _edges_search.cbegin(), _edges_search.cend(),
308 [=](const weak_edge_t& n ) { return ( compare_weak_ptr<>( edge, n ) ); } );
309 return edgeIter != _edges_search.cend();
310 }
311 //-----------------------------------------------------------------------------
312
313 /* Graph Group Management *///-------------------------------------------------
314 template < class config_t >
insert_group(shared_group_t group)315 auto graph<config_t>::insert_group( shared_group_t group ) noexcept( false ) -> weak_group_t
316 {
317 assert_throw( group != nullptr, "gtpo::graph<>::insert_group(): Error: trying to insert a nullptr shared_node_t" );
318 weak_node_t group_node_ptr = insert_node(group);
319 try {
320 group->set_graph( this );
321 config_t::template container_adapter<weak_groups_t>::insert( group, _groups );
322
323 // FIXME GROUPS
324 //this->notify_group_inserted( weakGroup );
325 } catch (...) {
326 throw gtpo::bad_topology_error( "gtpo::graph<>::insert_group(): Insertion of group failed" );
327 }
328 return group;
329 }
330
331 template < class config_t >
remove_group(weak_group_t group_ptr)332 auto graph<config_t>::remove_group( weak_group_t group_ptr ) noexcept( false ) -> void
333 {
334 shared_node_t group = group_ptr.lock();
335 if ( !group )
336 gtpo::assert_throw( false, "graph<>::remove_group(): Error: trying to remove and expired group." );
337
338 // Remove group (it will be automatically deallocated)
339 // FIXME GROUPS
340 //this->notify_group_removed( group_ptr );
341 group->set_graph( nullptr );
342 config_t::template container_adapter<weak_groups_t>::remove( group_ptr, _groups );
343
344 remove_node(group_ptr);
345 }
346
347 template < class config_t >
has_group(const weak_group_t & group) const348 auto graph<config_t>::has_group( const weak_group_t& group ) const -> bool
349 {
350 if ( group.expired() )
351 return false;
352 auto groupIter = std::find_if( _groups.begin(), _groups.end(),
353 [=](const weak_group_t& graphGroup ){ return ( compare_weak_ptr<>( group, graphGroup ) ); } );
354 return groupIter != _groups.end();
355 }
356
357 template < class config_t >
group_node(weak_node_t node,weak_group_t group)358 auto graph<config_t>::group_node( weak_node_t node, weak_group_t group) noexcept(false) -> void
359 {
360 auto group_ptr = group.lock();
361 gtpo::assert_throw( group_ptr != nullptr, "gtpo::group<>::group_node(): Error: trying to insert a node into an expired group." );
362
363 auto node_ptr = node.lock();
364 gtpo::assert_throw( node_ptr != nullptr, "gtpo::group<>::group_node(): Error: trying to insert an expired node in group." );
365
366 node_ptr->set_group( group );
367 config_t::template container_adapter<weak_nodes_t>::insert( node, group_ptr->_nodes );
368
369 // FIXME GROUPS
370 //group_ptr->notify_node_inserted( node );
371 }
372
373 template < class config_t >
ungroup_node(weak_node_t weakNode,weak_node_t weakGroup)374 auto graph<config_t>::ungroup_node( weak_node_t weakNode, weak_node_t weakGroup ) noexcept(false) -> void
375 {
376 auto group = weakGroup.lock();
377 gtpo::assert_throw( group != nullptr, "gtpo::group<>::ungroup_node(): Error: trying to ungroup from an expired group." );
378
379 auto node = weakNode.lock();
380 gtpo::assert_throw( node != nullptr, "gtpo::group<>::ungroup_node(): Error: trying to ungroup an expired node from a group." );
381
382 gtpo::assert_throw( node->get_group().lock() == group, "gtpo::group<>::ungroup_node(): Error: trying to ungroup a node that is not part of group." );
383
384 config_t::template container_adapter<weak_nodes_t>::remove( weakNode, group->_nodes );
385 // FIXME GROUPS
386 //group->notify_node_removed( weakNode );
387 node->set_group( weak_group_t{} ); // Warning: group must remain valid while notify_node_removed() is called
388 }
389 //-----------------------------------------------------------------------------
390
391 } // ::gtpo
392
393