
On Tue, Feb 9, 2010 at 3:19 AM, Rutger ter Borg <rutger@terborg.net> wrote:
OvermindDL1 wrote:
Yes, the boost function just holds a pointer to a templated struct that is refined based on the function itself, that is how that struct know how to do everything, kind of like a subclass with a single virtual function.
Yes, I guess I got that part. My question was actually: when is the deserialization code generated? Due to the deferred calling, it can only be done on the point of function invocation, i.e., without an actual function call, there's no deserialization code?
It deserializes based on the actual function signature, not based on the input stream (although with RakNet, Boost.Serialization, and my scripting engine it was all type safe both ways, and if it did not match I threw an exception). On Tue, Feb 9, 2010 at 3:19 AM, Rutger ter Borg <rutger@terborg.net> wrote:
On the receiving side, you have
id -> boost::function
where this function knows what to do based on the arguments passed on the sending side. I.e., how does the receiving side know what to deserialize? If you do
auto my_func = register.get( func_type, id );
and later on
my_func( arg1, arg2, arg2 );
doesn't this require to somehow register the fusion vector for the arguments, too? I mean, if you generate somehow
boost::function< void( void*, std::size_t ) > m_type_erased_functor = my_func.magic_trick();
then, at that point, my_func hasn't seen { arg1, arg2, arg3 } yet. Is there another trick to tackle this?
Think of it this way (in pseudo-code in some cases, copy/pasted code in others): typedef boost::function<void(RakNet::BitStream &)> invoker_function_t; template<typename Function> class RPC_Caller; class RPC_Caller_Information { protected: friend class ODL1RPC; std::string name; invoker_function_t invoker; public: ODL1RPC *odl1Rpc; }; typedef std::map<std::string, RPC_Caller_Information> invoker_function_map_t; invoker_function_map_t m_Invokers; template<typename Function> class RPC_Caller { protected: public: friend class ::RakNet::ODL1RPC; ::RakNet::ODL1RPC::RPC_Caller_Information information; Function func; public: typedef void result_type; template<class Seq> void operator()(Seq & s) const { if(/*somecondition*/true) // Call locally { boost::fusion::invoke(func,s); } if(/*somecondition*/true) // Call remotely { BitStream bs; _invoker<Function>::template reduce<Seq>(s); information.odl1Rpc->__SendCall(bs, information); } } }; template< typename Function , class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type , class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type , class Enable = void > struct _invoker { // add an argument to a Fusion cons-list for each parameter type template<typename Args> static inline void apply(Function func, RakNet::BitStream &bs, Args const &args) { typedef typename boost::mpl::deref<From>::type arg_type; typedef typename boost::mpl::next<From>::type next_iter_type; arg_type t; t << bs; ::RakNet::ODL1RPC::_invoker<Function, next_iter_type, To>::apply ( func, bs, boost::fusion::push_back(args, t) ); } template<typename Args> static inline void reduce(RakNet::BitStream &bs, Args const &args) { typedef typename boost::mpl::deref<From>::type arg_type; typedef typename boost::mpl::next<From>::type next_iter_type; boost::mpl::at_c<0>(args) >> bs; ::RakNet::ODL1RPC::_invoker<Function, next_iter_type, To>::apply ( bs, boost::fusion::pop_front(args) ); } }; template<typename Function, class To> struct _invoker<Function,To,To> { // the argument list is complete, now call the function template<typename Args> static inline void apply(Function func, RakNet::BitStream &, Args const &args) { boost::fusion::invoke(func,args); } template<typename Args> static inline void reduce(RakNet::BitStream &bs, Args const &args) { // Do Nothing } }; template<class Function> boost::fusion::unfused_generic< RPC_Caller<Function> > register_function(std::string const & name, Function f, unsigned int flags) { // Yes, the this-> is required to keep the template params dependent RPC_Caller_Information &information = this->m_Invokers[name]; information.odl1Rpc = this; information.name = name; information.invoker = boost::bind(&_invoker<Function>::template apply<boost::fusion::nil>, f, 1, boost::fusion::nil()); RPC_Caller<Function> toReturn; toReturn.information = information; toReturn.func = f; return boost::fusion::unfused_generic< RPC_Caller<Function> >(toReturn); } That was my very first RakNet version as a demonstration at just how simple that code made his RPC system so much better. It eventually evolved into the huge amount of specializations and new features that you see in RPC3 in RakNet today. I based all my code from some example somewhere in Boost, fusion maybe... perhaps function_traits... This was back when I first learned Boost.Fusion and such (it was a newish library then), looking back at my old code I could write it so much better now with even better error reporting... But yes, as you can see, it is quite simple, and you should be able to see how easy it is to add specializations and new features.