Boost logo

Boost :

Subject: Re: [boost] Interest in Remote Procedure Call Library?
From: OvermindDL1 (overminddl1_at_[hidden])
Date: 2010-02-09 05:50:08


On Tue, Feb 9, 2010 at 3:19 AM, Rutger ter Borg <rutger_at_[hidden]> 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_at_[hidden]> 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.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk