This section describes several advanced RPC techniques. The subsections are sorted roughly by their difficulty (in the increasing order) and usefulness (in the decreasing order). You should understand the replication (Chapter 7, Replication) before reading this section.
Both synchronous and asynchronous RPC can be used to trigger a replication of a callee object to a caller node:
/* Call asynchronously method bar of object foo, passing 1 as param and request replication of foo to local node for 60 seconds. */ foo->request_replica( STime( 60.0 ) ).async_bar( 1 ); /* Synchronous call with replication request. */ Int32 ires; bool bres = foo->request_replica( STime( 60.0 ) ).sync_baz( ires, 2 ); /* Asynchronous call scheduled to the future */ /* combined with replication request. */ foo->param( RPC_DELAYED, STime( 10.0 ) ). request_replica( STime( 60.0 ) ).async_bar( 2 ); |
Usefulness of this will be obvious in the next section.
The Massiv offers two methods of SRPC optimization. The first one is really simple and automatic - if the callee object is local, no RPCObject and ResultObject objects will be created. The call will be performed directly instead of the standard RPC-over-migration mechanism, and will be nearly as fast as the direct call using Pointer or WeakPointer.
![]() | Note |
---|---|
It is ensured that if the "target" object is local and not replica, it is stack-strong referenced during the call. Thus, it cannot migrate away causing an inconsistency. |
![]() | Warning |
---|---|
In the current implementation the semantics of locally optimized SRPC calls is different from the standard SRPC. If a callee throws an unmanaged exception, it will not be remapped to Massiv::Core::Lib::CoreException. |
![]() | Note |
---|---|
Asynchronous calls are never optimized this way, even if they are sheduled to ASAP (default behavior). This ensures that semantics of asynchronous calls is always the same. |
The second optimization is similar to the first one, but it must be explicitly requested. It allows to optimize SRPC to const methods as local call even when the callee object is not local, if consistent replica of the object exists on the caller node. To enable this optimization, use:
/* Call method bar of object foo synchronously. If foo is local */ /* object, or if a consistent replica of foo exists, the call */ /* will be optimized. */ Int32 ires; foo->param( RPC_ALLOW_LOCAL_REPLICA_CALL ).sync_bar( ires, 1 ); /* It's illegal to call non-const methods with the */ /* RPC_ALLOC_LOCAL_REPLICA_CALL flag set. This call will fail. */ foo->param( RPC_ALLOW_LOCAL_REPLICA_CALL ).sync_baz( ires, 2 ); /* This way, you will request replication of foo if it's */ /* not local and its replica does not exist yet, and */ /* optimize the call otherwise. */ foo->param( RPC_ALLOW_LOCAL_REPLICA_CALL ). request_replica( 60.0 ).sync_bar( ires, 3 ); /* This version is a shortcut for the same thing. */ foo->optimize_replica( 60.0 ).sync_bar( ires, 4 ); |
![]() | Note |
---|---|
Currently the last two commands are not implemented 100% optimally. If a consistent replica exists, the replication request will never be sent to the callee node. When replication timeouts, the local replica will be destroyed by the callee node, and next call will be sent over the network. That call will trigger replication again. You should be aware of this behavior. |
![]() | Warning | |
---|---|---|
Let's assume the baz method sets variable of the Foo object to param, and bar returns its previous value in result. Check the following code:
It's actually wrong! The assert (using the Massiv debugging hope macro here) may fail, iff all of the following conditions are met:
As you can see, bugs like this might be hard to debug, because the conditions of failure are met only rarely. |
![]() | Note |
---|---|
This optimization is the only place where the Massiv checks a value of the const method attribute. That's why it was said that the definition of this flag is a bit vague. At the time it was introducted to IDL its meaning was “the method is const in the C++ point of view”. However, in reality it means “replica-optimized SRPC to this method is illegal”. You may want to mark non-const (in C++ sense) methods as const in IDL, if calling this method may be dangerous if object is replica. |
This special feature allows you to call methods on replicas of a given local object, instead of on the object itself. Note that this is different from the SRPC optimization mentioned above. The call request will be delivered to all known replicas of the object, but never to the object itself. This weird feature allows object (or anyone else) to communicate with all its replicas.
For example, it can be used to implement “special effects” on client nodes. This may include, but is not limited to:
Sound effects. When an entity should make a sound, it sends request to play a given sample to all its replicas. All clients which see the entity will play the sound effect.
Graphic effects, for example sparkles, explosions. Pretty much the same thing.
Trigger animation of an entity. You could use replicated variable of an entity object to indicate which animation it should play. However, that's not very useful way to trigger one-shot animations, such as firing a gun, slashing a sword, etc.
Unfortunately, because the Massiv Demo lacks special effects, this feature is not used there at all.
Only single param() flag is needed to change behavior of asynchrnous call to call-to-replicas:
foo->param( RPC_REPLICAS ).async_bar( 1 );
/* You may delay this call as well. */
foo->param( RPC_REPLICAS | RPC_DELAYED, 10.0 ).async_bar( 2 ); |
![]() | Note |
---|---|
The object (foo in the example) must be local. Otherwise the Core does not know anything about locations of its replicas and will not deliver the calls. |
![]() | Note |
---|---|
It's illegal to request reply to asynchronous call to replicas (see next section), or to perform synchronous call to replicas. |
As you already know, the implementation of SRPC creates two objects. The ResultObject stays on the caller node, while the RPCObject migrates to the callee node (call request) and back to the caller (results). You can actually do the same thing with the asynchronous RPC too. If you set the RPC_RESULTS flag, the async_ call will return a pointer to the ResultObject, and the RPCObject will be instructed to return back to the ResultObject with the call results. You can check the ResultObject regularly to monitor the state of the call:
/* Initiate the call. */ Pointer< ResultObject > result = foo->param( RPC_RESULTS ).async_bar( 1 ); /* Later somewhere in a galaxy far away... :) */ /* Check state of the call. */ if( result->state != ResultObject::STATE_UNKNOWN ) { if( result->state == ResultObject::STATE_SUCCESS ) { /* Get a packet with method results. */ std::auto_ptr< MethodPacket > packet( result->create_results() ); /* Get the value of the boolean return type as a string. */ const std::string bres_str( packet->get_argument_value( -1 ) ); /* Get the value of the integral out argument result. */ const std::string ires_str( packet->get_argument_value( 0 ) ); /* Cast to the real packet type. */ METHOD_PACKET( Foo, bar ) * const results = checked_cast< METHOD_PACKET( Foo, bar ) * > ( packet.get() ); /* Access results directly. */ const bool bres = results->_result; const Int32 ires = results->result; /* Can access param too, but its value will be random. */ const Int32 irand = results->param; } } |
It's illegal to request reply from client-side objects (the only one with well-known object id is client node object).
See Section 8.8, “RPC Reference Guide” for complete listing of public methods of ResultObject and MethodPacket.
A call where method name and arguments are not known at compile-time and are constructed at run-time (usually from strings) is called a dynamic call. Metaobjects (see Chapter 12, Metaobjects) provide several methods that can be used to perform a dynamic synchronous RPC. The following example demonstrates a dynamic call using method which identifies the method to call by a string. See the Massiv Core Reference Guide for in-depth reference guide to the MetaObject API.
![]() | This is the easiest way to obtain a pointer to a metaobject of given managed object. See also Section 12.2.1, “Obtaining a MetaObject”. | ||||
![]() | In this case we identify the method to call by its name. | ||||
![]() | Here a stream containing all in and inout arguments is constructed. The arguments are expected to be stored in their textual serialization form. In the example, a serializable type and a text writer object are used to ensure that the argument is serialized correctly. If the method foo() had multiple arguments, method write_space() of TextWriter would be used to separate the arguments.
The Serializer::Description object must be passed to all serialization methods. It's used to describe special serialization options during migration and replication. Always use default-constructed serializer description here, it ensures that all properties will be serialized as needed by the remote call. | ||||
![]() | A TextReader object is constructed from the argument stream. The next_line() method must be called to intruct the reader to read in the first (and only) line of the stream. | ||||
![]() | This is how the call itself is made. It may throw a Lib::RemoteCallFailedException exception with “Invalid method name” description if the method name is invalid (the object does not have such method), serialization exception if the arguments are not stored properly in the stream, or any RPC-related exception as described in the previous sections. On success, a new method packet with method results is returned. It's documented in Section 8.8, “RPC Reference Guide”. |
![]() | Note |
---|---|
TextWriter and TextReader classes simplify work with line-oriented textual streams. If you find them useful, you can freely use them in your Massiv-based application. For example, the Massiv Demo uses these classes to parse all textual data objects. Refer the Massiv Core Reference Guide for more information about the classes and their methods. |
![]() | Note |
---|---|
Currently there is no easy way to perform a dynamic asynchronous RPC. You could check how the remote_call_method() methods are implemented (see src/core/object/metaobject.cpp) and use it as a starting point for implementation of dynamic asynchronous RPC. It should be quite easy; asynchornous dynamic RPC has never been implemented because no-one really needed it yet. |