Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
85a4fc8d
Commit
85a4fc8d
authored
Jun 21, 2012
by
Dominik Charousset
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
maintenance & documentation
parent
7e757380
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
104 additions
and
69 deletions
+104
-69
CMakeLists.txt
CMakeLists.txt
+19
-10
benchmarks/FindLibcppa.cmake
benchmarks/FindLibcppa.cmake
+1
-0
cppa/event_based_actor.hpp
cppa/event_based_actor.hpp
+1
-0
cppa/local_actor.hpp
cppa/local_actor.hpp
+16
-3
cppa/match_expr.hpp
cppa/match_expr.hpp
+0
-4
cppa/thread_mapped_actor.hpp
cppa/thread_mapped_actor.hpp
+3
-0
examples/FindLibcppa.cmake
examples/FindLibcppa.cmake
+1
-0
src/uniform_type_info.cpp
src/uniform_type_info.cpp
+1
-2
unit_testing/FindLibcppa.cmake
unit_testing/FindLibcppa.cmake
+1
-0
unit_testing/test__spawn.cpp
unit_testing/test__spawn.cpp
+61
-50
No files found.
CMakeLists.txt
View file @
85a4fc8d
...
@@ -126,24 +126,33 @@ add_custom_target(uninstall
...
@@ -126,24 +126,33 @@ add_custom_target(uninstall
COMMAND
${
CMAKE_COMMAND
}
-P
COMMAND
${
CMAKE_COMMAND
}
-P
${
CMAKE_CURRENT_BINARY_DIR
}
/cmake_uninstall.cmake
)
${
CMAKE_CURRENT_BINARY_DIR
}
/cmake_uninstall.cmake
)
set
(
CPPA_BINARY_OUTPUT_PATH
${
CMAKE_SOURCE_DIR
}
/bin
)
set
(
CPPA_LIBRARY_OUTPUT_PATH
${
CMAKE_SOURCE_DIR
}
/lib
)
if
(
LIBRARY_OUTPUT_PATH
)
set
(
CPPA_LIBRARY_OUTPUT_PATH
${
LIBRARY_OUTPUT_PATH
}
)
set
(
CPPA_LIBRARY_PATH
${
LIBRARY_OUTPUT_PATH
}
)
else
()
set
(
CPPA_LIBRARY_OUTPUT_PATH
${
CMAKE_SOURCE_DIR
}
/lib
)
set
(
CPPA_LIBRARY_PATH
${
CPPA_LIBRARY_OUTPUT_PATH
}
)
set
(
LIBRARY_OUTPUT_PATH
${
CPPA_LIBRARY_OUTPUT_PATH
}
CACHE PATH
"Single directory for all libraries"
)
endif
()
# setting path to cppa headers and libcppa
# setting path to cppa headers and libcppa
set
(
CPPA_INCLUDE_PATH
${
CMAKE_SOURCE_DIR
}
/libcppa
)
set
(
CPPA_INCLUDE_PATH
${
CMAKE_SOURCE_DIR
}
/libcppa
)
set
(
CPPA_LIBRARY_PATH
${
CPPA_LIBRARY_OUTPUT_PATH
}
)
set
(
CPPA_INCLUDE
${
CPPA_INCLUDE_PATH
}
)
set
(
CPPA_INCLUDE
${
CPPA_INCLUDE_PATH
}
)
if
(
APPLE
)
if
(
APPLE
)
set
(
CPPA_LIBRARY
${
CPPA_LIBRARY
_PATH
}
/libcppa.dylib
)
set
(
CPPA_LIBRARY
${
LIBRARY_OUTPUT
_PATH
}
/libcppa.dylib
)
elseif
(
UNIX
)
elseif
(
UNIX
)
set
(
CPPA_LIBRARY
${
CPPA_LIBRARY
_PATH
}
/libcppa.so
)
set
(
CPPA_LIBRARY
${
LIBRARY_OUTPUT
_PATH
}
/libcppa.so
)
else
()
else
()
message
(
SEND_FATAL
"Host platform not supported ..."
)
message
(
SEND_FATAL
"Host platform not supported ..."
)
endif
()
endif
()
set
(
LIBRARY_OUTPUT_PATH
${
CPPA_LIBRARY_OUTPUT_PATH
}
if
(
EXECUTABLE_OUTPUT_PATH
)
CACHE PATH
"Single directory for all libraries"
)
else
(
)
set
(
EXECUTABLE_OUTPUT_PATH
${
CPPA_BINARY_OUTPUT_PATH
}
set
(
EXECUTABLE_OUTPUT_PATH
${
CMAKE_SOURCE_DIR
}
/bin CACHE PATH
"Single directory for all executables"
)
CACHE PATH
"Single directory for all executables"
)
endif
(
)
add_subdirectory
(
unit_testing
)
add_subdirectory
(
unit_testing
)
add_subdirectory
(
examples
)
add_subdirectory
(
examples
)
...
...
benchmarks/FindLibcppa.cmake
View file @
85a4fc8d
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
${
CPPA_LIBRARY_PATH
}
/.libs
${
CPPA_LIBRARY_PATH
}
/.libs
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_INSTALL_PREFIX
}
/lib
${
CMAKE_INSTALL_PREFIX
}
/lib
${
LIBRARY_OUTPUT_PATH
}
)
)
if
(
CPPA_LIBRARY
)
if
(
CPPA_LIBRARY
)
...
...
cppa/event_based_actor.hpp
View file @
85a4fc8d
...
@@ -31,6 +31,7 @@
...
@@ -31,6 +31,7 @@
#ifndef CPPA_ABSTRACT_EVENT_BASED_ACTOR_HPP
#ifndef CPPA_ABSTRACT_EVENT_BASED_ACTOR_HPP
#define CPPA_ABSTRACT_EVENT_BASED_ACTOR_HPP
#define CPPA_ABSTRACT_EVENT_BASED_ACTOR_HPP
#include <tuple>
#include <stack>
#include <stack>
#include <memory>
#include <memory>
#include <vector>
#include <vector>
...
...
cppa/local_actor.hpp
View file @
85a4fc8d
...
@@ -189,6 +189,9 @@ class local_actor : public actor {
...
@@ -189,6 +189,9 @@ class local_actor : public actor {
do_become
(
bhvr
,
false
,
true
);
do_become
(
bhvr
,
false
,
true
);
}
}
/**
* @brief Sets the actor's behavior.
*/
inline
void
become
(
discard_behavior_t
,
behavior
&&
bhvr
)
{
inline
void
become
(
discard_behavior_t
,
behavior
&&
bhvr
)
{
do_become
(
new
behavior
(
std
::
move
(
bhvr
)),
true
,
true
);
do_become
(
new
behavior
(
std
::
move
(
bhvr
)),
true
,
true
);
}
}
...
@@ -206,10 +209,16 @@ class local_actor : public actor {
...
@@ -206,10 +209,16 @@ class local_actor : public actor {
do_become
(
bhvr
,
false
,
false
);
do_become
(
bhvr
,
false
,
false
);
}
}
/**
* @brief Sets the actor's behavior.
*/
inline
void
become
(
keep_behavior_t
,
behavior
&&
bhvr
)
{
inline
void
become
(
keep_behavior_t
,
behavior
&&
bhvr
)
{
do_become
(
new
behavior
(
std
::
move
(
bhvr
)),
true
,
false
);
do_become
(
new
behavior
(
std
::
move
(
bhvr
)),
true
,
false
);
}
}
/**
* @brief Sets the actor's behavior.
*/
inline
void
become
(
behavior
&&
bhvr
)
{
inline
void
become
(
behavior
&&
bhvr
)
{
become
(
discard_behavior
,
std
::
move
(
bhvr
));
become
(
discard_behavior
,
std
::
move
(
bhvr
));
}
}
...
@@ -237,21 +246,25 @@ class local_actor : public actor {
...
@@ -237,21 +246,25 @@ class local_actor : public actor {
become
(
keep_behavior
,
match_expr_concat
(
std
::
move
(
arg0
),
std
::
forward
<
Args
>
(
args
)...));
become
(
keep_behavior
,
match_expr_concat
(
std
::
move
(
arg0
),
std
::
forward
<
Args
>
(
args
)...));
}
}
/**
* @brief Sets the actor's behavior.
*/
template
<
typename
...
Cases
,
typename
...
Args
>
template
<
typename
...
Cases
,
typename
...
Args
>
inline
void
become
(
match_expr
<
Cases
...
>
arg0
,
Args
&&
...
args
)
{
inline
void
become
(
match_expr
<
Cases
...
>
arg0
,
Args
&&
...
args
)
{
become
(
discard_behavior
,
match_expr_concat
(
std
::
move
(
arg0
),
std
::
forward
<
Args
>
(
args
)...));
become
(
discard_behavior
,
match_expr_concat
(
std
::
move
(
arg0
),
std
::
forward
<
Args
>
(
args
)...));
}
}
/**
/**
* @brief Returns to a previous behavior or finishes execution
* @brief Returns to a previous behavior if available.
* if no previous behavior is available.
*/
*/
virtual
void
unbecome
()
=
0
;
virtual
void
unbecome
()
=
0
;
/**
/**
* @brief Can be overridden to initialize an actor before any
* @brief Can be overridden to initialize an actor before any
* message is handled.
* message is handled.
* @warning Must not call blocking functions.
* @warning Must not call blocking functions such as
* {@link cppa::receive receive}.
* @note Calling {@link become} to set an initial behavior is supported.
*/
*/
virtual
void
init
();
virtual
void
init
();
...
...
cppa/match_expr.hpp
View file @
85a4fc8d
...
@@ -586,10 +586,6 @@ struct mexpr_fwd {
...
@@ -586,10 +586,6 @@ struct mexpr_fwd {
namespace
cppa
{
namespace
cppa
{
/**
* @brief A function that works on the projection of given data rather than
* on the data itself.
*/
template
<
class
...
Cases
>
template
<
class
...
Cases
>
class
match_expr
{
class
match_expr
{
...
...
cppa/thread_mapped_actor.hpp
View file @
85a4fc8d
...
@@ -79,10 +79,13 @@ class thread_mapped_actor : public local_actor {
...
@@ -79,10 +79,13 @@ class thread_mapped_actor : public local_actor {
#else // CPPA_DOCUMENTATION
#else // CPPA_DOCUMENTATION
class
self_type
;
class
thread_mapped_actor
:
public
detail
::
stacked_actor_mixin
<
class
thread_mapped_actor
:
public
detail
::
stacked_actor_mixin
<
thread_mapped_actor
,
thread_mapped_actor
,
detail
::
abstract_actor
<
local_actor
>
>
{
detail
::
abstract_actor
<
local_actor
>
>
{
friend
class
self_type
;
// needs access to cleanup()
friend
class
detail
::
receive_policy
;
friend
class
detail
::
receive_policy
;
typedef
detail
::
stacked_actor_mixin
<
typedef
detail
::
stacked_actor_mixin
<
...
...
examples/FindLibcppa.cmake
View file @
85a4fc8d
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
${
CPPA_LIBRARY_PATH
}
/.libs
${
CPPA_LIBRARY_PATH
}
/.libs
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_INSTALL_PREFIX
}
/lib
${
CMAKE_INSTALL_PREFIX
}
/lib
${
LIBRARY_OUTPUT_PATH
}
)
)
if
(
CPPA_LIBRARY
)
if
(
CPPA_LIBRARY
)
...
...
src/uniform_type_info.cpp
View file @
85a4fc8d
...
@@ -774,8 +774,7 @@ bool announce(const std::type_info& tinfo, uniform_type_info* utype) {
...
@@ -774,8 +774,7 @@ bool announce(const std::type_info& tinfo, uniform_type_info* utype) {
return
uti_map
().
insert
({
raw_name
(
tinfo
)
},
utype
);
return
uti_map
().
insert
({
raw_name
(
tinfo
)
},
utype
);
}
}
uniform_type_info
::
uniform_type_info
(
const
std
::
string
&
uname
)
:
m_name
(
uname
)
{
uniform_type_info
::
uniform_type_info
(
const
std
::
string
&
str
)
:
m_name
(
str
)
{
}
}
uniform_type_info
::~
uniform_type_info
()
{
uniform_type_info
::~
uniform_type_info
()
{
}
}
...
...
unit_testing/FindLibcppa.cmake
View file @
85a4fc8d
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
...
@@ -45,6 +45,7 @@ else (CPPA_LIBRARY AND CPPA_INCLUDE)
${
CPPA_LIBRARY_PATH
}
/.libs
${
CPPA_LIBRARY_PATH
}
/.libs
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_LIBRARY_PATH
}
${
CMAKE_INSTALL_PREFIX
}
/lib
${
CMAKE_INSTALL_PREFIX
}
/lib
${
LIBRARY_OUTPUT_PATH
}
)
)
if
(
CPPA_LIBRARY
)
if
(
CPPA_LIBRARY
)
...
...
unit_testing/test__spawn.cpp
View file @
85a4fc8d
...
@@ -326,25 +326,17 @@ auto actor_prototype(const Args&... args) -> actor_template<decltype(mexpr_conca
...
@@ -326,25 +326,17 @@ auto actor_prototype(const Args&... args) -> actor_template<decltype(mexpr_conca
return
{
mexpr_concat
(
args
...)};
return
{
mexpr_concat
(
args
...)};
}
}
class
actor_factory
{
public:
virtual
~
actor_factory
()
{
}
virtual
actor_ptr
spawn
()
=
0
;
};
template
<
typename
InitFun
,
typename
CleanupFun
,
typename
...
Members
>
template
<
typename
InitFun
,
typename
CleanupFun
,
typename
...
Members
>
class
simple_event_based_actor_impl
:
public
event_based_actor
{
class
simple_event_based_actor_impl
:
public
event_based_actor
{
public:
public:
simple_event_based_actor_impl
(
InitFun
fun
,
CleanupFun
cfun
)
template
<
typename
...
Args
>
:
m_init
(
std
::
move
(
fun
)),
m_cleanup
(
std
::
move
(
cfun
))
{
}
simple_event_based_actor_impl
(
InitFun
fun
,
CleanupFun
cfun
,
Args
&&
...
args
)
:
m_init
(
std
::
move
(
fun
)),
m_cleanup
(
std
::
move
(
cfun
))
,
m_members
(
std
::
forward
<
Args
>
(
args
)...)
{
}
void
init
()
{
apply
();
}
void
init
()
{
apply
(
m_init
);
}
void
on_exit
()
{
m_cleanup
();
}
void
on_exit
()
{
m_cleanup
();
}
...
@@ -352,53 +344,74 @@ class simple_event_based_actor_impl : public event_based_actor {
...
@@ -352,53 +344,74 @@ class simple_event_based_actor_impl : public event_based_actor {
InitFun
m_init
;
InitFun
m_init
;
CleanupFun
m_cleanup
;
CleanupFun
m_cleanup
;
std
::
tuple
<
Members
...
>
m_members
;
detail
::
tdata
<
Members
...
>
m_members
;
void
apply
(
typename
std
::
add_pointer
<
Members
>::
type
...
args
)
{
template
<
typename
F
>
m_init
(
args
...);
void
apply
(
F
&
f
,
typename
std
::
add_pointer
<
Members
>::
type
...
args
)
{
f
(
args
...);
}
}
template
<
typename
...
Args
>
template
<
typename
F
,
typename
...
Args
>
void
apply
(
Args
...
args
)
{
void
apply
(
F
&
f
,
Args
...
args
)
{
apply
(
args
...,
&
std
::
get
<
sizeof
...(
Args
)
>
(
m_members
));
apply
(
f
,
args
...,
&
get_ref
<
sizeof
...(
Args
)
>
(
m_members
));
}
}
};
};
template
<
typename
InitFun
,
typename
...
Members
>
template
<
typename
InitFun
,
typename
CleanupFun
,
typename
...
Members
>
class
actor_tpl
:
public
actor_factory
{
class
simple_event_based_actor_factory
{
InitFun
m_init_fun
;
public:
public:
actor_tpl
(
InitFun
fun
)
:
m_init_fun
(
std
::
move
(
fun
))
{
}
typedef
simple_event_based_actor_impl
<
InitFun
,
CleanupFun
,
Members
...
>
impl
;
actor_ptr
spawn
()
{
simple_event_based_actor_factory
(
InitFun
fun
,
CleanupFun
cfun
)
auto
void_fun
=
[]()
{
};
:
m_init
(
std
::
move
(
fun
)),
m_cleanup
(
std
::
move
(
cfun
))
{
}
return
cppa
::
spawn
(
new
simple_event_based_actor_impl
<
InitFun
,
decltype
(
void_fun
),
Members
...
>
(
m_init_fun
,
void_fun
));
template
<
typename
...
Args
>
actor_ptr
spawn
(
Args
&&
...
args
)
{
return
cppa
::
spawn
(
new
impl
(
m_init
,
m_cleanup
,
std
::
forward
<
Args
>
(
args
)...));
}
}
private:
InitFun
m_init
;
CleanupFun
m_cleanup
;
};
};
template
<
typename
InitFun
,
class
TypeList
>
template
<
typename
InitFun
,
typename
CleanupFun
,
class
TypeList
>
struct
actor_tpl_from_type_list
;
struct
actor_tpl_from_type_list
;
template
<
typename
InitFun
,
typename
...
Ts
>
template
<
typename
InitFun
,
typename
CleanupFun
,
typename
...
Ts
>
struct
actor_tpl_from_type_list
<
InitFun
,
util
::
type_list
<
Ts
...
>
>
{
struct
actor_tpl_from_type_list
<
InitFun
,
CleanupFun
,
util
::
type_list
<
Ts
...
>
>
{
typedef
actor_tpl
<
Init
Fun
,
Ts
...
>
type
;
typedef
simple_event_based_actor_factory
<
InitFun
,
Cleanup
Fun
,
Ts
...
>
type
;
};
};
template
<
typename
Fun
>
template
<
typename
Init
,
typename
Cleanup
>
st
d
::
unique_ptr
<
actor_factory
>
foobaz
(
Fun
fun
)
{
st
ruct
actor_tpl_from_fun
{
typedef
typename
util
::
get_
callable_trait
<
Fun
>::
type
ctrait
;
typedef
typename
util
::
get_
arg_types
<
Init
>::
types
arg_types
;
typedef
typename
ctrait
::
arg_types
arg_types
;
typedef
typename
util
::
get_arg_types
<
Cleanup
>::
types
arg_types2
;
static_assert
(
util
::
tl_forall
<
arg_types
,
std
::
is_pointer
>::
value
,
static_assert
(
util
::
tl_forall
<
arg_types
,
std
::
is_pointer
>::
value
,
"Functor takes non-pointer arguments"
);
"First functor takes non-pointer arguments"
);
static_assert
(
std
::
is_same
<
arg_types
,
arg_types2
>::
value
||
std
::
is_same
<
util
::
type_list
<>
,
arg_types2
>::
value
,
"Second functor must provide either the same signature "
" as the first one or take zero arguments"
);
typedef
typename
util
::
tl_map
<
arg_types
,
std
::
remove_pointer
>::
type
mems
;
typedef
typename
util
::
tl_map
<
arg_types
,
std
::
remove_pointer
>::
type
mems
;
typedef
typename
actor_tpl_from_type_list
<
Fun
,
mems
>::
type
tpl_type
;
typedef
typename
actor_tpl_from_type_list
<
Init
,
Cleanup
,
mems
>::
type
type
;
return
std
::
unique_ptr
<
actor_factory
>
{
new
tpl_type
(
fun
)};
};
}
void
dummy_function
()
{
}
struct
factory
{
template
<
typename
Fun
>
static
inline
typename
actor_tpl_from_fun
<
Fun
,
void
(
*
)()
>::
type
event_based
(
Fun
fun
)
{
return
{
std
::
move
(
fun
),
dummy_function
};
}
};
size_t
test__spawn
()
{
size_t
test__spawn
()
{
using
std
::
string
;
using
std
::
string
;
...
@@ -421,14 +434,6 @@ size_t test__spawn() {
...
@@ -421,14 +434,6 @@ size_t test__spawn() {
);
);
CPPA_IF_VERBOSE
(
cout
<<
"ok"
<<
endl
);
CPPA_IF_VERBOSE
(
cout
<<
"ok"
<<
endl
);
/*
auto mirror = actor_prototype (
others() >> []() {
self->last_sender() << self->last_dequeued();
}
).spawn();
*/
CPPA_IF_VERBOSE
(
cout
<<
"test echo actor ... "
<<
std
::
flush
);
CPPA_IF_VERBOSE
(
cout
<<
"test echo actor ... "
<<
std
::
flush
);
auto
mecho
=
spawn
(
echo_actor
);
auto
mecho
=
spawn
(
echo_actor
);
send
(
mecho
,
"hello echo"
);
send
(
mecho
,
"hello echo"
);
...
@@ -498,8 +503,8 @@ size_t test__spawn() {
...
@@ -498,8 +503,8 @@ size_t test__spawn() {
await_all_others_done
();
await_all_others_done
();
CPPA_IF_VERBOSE
(
cout
<<
"ok"
<<
endl
);
CPPA_IF_VERBOSE
(
cout
<<
"ok"
<<
endl
);
CPPA_IF_VERBOSE
(
cout
<<
"test factory ... "
<<
std
::
flush
);
CPPA_IF_VERBOSE
(
cout
<<
"test
event-based
factory ... "
<<
std
::
flush
);
auto
factory
=
f
oobaz
([
&
](
int
*
i
,
float
*
,
std
::
string
*
)
{
auto
factory
=
f
actory
::
event_based
([
&
](
int
*
i
,
float
*
,
std
::
string
*
)
{
self
->
become
(
self
->
become
(
on
(
atom
(
"get_int"
))
>>
[
i
]()
{
on
(
atom
(
"get_int"
))
>>
[
i
]()
{
reply
(
*
i
);
reply
(
*
i
);
...
@@ -512,10 +517,16 @@ size_t test__spawn() {
...
@@ -512,10 +517,16 @@ size_t test__spawn() {
}
}
);
);
});
});
auto
foobaz_actor
=
factory
->
spawn
();
auto
foobaz_actor
=
factory
.
spawn
(
23
);
send
(
foobaz_actor
,
atom
(
"get_int"
));
send
(
foobaz_actor
,
atom
(
"set_int"
),
42
);
send
(
foobaz_actor
,
atom
(
"set_int"
),
42
);
send
(
foobaz_actor
,
atom
(
"get_int"
));
send
(
foobaz_actor
,
atom
(
"get_int"
));
send
(
foobaz_actor
,
atom
(
"done"
));
send
(
foobaz_actor
,
atom
(
"done"
));
receive
(
on_arg_match
>>
[
&
](
int
value
)
{
CPPA_CHECK_EQUAL
(
23
,
value
);
}
);
receive
(
receive
(
on_arg_match
>>
[
&
](
int
value
)
{
on_arg_match
>>
[
&
](
int
value
)
{
CPPA_CHECK_EQUAL
(
42
,
value
);
CPPA_CHECK_EQUAL
(
42
,
value
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment