2.Marshaling PIDL parameter type

In PIDL, you can use various types such as integer, string and etc besides from the basic variable.

Note: Marshaling in C# language is guided in C# Marshaling user-defined type in C# .

2.1Using user-defined class type in RMI

The act of converting RMI call to message or extracting necessary value from message to call RMI is called marshaling.

ProudNet offers a marshaling function for the basic type such as int or float.

Of course, you can also use user-defined class type besides from the basic one. In order to use user-defined class type, you need to create overloading of following functions.

// MyType.h

namespace Proud
{
// Ouput the contents within called RMI after converting them to strings.
// This is convenient in making a log.
void AppendTextOut(String &a,const MyType &b);

// Read the custom type content from message buffer.
CMessage& operator>>(CMessage &a, MyType &b);

// Insert the custom type content in message buffer.
CMessage& operator<<(CMessage &a, const MyType &b);
}

ProudNet's RMI function uses Proud.CMessage internally. And through overloading of above functions, RMI parameter gets marshaling.

Here is the case of using Proud.CMessage. Proud.CMessage holds message data that is used for converting RMI to message or reading parameter from message and also can be used as stream object.

How to stream marshaling function is shown in below.

// MyType.cpp

namespace Proud
{
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        a>>b.x,b.y>>b.z>>b.w;
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        // Do not use a.UseInternalBuffer()!
        a<<b.x,b.y<<b.z<<b.w;
        return a;
    }
    void AppendTextOut(String &a,const MyType &b)
    {
        String f;
        f.Format(L"{x=%f,y=%f,z=%f,w=%f}",b.x,b.y,b.z,b.w);
        a+=f;
    }
}

In last, the header file that declares above overloading methods must be #include first before #include proxy and stub files generated by PIDL compiler.

// Example 1
#include "MyType.h"
#include "MyPIDL_proxy.h"

// Example 2
#include "MyType.h"
#include "MyPIDL_stub.h"

The practical use of marshaling is guided in 9. Sample code of marshaling custom type object or <Sample/CasualGame/GCServer/FarmCommon.h>.

Also you can check whether it works or not with Proud.TestMarshal()

2.2Changing marshaling method depending on condition

There is a method to marshal character information that differs depending on validity or invalidity of filed between character types. You can also have multi-methods of marshaling by taking leverage on switch/case characters or polymorphic objects. For easier understanding, we made an example of using swich/case as followed.

namespace Proud
{
    enum UnitType
    {
        Zergling,     // Zergling of STARCRAFT (Typical ground attack unit)
        Queen,        // Queen of STARCRAFT (Aerial unit with special ability)
        Broodling    // Temporary units produced by Queen by using Broodling ability. Have a short lifetime.
    };

    struct Unit
    {
        UnitType m_type;          // Type of unit
        Vector2D m_position;    // Position of unit
        int m_energy;            // Remaining magic energy of unit (or mana) -> valid to Queen only
        float m_lifeTime;        // Remaining lifetime of unit -> valid to Broodling only
        int m_attackPower;        // Attacking power of unit->vaild to Zergling only
    };

    CMessage& operator<<(CMessage& msg,const Unit& unit)
    {
        msg<<unit.m_type<<unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg<<unit.m_attackPower;
            break;
        case Queen:
            msg<<unit.m_energy;
            break;
        case Broodling:
            msg<<unit.m_lifeTime;
            break;
        }
        return msg;
    }

    CMessage& operator>>(CMessage& msg,Unit& unit)
    {
        msg>>unit.m_type>>unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg>>unit.m_attackPower;
            break;
        case Queen:
            msg>>unit.m_energy;
            break;
        case Broodling:
            msg>>unit.m_lifeTime;
            break;
        }
        return msg;
    }
}

2.3Marshaling data in Bit

To reduce the amount of data being saved message, you can marshal data in bit during Proud.CMessage has methods that can save data in bit as listed in below.

Read from bit: Proud.CMessage.ReadBits

Write in bit: Proud.CMessage.WriteBits

Here is an example.

namespace Proud
{
    struct MyType
    {
        int x,y,z;
    };
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        // Use 6 bits to read x value
        a.ReadBits(b.x,6);
        // Use 3 bits to read y value
        a.ReadBits(b.y,3);
        // Use 15 bits to read z value. The total bits used for reading is 6+3+15=24 bits (3 bytes)
        a.ReadBits(b.z,15);
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        a.WriteBits(b.x,6); // Save x,y and z in bit.
        a.WriteBits(b.y,3);
        a.WriteBits(b.z,15);
        return a;
    }
}

Things you need to be careful about as marshaling data in bit.

The number of bits that you want to use for saving should not exceed the range of actual value. For example, let's say there is an int which has negative value. Then the first bit of that int will be 1. So if you push to save it with less than 31 bits to reduce the amount of bits, you would end up losing the first bit value. Thus you need to be cautious as using read/write in bits.

2.4Marshaling enum type

You can marshal enum type by the following functions.

enum type { ... } ;

namespace Proud
{
    inline CMessage& operator<<(CMessage& a,type b)
    {
        a<<(int)b;
        return a;
    }
    inline CMessage& operator>>(CMessage& a,type& b)
    {
        int x;
        a>>x;
        b=(type)x;
        return a;
    }
    inline void AppendTextOut(String &a,type b)
    {
        String txt;
        txt.Format(L"%d",(int)b);
        a+=txt;
    }
}

It is much easier to create the above setup if you use the already defined macros of ProudNet, PROUDNET_SERIALIZE_ENUM.

PROUDNET_SERIALIZE_ENUM(type)

2.5Marshaling collection (array and etc.)

ProudNet is already set to direcly perform marshaling of a few of the basic collection (array and etc.) types by supporting Proud.CFastArray, Proud.CFastMap, std.vector and CAtlArray. Please refer to operator>>, operator<< override from marshaler.h for more details.

Following is the case showing how array type is being declared from PIDL.

Foo([in] Proud::CFastArray<MyType> a, [in] std::vector<MyType> b);

If there are any collection types in need of marsharling beyond the basic collection type defined in marshaler.h, override must be set to respond to those collection types. How you can override is guided in 2.1 Using user-defined class type in RMI. If you want to see the implemented case of override, pleas refer to marshaler.h.

Next is the example of marshaling std.vector. If there are any collection types that ProudNet does not support as default then all you need to do is to create a marshaling routine as referring to below.

namespace Proud
{
    // serialization functions that can be used in vector
    // for output to stream    template<typename elem>
    inline CMessage& operator>>(CMessage &a, std::vector<elem> &b)
    {
        // Gain size
        int size;
        a >> size;

        // Prompt exception if the size is unacceptable. This could be caused by hacking.
        if (size<0 ||size >= CNetConfig::MessageMaxLength)
            ThrowExceptionOnReadArray(size);

        // to reduce memory frag
        b.reserve(size);
        b.resize(0);

        // read array one by one.
        elem e;
        for (int i = 0;i < size;i++)
        {
            a >> e;
            b.push_back(e);
        }
        return a;
    }

    // serialization functions that can be used in vetor lists, unary itme elem and etc.
    // for input from stream    template<typename elem>
    inline CMessage& operator<<(CMessage &a, const std::vector<elem> &b)
    {
        // Write the size of array.
        int size = (int)b.size();
        a << size;
        
        // Write each array factor.
        for (std::vector<elem>::const_iterator i = b.begin();i != b.end();i++)
        {
            a << (*i);
        }
        return a;
    }

    template<typename elem>
    inline void AppendTextOut(String &a, std::vector<elem> &b)
    {
        a += L"<vector>";
    }
}