Chris@16
|
1 //=======================================================================
|
Chris@16
|
2 // Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
|
Chris@16
|
3 // Copyright 2003 Bruce Barr
|
Chris@16
|
4 // Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
|
Chris@16
|
5 //
|
Chris@16
|
6 // Distributed under the Boost Software License, Version 1.0. (See
|
Chris@16
|
7 // accompanying file LICENSE_1_0.txt or copy at
|
Chris@16
|
8 // http://www.boost.org/LICENSE_1_0.txt)
|
Chris@16
|
9 //=======================================================================
|
Chris@16
|
10
|
Chris@16
|
11 // Nonrecursive implementation of depth_first_visit_impl submitted by
|
Chris@16
|
12 // Bruce Barr, schmoost <at> yahoo.com, May/June 2003.
|
Chris@16
|
13 #ifndef BOOST_GRAPH_RECURSIVE_DFS_HPP
|
Chris@16
|
14 #define BOOST_GRAPH_RECURSIVE_DFS_HPP
|
Chris@16
|
15
|
Chris@16
|
16 #include <boost/config.hpp>
|
Chris@16
|
17 #include <boost/graph/graph_traits.hpp>
|
Chris@16
|
18 #include <boost/graph/graph_concepts.hpp>
|
Chris@16
|
19 #include <boost/graph/properties.hpp>
|
Chris@16
|
20 #include <boost/graph/visitors.hpp>
|
Chris@16
|
21 #include <boost/graph/named_function_params.hpp>
|
Chris@16
|
22 #include <boost/ref.hpp>
|
Chris@16
|
23 #include <boost/implicit_cast.hpp>
|
Chris@16
|
24 #include <boost/optional.hpp>
|
Chris@16
|
25 #include <boost/parameter.hpp>
|
Chris@16
|
26 #include <boost/concept/assert.hpp>
|
Chris@16
|
27 #include <boost/tti/has_member_function.hpp>
|
Chris@16
|
28
|
Chris@16
|
29 #include <vector>
|
Chris@16
|
30 #include <utility>
|
Chris@16
|
31
|
Chris@16
|
32 namespace boost {
|
Chris@16
|
33
|
Chris@16
|
34 template <class Visitor, class Graph>
|
Chris@16
|
35 class DFSVisitorConcept {
|
Chris@16
|
36 public:
|
Chris@16
|
37 void constraints() {
|
Chris@16
|
38 BOOST_CONCEPT_ASSERT(( CopyConstructibleConcept<Visitor> ));
|
Chris@16
|
39 vis.initialize_vertex(u, g);
|
Chris@16
|
40 vis.start_vertex(u, g);
|
Chris@16
|
41 vis.discover_vertex(u, g);
|
Chris@16
|
42 vis.examine_edge(e, g);
|
Chris@16
|
43 vis.tree_edge(e, g);
|
Chris@16
|
44 vis.back_edge(e, g);
|
Chris@16
|
45 vis.forward_or_cross_edge(e, g);
|
Chris@16
|
46 // vis.finish_edge(e, g); // Optional for user
|
Chris@16
|
47 vis.finish_vertex(u, g);
|
Chris@16
|
48 }
|
Chris@16
|
49 private:
|
Chris@16
|
50 Visitor vis;
|
Chris@16
|
51 Graph g;
|
Chris@16
|
52 typename graph_traits<Graph>::vertex_descriptor u;
|
Chris@16
|
53 typename graph_traits<Graph>::edge_descriptor e;
|
Chris@16
|
54 };
|
Chris@16
|
55
|
Chris@16
|
56 namespace detail {
|
Chris@16
|
57
|
Chris@16
|
58 struct nontruth2 {
|
Chris@16
|
59 template<class T, class T2>
|
Chris@16
|
60 bool operator()(const T&, const T2&) const { return false; }
|
Chris@16
|
61 };
|
Chris@16
|
62
|
Chris@16
|
63 BOOST_TTI_HAS_MEMBER_FUNCTION(finish_edge)
|
Chris@16
|
64
|
Chris@16
|
65 template <bool IsCallable> struct do_call_finish_edge {
|
Chris@16
|
66 template <typename E, typename G, typename Vis>
|
Chris@16
|
67 static void call_finish_edge(Vis& vis, const E& e, const G& g) {
|
Chris@16
|
68 vis.finish_edge(e, g);
|
Chris@16
|
69 }
|
Chris@16
|
70 };
|
Chris@16
|
71
|
Chris@16
|
72 template <> struct do_call_finish_edge<false> {
|
Chris@16
|
73 template <typename E, typename G, typename Vis>
|
Chris@16
|
74 static void call_finish_edge(Vis&, const E&, const G&) {}
|
Chris@16
|
75 };
|
Chris@16
|
76
|
Chris@16
|
77 template <typename E, typename G, typename Vis>
|
Chris@16
|
78 void call_finish_edge(Vis& vis, const E& e, const G& g) { // Only call if method exists
|
Chris@16
|
79 do_call_finish_edge<has_member_function_finish_edge<Vis, void>::value>::call_finish_edge(vis, e, g);
|
Chris@16
|
80 }
|
Chris@16
|
81
|
Chris@16
|
82
|
Chris@16
|
83 // Define BOOST_RECURSIVE_DFS to use older, recursive version.
|
Chris@16
|
84 // It is retained for a while in order to perform performance
|
Chris@16
|
85 // comparison.
|
Chris@16
|
86 #ifndef BOOST_RECURSIVE_DFS
|
Chris@16
|
87
|
Chris@16
|
88 // If the vertex u and the iterators ei and ei_end are thought of as the
|
Chris@16
|
89 // context of the algorithm, each push and pop from the stack could
|
Chris@16
|
90 // be thought of as a context shift.
|
Chris@16
|
91 // Each pass through "while (ei != ei_end)" may refer to the out-edges of
|
Chris@16
|
92 // an entirely different vertex, because the context of the algorithm
|
Chris@16
|
93 // shifts every time a white adjacent vertex is discovered.
|
Chris@16
|
94 // The corresponding context shift back from the adjacent vertex occurs
|
Chris@16
|
95 // after all of its out-edges have been examined.
|
Chris@16
|
96 //
|
Chris@16
|
97 // See http://lists.boost.org/MailArchives/boost/msg48752.php for FAQ.
|
Chris@16
|
98
|
Chris@16
|
99 template <class IncidenceGraph, class DFSVisitor, class ColorMap,
|
Chris@16
|
100 class TerminatorFunc>
|
Chris@16
|
101 void depth_first_visit_impl
|
Chris@16
|
102 (const IncidenceGraph& g,
|
Chris@16
|
103 typename graph_traits<IncidenceGraph>::vertex_descriptor u,
|
Chris@16
|
104 DFSVisitor& vis,
|
Chris@16
|
105 ColorMap color, TerminatorFunc func = TerminatorFunc())
|
Chris@16
|
106 {
|
Chris@16
|
107 BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept<IncidenceGraph> ));
|
Chris@16
|
108 BOOST_CONCEPT_ASSERT(( DFSVisitorConcept<DFSVisitor, IncidenceGraph> ));
|
Chris@16
|
109 typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex;
|
Chris@16
|
110 typedef typename graph_traits<IncidenceGraph>::edge_descriptor Edge;
|
Chris@16
|
111 BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<ColorMap, Vertex> ));
|
Chris@16
|
112 typedef typename property_traits<ColorMap>::value_type ColorValue;
|
Chris@16
|
113 BOOST_CONCEPT_ASSERT(( ColorValueConcept<ColorValue> ));
|
Chris@16
|
114 typedef color_traits<ColorValue> Color;
|
Chris@16
|
115 typedef typename graph_traits<IncidenceGraph>::out_edge_iterator Iter;
|
Chris@16
|
116 typedef std::pair<Vertex, std::pair<boost::optional<Edge>, std::pair<Iter, Iter> > > VertexInfo;
|
Chris@16
|
117
|
Chris@16
|
118 boost::optional<Edge> src_e;
|
Chris@16
|
119 Iter ei, ei_end;
|
Chris@16
|
120 std::vector<VertexInfo> stack;
|
Chris@16
|
121
|
Chris@16
|
122 // Possible optimization for vector
|
Chris@16
|
123 //stack.reserve(num_vertices(g));
|
Chris@16
|
124
|
Chris@16
|
125 put(color, u, Color::gray());
|
Chris@16
|
126 vis.discover_vertex(u, g);
|
Chris@16
|
127 boost::tie(ei, ei_end) = out_edges(u, g);
|
Chris@16
|
128 if (func(u, g)) {
|
Chris@16
|
129 // If this vertex terminates the search, we push empty range
|
Chris@16
|
130 stack.push_back(std::make_pair(u, std::make_pair(boost::optional<Edge>(), std::make_pair(ei_end, ei_end))));
|
Chris@16
|
131 } else {
|
Chris@16
|
132 stack.push_back(std::make_pair(u, std::make_pair(boost::optional<Edge>(), std::make_pair(ei, ei_end))));
|
Chris@16
|
133 }
|
Chris@16
|
134 while (!stack.empty()) {
|
Chris@16
|
135 VertexInfo& back = stack.back();
|
Chris@16
|
136 u = back.first;
|
Chris@16
|
137 src_e = back.second.first;
|
Chris@16
|
138 boost::tie(ei, ei_end) = back.second.second;
|
Chris@16
|
139 stack.pop_back();
|
Chris@16
|
140 while (ei != ei_end) {
|
Chris@16
|
141 Vertex v = target(*ei, g);
|
Chris@16
|
142 vis.examine_edge(*ei, g);
|
Chris@16
|
143 ColorValue v_color = get(color, v);
|
Chris@16
|
144 if (v_color == Color::white()) {
|
Chris@16
|
145 vis.tree_edge(*ei, g);
|
Chris@16
|
146 src_e = *ei;
|
Chris@16
|
147 stack.push_back(std::make_pair(u, std::make_pair(src_e, std::make_pair(++ei, ei_end))));
|
Chris@16
|
148 u = v;
|
Chris@16
|
149 put(color, u, Color::gray());
|
Chris@16
|
150 vis.discover_vertex(u, g);
|
Chris@16
|
151 boost::tie(ei, ei_end) = out_edges(u, g);
|
Chris@16
|
152 if (func(u, g)) {
|
Chris@16
|
153 ei = ei_end;
|
Chris@16
|
154 }
|
Chris@16
|
155 } else {
|
Chris@16
|
156 if (v_color == Color::gray()) {
|
Chris@16
|
157 vis.back_edge(*ei, g);
|
Chris@16
|
158 } else {
|
Chris@16
|
159 vis.forward_or_cross_edge(*ei, g);
|
Chris@16
|
160 }
|
Chris@16
|
161 call_finish_edge(vis, *ei, g);
|
Chris@16
|
162 ++ei;
|
Chris@16
|
163 }
|
Chris@16
|
164 }
|
Chris@16
|
165 put(color, u, Color::black());
|
Chris@16
|
166 vis.finish_vertex(u, g);
|
Chris@16
|
167 if (src_e) call_finish_edge(vis, src_e.get(), g);
|
Chris@16
|
168 }
|
Chris@16
|
169 }
|
Chris@16
|
170
|
Chris@16
|
171 #else // BOOST_RECURSIVE_DFS is defined
|
Chris@16
|
172
|
Chris@16
|
173 template <class IncidenceGraph, class DFSVisitor, class ColorMap,
|
Chris@16
|
174 class TerminatorFunc>
|
Chris@16
|
175 void depth_first_visit_impl
|
Chris@16
|
176 (const IncidenceGraph& g,
|
Chris@16
|
177 typename graph_traits<IncidenceGraph>::vertex_descriptor u,
|
Chris@16
|
178 DFSVisitor& vis, // pass-by-reference here, important!
|
Chris@16
|
179 ColorMap color, TerminatorFunc func)
|
Chris@16
|
180 {
|
Chris@16
|
181 BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept<IncidenceGraph> ));
|
Chris@16
|
182 BOOST_CONCEPT_ASSERT(( DFSVisitorConcept<DFSVisitor, IncidenceGraph> ));
|
Chris@16
|
183 typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex;
|
Chris@16
|
184 BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<ColorMap, Vertex> ));
|
Chris@16
|
185 typedef typename property_traits<ColorMap>::value_type ColorValue;
|
Chris@16
|
186 BOOST_CONCEPT_ASSERT(( ColorValueConcept<ColorValue> ));
|
Chris@16
|
187 typedef color_traits<ColorValue> Color;
|
Chris@16
|
188 typename graph_traits<IncidenceGraph>::out_edge_iterator ei, ei_end;
|
Chris@16
|
189
|
Chris@16
|
190 put(color, u, Color::gray()); vis.discover_vertex(u, g);
|
Chris@16
|
191
|
Chris@16
|
192 if (!func(u, g))
|
Chris@16
|
193 for (boost::tie(ei, ei_end) = out_edges(u, g); ei != ei_end; ++ei) {
|
Chris@16
|
194 Vertex v = target(*ei, g); vis.examine_edge(*ei, g);
|
Chris@16
|
195 ColorValue v_color = get(color, v);
|
Chris@16
|
196 if (v_color == Color::white()) { vis.tree_edge(*ei, g);
|
Chris@16
|
197 depth_first_visit_impl(g, v, vis, color, func);
|
Chris@16
|
198 } else if (v_color == Color::gray()) vis.back_edge(*ei, g);
|
Chris@16
|
199 else vis.forward_or_cross_edge(*ei, g);
|
Chris@16
|
200 call_finish_edge(vis, *ei, g);
|
Chris@16
|
201 }
|
Chris@16
|
202 put(color, u, Color::black()); vis.finish_vertex(u, g);
|
Chris@16
|
203 }
|
Chris@16
|
204
|
Chris@16
|
205 #endif
|
Chris@16
|
206
|
Chris@16
|
207 } // namespace detail
|
Chris@16
|
208
|
Chris@16
|
209 template <class VertexListGraph, class DFSVisitor, class ColorMap>
|
Chris@16
|
210 void
|
Chris@16
|
211 depth_first_search(const VertexListGraph& g, DFSVisitor vis, ColorMap color,
|
Chris@16
|
212 typename graph_traits<VertexListGraph>::vertex_descriptor start_vertex)
|
Chris@16
|
213 {
|
Chris@16
|
214 typedef typename graph_traits<VertexListGraph>::vertex_descriptor Vertex;
|
Chris@16
|
215 BOOST_CONCEPT_ASSERT(( DFSVisitorConcept<DFSVisitor, VertexListGraph> ));
|
Chris@16
|
216 typedef typename property_traits<ColorMap>::value_type ColorValue;
|
Chris@16
|
217 typedef color_traits<ColorValue> Color;
|
Chris@16
|
218
|
Chris@16
|
219 typename graph_traits<VertexListGraph>::vertex_iterator ui, ui_end;
|
Chris@16
|
220 for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) {
|
Chris@16
|
221 Vertex u = implicit_cast<Vertex>(*ui);
|
Chris@16
|
222 put(color, u, Color::white()); vis.initialize_vertex(u, g);
|
Chris@16
|
223 }
|
Chris@16
|
224
|
Chris@16
|
225 if (start_vertex != detail::get_default_starting_vertex(g)){ vis.start_vertex(start_vertex, g);
|
Chris@16
|
226 detail::depth_first_visit_impl(g, start_vertex, vis, color,
|
Chris@16
|
227 detail::nontruth2());
|
Chris@16
|
228 }
|
Chris@16
|
229
|
Chris@16
|
230 for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) {
|
Chris@16
|
231 Vertex u = implicit_cast<Vertex>(*ui);
|
Chris@16
|
232 ColorValue u_color = get(color, u);
|
Chris@16
|
233 if (u_color == Color::white()) { vis.start_vertex(u, g);
|
Chris@16
|
234 detail::depth_first_visit_impl(g, u, vis, color, detail::nontruth2());
|
Chris@16
|
235 }
|
Chris@16
|
236 }
|
Chris@16
|
237 }
|
Chris@16
|
238
|
Chris@16
|
239 template <class VertexListGraph, class DFSVisitor, class ColorMap>
|
Chris@16
|
240 void
|
Chris@16
|
241 depth_first_search(const VertexListGraph& g, DFSVisitor vis, ColorMap color)
|
Chris@16
|
242 {
|
Chris@16
|
243 typedef typename boost::graph_traits<VertexListGraph>::vertex_iterator vi;
|
Chris@16
|
244 std::pair<vi, vi> verts = vertices(g);
|
Chris@16
|
245 if (verts.first == verts.second)
|
Chris@16
|
246 return;
|
Chris@16
|
247
|
Chris@16
|
248 depth_first_search(g, vis, color, detail::get_default_starting_vertex(g));
|
Chris@16
|
249 }
|
Chris@16
|
250
|
Chris@16
|
251 template <class Visitors = null_visitor>
|
Chris@16
|
252 class dfs_visitor {
|
Chris@16
|
253 public:
|
Chris@16
|
254 dfs_visitor() { }
|
Chris@16
|
255 dfs_visitor(Visitors vis) : m_vis(vis) { }
|
Chris@16
|
256
|
Chris@16
|
257 template <class Vertex, class Graph>
|
Chris@16
|
258 void initialize_vertex(Vertex u, const Graph& g) {
|
Chris@16
|
259 invoke_visitors(m_vis, u, g, ::boost::on_initialize_vertex());
|
Chris@16
|
260 }
|
Chris@16
|
261 template <class Vertex, class Graph>
|
Chris@16
|
262 void start_vertex(Vertex u, const Graph& g) {
|
Chris@16
|
263 invoke_visitors(m_vis, u, g, ::boost::on_start_vertex());
|
Chris@16
|
264 }
|
Chris@16
|
265 template <class Vertex, class Graph>
|
Chris@16
|
266 void discover_vertex(Vertex u, const Graph& g) {
|
Chris@16
|
267 invoke_visitors(m_vis, u, g, ::boost::on_discover_vertex());
|
Chris@16
|
268 }
|
Chris@16
|
269 template <class Edge, class Graph>
|
Chris@16
|
270 void examine_edge(Edge u, const Graph& g) {
|
Chris@16
|
271 invoke_visitors(m_vis, u, g, ::boost::on_examine_edge());
|
Chris@16
|
272 }
|
Chris@16
|
273 template <class Edge, class Graph>
|
Chris@16
|
274 void tree_edge(Edge u, const Graph& g) {
|
Chris@16
|
275 invoke_visitors(m_vis, u, g, ::boost::on_tree_edge());
|
Chris@16
|
276 }
|
Chris@16
|
277 template <class Edge, class Graph>
|
Chris@16
|
278 void back_edge(Edge u, const Graph& g) {
|
Chris@16
|
279 invoke_visitors(m_vis, u, g, ::boost::on_back_edge());
|
Chris@16
|
280 }
|
Chris@16
|
281 template <class Edge, class Graph>
|
Chris@16
|
282 void forward_or_cross_edge(Edge u, const Graph& g) {
|
Chris@16
|
283 invoke_visitors(m_vis, u, g, ::boost::on_forward_or_cross_edge());
|
Chris@16
|
284 }
|
Chris@16
|
285 template <class Edge, class Graph>
|
Chris@16
|
286 void finish_edge(Edge u, const Graph& g) {
|
Chris@16
|
287 invoke_visitors(m_vis, u, g, ::boost::on_finish_edge());
|
Chris@16
|
288 }
|
Chris@16
|
289 template <class Vertex, class Graph>
|
Chris@16
|
290 void finish_vertex(Vertex u, const Graph& g) {
|
Chris@16
|
291 invoke_visitors(m_vis, u, g, ::boost::on_finish_vertex());
|
Chris@16
|
292 }
|
Chris@16
|
293
|
Chris@16
|
294 BOOST_GRAPH_EVENT_STUB(on_initialize_vertex,dfs)
|
Chris@16
|
295 BOOST_GRAPH_EVENT_STUB(on_start_vertex,dfs)
|
Chris@16
|
296 BOOST_GRAPH_EVENT_STUB(on_discover_vertex,dfs)
|
Chris@16
|
297 BOOST_GRAPH_EVENT_STUB(on_examine_edge,dfs)
|
Chris@16
|
298 BOOST_GRAPH_EVENT_STUB(on_tree_edge,dfs)
|
Chris@16
|
299 BOOST_GRAPH_EVENT_STUB(on_back_edge,dfs)
|
Chris@16
|
300 BOOST_GRAPH_EVENT_STUB(on_forward_or_cross_edge,dfs)
|
Chris@16
|
301 BOOST_GRAPH_EVENT_STUB(on_finish_edge,dfs)
|
Chris@16
|
302 BOOST_GRAPH_EVENT_STUB(on_finish_vertex,dfs)
|
Chris@16
|
303
|
Chris@16
|
304 protected:
|
Chris@16
|
305 Visitors m_vis;
|
Chris@16
|
306 };
|
Chris@16
|
307 template <class Visitors>
|
Chris@16
|
308 dfs_visitor<Visitors>
|
Chris@16
|
309 make_dfs_visitor(Visitors vis) {
|
Chris@16
|
310 return dfs_visitor<Visitors>(vis);
|
Chris@16
|
311 }
|
Chris@16
|
312 typedef dfs_visitor<> default_dfs_visitor;
|
Chris@16
|
313
|
Chris@16
|
314 // Boost.Parameter named parameter variant
|
Chris@16
|
315 namespace graph {
|
Chris@16
|
316 namespace detail {
|
Chris@16
|
317 template <typename Graph>
|
Chris@16
|
318 struct depth_first_search_impl {
|
Chris@16
|
319 typedef void result_type;
|
Chris@16
|
320 template <typename ArgPack>
|
Chris@16
|
321 void operator()(const Graph& g, const ArgPack& arg_pack) const {
|
Chris@16
|
322 using namespace boost::graph::keywords;
|
Chris@16
|
323 boost::depth_first_search(g,
|
Chris@16
|
324 arg_pack[_visitor | make_dfs_visitor(null_visitor())],
|
Chris@16
|
325 boost::detail::make_color_map_from_arg_pack(g, arg_pack),
|
Chris@16
|
326 arg_pack[_root_vertex || boost::detail::get_default_starting_vertex_t<Graph>(g)]);
|
Chris@16
|
327 }
|
Chris@16
|
328 };
|
Chris@16
|
329 }
|
Chris@16
|
330 BOOST_GRAPH_MAKE_FORWARDING_FUNCTION(depth_first_search, 1, 4)
|
Chris@16
|
331 }
|
Chris@16
|
332
|
Chris@16
|
333 BOOST_GRAPH_MAKE_OLD_STYLE_PARAMETER_FUNCTION(depth_first_search, 1)
|
Chris@16
|
334
|
Chris@16
|
335 template <class IncidenceGraph, class DFSVisitor, class ColorMap>
|
Chris@16
|
336 void depth_first_visit
|
Chris@16
|
337 (const IncidenceGraph& g,
|
Chris@16
|
338 typename graph_traits<IncidenceGraph>::vertex_descriptor u,
|
Chris@16
|
339 DFSVisitor vis, ColorMap color)
|
Chris@16
|
340 {
|
Chris@16
|
341 vis.start_vertex(u, g);
|
Chris@16
|
342 detail::depth_first_visit_impl(g, u, vis, color, detail::nontruth2());
|
Chris@16
|
343 }
|
Chris@16
|
344
|
Chris@16
|
345 template <class IncidenceGraph, class DFSVisitor, class ColorMap,
|
Chris@16
|
346 class TerminatorFunc>
|
Chris@16
|
347 void depth_first_visit
|
Chris@16
|
348 (const IncidenceGraph& g,
|
Chris@16
|
349 typename graph_traits<IncidenceGraph>::vertex_descriptor u,
|
Chris@16
|
350 DFSVisitor vis, ColorMap color, TerminatorFunc func = TerminatorFunc())
|
Chris@16
|
351 {
|
Chris@16
|
352 vis.start_vertex(u, g);
|
Chris@16
|
353 detail::depth_first_visit_impl(g, u, vis, color, func);
|
Chris@16
|
354 }
|
Chris@16
|
355 } // namespace boost
|
Chris@16
|
356
|
Chris@16
|
357 #ifdef BOOST_GRAPH_USE_MPI
|
Chris@16
|
358 # include <boost/graph/distributed/depth_first_search.hpp>
|
Chris@16
|
359 #endif
|
Chris@16
|
360
|
Chris@16
|
361 #endif
|