Networking serialization

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Networking serialization

Post by LunaRebirth »

This question is based on almost no research behind it, so bear with me.

I'm making a function that easily sends an object, an IGUIImage object that uses IGUIElement functions, over a socket connection.

I want to keep the serialized object below 1400 bytes (fits into a char[1400]).
If I were to use Cereal or Boost, would this complete the task?

I'm debating either sending the object as serialized, in case the client changed an object (I.E. moved a single vertex on a 3D object) or sending specific variables (I.E. X, Y, Z, Rotation, etc.) and making the other client manually also change the vertex (maybe another send() for the changed variable).

My question is basically, would it be better for me to serialize the object and send it, or send pieces of information about an object?
The disadvantages of pieces would be that not all information about an object would send over (obviously), but I'm assuming that a serialized IGUIImage element may be a very large amount of characters, and I want fast speeds.

Which route should I take?
Rayshader
Posts: 21
Joined: Tue Mar 28, 2017 1:18 pm

Re: Networking serialization

Post by Rayshader »

LunaRebirth wrote: I want to keep the serialized object below 1400 bytes (fits into a char[1400]).
If I were to use Cereal or Boost, would this complete the task?
I don't know for Cereal, but Boost is a library containing a bunch of API to deal with different tasks... including networking.
So I guess you want to send packet through network limited to a maximum of 1400 bytes. Fine, this is your "standard" and it's up to you to limit the amount of data send in a packet. Therefore Boost will complete the task.
LunaRebirth wrote: I'm debating either sending the object as serialized, in case the client changed an object (I.E. moved a single vertex on a 3D object) or sending specific variables (I.E. X, Y, Z, Rotation, etc.) and making the other client manually also change the vertex (maybe another send() for the changed variable).

My question is basically, would it be better for me to serialize the object and send it, or send pieces of information about an object?
The disadvantages of pieces would be that not all information about an object would send over (obviously), but I'm assuming that a serialized IGUIImage element may be a very large amount of characters, and I want fast speeds.

Which route should I take?
In my opinion, it depends of how often pieces of information about an object will changes through times ? Let's say small information like X, Y floats change often but your image change sometimes, I believe you may just send pieces of information. This way, your packet will contain empty values when there is no change for them...

Question, is it really necessary for you to send an IGUIImage through a socket connection ? Don't all your clients are able to load the texture from an assets directory ?
CuteAlien
Admin
Posts: 9628
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Networking serialization

Post by CuteAlien »

One solution is - you have a list of files with date and size. On both systems. The client asks for that list from the server and compares to it's own. When they differ the client requests the missing files. How often you update those lists... depends on your application (at least on client-start, maybe every time the files on server change additionally).

You don't want to send the full image every time it's needed. Some kind of cache on client will be necessary usually.

I wouldn't use boost serialization. Find a simple serialization lib which simply sends data per type. Like sendBool, sendInt, sendString, etc (maybe it even uses templates then you will have only 2-3 send/receive functions). I don't know which are good (I always write my own), but boost has too much overhead for my taste. It will also serialize stuff like version info which depending on the project is nice or stuff that just slows down your application - also I think it doesn't serialize network independent data if I remember right, thought maybe that can be made working. I just remember I wasn't too happy I chose it last time I used it, but that was a decade ago, so... maybe I'm wrong.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Rayshader
Posts: 21
Joined: Tue Mar 28, 2017 1:18 pm

Re: Networking serialization

Post by Rayshader »

Ok see why you need the full image.
Then when requesting the missing files, just store downloaded files. For this, a simple TCP connection with a while, a read from the file and a write to the socket with a buffer and you're done...
I've never used Boost for network, in fact I always done my own network layer based on socket, bind, connect, listen, write and read functions. This way you have a simple network layer, you know how it works and how it is optimized.
For serialization, you can easily do it your self. Template could be good choice. And you could use an interface like:

Code: Select all

 
class ISerialize {
 
public:
  virtual ~ISerialize() {};
 
  // Serialize instance and return the data.
  // Set size variable to the size of the data.
  virtual void* serialize(int& size) = 0;
 
  // Unserialize instance with given data and size.
  virtual ISerialize& unserialize(void* data, int size) = 0;
}
 
All classes that need to be send over the network shall inherit from this interface. You then have to call serialize/unserialize before writing or after reading from socket.
CuteAlien wrote: ... Find a simple serialization lib which simply sends data per type. Like sendBool, sendInt, sendString, etc ...
When saying "sends data per type", you mean calling sendX would send a packet to the socket with a variable of type X in it ? If so, don't... Put as much data as you can in one packet and then send it. As long it is logical and data in the packet are structured. It would be very network consuming to send data type after type over the socket.
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: Networking serialization

Post by LunaRebirth »

Sorry, I failed to explain a few things.

I already have the networking section done, including file updates.
The software I'm designing is an online collaborative game editor, so if someone codes something, it'll show up on their friend's screen in real-time too.
The networking is set up to send everything as a char* -- so if a player codes a box with networking using Lua, it'll send the box X and Y coordinates as an int stored inside of the char*, send it, and the other clients receive it as a char* that converts each piece into an int.

So for this to work using my current system, I would need the serialized information to be able to be put into a char*, sent to another client, and then the client to convert it into an IGUIImage or whatever.
The point of doing this over other mentioned strategies is that I don't know what a person would want to code.
Assuming a client codes an object to put a single vertex at 0,0, I want even that information to send over for easy networking (I.E. object:sendToClients() rather than my current server:send("object","30,30,20") and then using that data to update the position).

If that makes sense
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: Networking serialization

Post by LunaRebirth »

Rayshader wrote:

Code: Select all

 
class ISerialize {
 
public:
  virtual ~ISerialize() {};
 
  // Serialize instance and return the data.
  // Set size variable to the size of the data.
  virtual void* serialize(int& size) = 0;
 
  // Unserialize instance with given data and size.
  virtual ISerialize& unserialize(void* data, int size) = 0;
}
 
All classes that need to be send over the network shall inherit from this interface. You then have to call serialize/unserialize before writing or after reading from socket.
Sweet so if I use this code, I could send the serialized information over the socket to a client, and then unserialize it on the receiving client and it'll convert it to the proper IGUIImage without messing up pointers?
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: Networking serialization

Post by LunaRebirth »

Rayshader wrote:Boost is a library containing a bunch of API to deal with different tasks... including networking.
I'm strongly opinionated on the idea that I should stay away from Boost because the file is so large and I don't need 99% of the things it has.
My networking is complete with Winsock
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: Networking serialization

Post by LunaRebirth »

Rayshader wrote:In my opinion, it depends of how often pieces of information about an object will changes through times ? Let's say small information like X, Y floats change often but your image change sometimes, I believe you may just send pieces of information. This way, your packet will contain empty values when there is no change for them...

Question, is it really necessary for you to send an IGUIImage through a socket connection ? Don't all your clients are able to load the texture from an assets directory ?
Yes, all asset directories will have the same information, it's in case of an update on an image x, y, w, h, setScaleImage, setColor, etc. but also for more complex things like an IAnimatedMeshSceneNode.
It will be up to the server owner how he/she wants to send the information from one client to another, I'm just putting together an application to make it easy for them to do
Rayshader
Posts: 21
Joined: Tue Mar 28, 2017 1:18 pm

Re: Networking serialization

Post by Rayshader »

Ok it's more clear.
The ISerialize interface allow you to serialize/unserialize data as you want. If your class contains pointer, it will be up to you to serialize them too. But this pointer (if it is a class) could inherit itself of this ISerialize interface. You would then in the functions serialize/unserialize common types and call serialize/unserialize from the pointer (class).
For things like ITexture, IAnimatedMeshSceneNode, I see two ways :
You manage your own scene node layer with your own class containing an ITexture or IAnimatedMeshSceneNode. In this case, your own class could simply inherit from ISerialize and you may serialize/unserialize your ITexture/IAnimatedMeshSceneNode yourself.
Or, maybe a factory which would receive an ITexture or an IAnimatedMeshSceneNode and serialize/unserialize them by gathering needed informations (mesh file, position, rotation, scale, etc.) in order to be able to recreate them when received.

Code: Select all

 
enum eIrrType {
  EIT_NONE,
  EIT_TEXTURE,
  EIT_ANIMATEDMESH
};
 
class NetworkFactory {
 
  map<eIrrType, char* (NetworkManager::*)(void*)> _serializers;
  map<eIrrType, void* (NetworkManager::*)(char*)> _unserializers;
  ISceneManager* _scene;
 
public:
  NetworkFactory(ISceneManager* scene);
  ~NetworkFactory();
 
  char* serialize(eIrrType type, void* instance);
  void* unserialize(eIrrType type, char* data);
 
private:
  char* serializeTexture(const void* texture);
  char* serializeAnimatedMesh(const void* animatedMesh);
 
  void* unserializeTexture(char* data);
  void* unserializeAnimatedMesh(char* data);
 
With pointer member functions, two maps, two loops... You could do it.
Last edited by Rayshader on Tue Apr 11, 2017 5:20 pm, edited 1 time in total.
CuteAlien
Admin
Posts: 9628
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Networking serialization

Post by CuteAlien »

Rayshader wrote:
CuteAlien wrote: ... Find a simple serialization lib which simply sends data per type. Like sendBool, sendInt, sendString, etc ...
When saying "sends data per type", you mean calling sendX would send a packet to the socket with a variable of type X in it ? If so, don't... Put as much data as you can in one packet and then send it. As long it is logical and data in the packet are structured. It would be very network consuming to send data type after type over the socket.
No - I didn't mean to send them one by one. Just easy commands to serialize variables of different types into a block. You still only send a single block at the end.


@LunaRebirth: You don't have to handle everything in a single protocol. For example don't mix up sending player-moves with file transfers in one protocol. Or a show-image-click with a send-image-file (because second click for example no longer sends the file as it should be cached by now).
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
MartinVee
Posts: 139
Joined: Tue Aug 02, 2016 3:38 pm
Location: Québec, Canada

Re: Networking serialization

Post by MartinVee »

Have you checked Google's Protocol Buffer? It's awesome for serializing.
LunaRebirth
Posts: 386
Joined: Sun May 11, 2014 12:13 am

Re: Networking serialization

Post by LunaRebirth »

CuteAlien wrote:LunaRebirth: You don't have to handle everything in a single protocol. For example don't mix up sending player-moves with file transfers in one protocol. Or a show-image-click with a send-image-file (because second click for example no longer sends the file as it should be cached by now).
I've been working on strictly data transfers for the past few months. I'm able to send multiple things (up to 350 variables - less when sending files - to be exact) at once.
The way I have it working right now is that a client using the in-game script editor can code a networking system to show hundreds of players as boxes moving around with the following script:

Code: Select all

server:sendToClients("box", myID .. "," .. myBox:getX() .. "," .. myBox:getY() .. "," .. myBox:getW() .. "," .. myBox:getH());
 
while server:getCommand("box") do
  vars = operations:getTokens(command.str);
  if boxes[var[0]] == nill then
    boxes[var[0]] = CreateImage("Box.png");
  end
  boxes[var[0]]:setRect(var[1],var[2],var[3],var[4]);
end
It works successfully with no issues, tested on Amazon EC2 from a previous post.
Duplicate sends with not send, so there are no issues with stacking data transfers.

Thing is, I want to reduce it simply to

Code: Select all

myBox:sendToClients();
I figured the easiest way to do this to make scripting very easy on players would be to send the object as a serialized object for the receiving clients to deserialize.
But seeing how an IGUIElement has a pointer to the IGUIImage which has an ITexture, and how an IAnimatedMeshSceneNode has a mesh which has a texture, this might be very difficult, or at least very slow since it would be sending data as large as a file.

Maybe I'll just stick to sending very basic information and then the person scripting the receiving end will just have to account for the fact that setScaleImage, setColor, etc, will not transfer over using the function.
MartinVee wrote:Have you checked Google's Protocol Buffer? It's awesome for serializing.
Looks cool, I'll check it out. Thanks!
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Re: Networking serialization

Post by sudi »

If you want some quick serialization you can do something like this which is doing some magic based on if it is a pointer or not

Code: Select all

 
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <iostream>
#include <string.h>
 
class IDataStream;
 
template<typename T, typename D = void*>
class TypeSerializer
{
public:
    void serialize(T& object, IDataStream* stream, D provider) const
    {
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
    }
};
 
class IDataStream
{
public:
    template<typename T, typename D = void*>
    void Write(T& value, D provider = nullptr)
    {
        TypeSerializer<T, D> serializer;
        serializer.serialize(value, this, provider);
    }
 
    template<typename T, typename D = void*>
    void Read(T& value, D provider = nullptr)
    {
        TypeSerializer<T, D> serializer;
        serializer.deserialize(this, &value, provider);
    }
 
    virtual unsigned int getSize() const { return 0; }
    virtual const void* getData() const { return nullptr; }
 
    virtual void write(const int& size, void* data)  {}
    virtual void* read(const unsigned int& size) { return nullptr; }
};
 
class Writer : public IDataStream
{
    std::vector<unsigned char> stream;
 
    void write(const int& size, void* data)
    {
        for (int i=0;i<size;i++)
        {
            stream.push_back(((unsigned char*)data)[i]);
        }
    }
 
    virtual unsigned int getSize() const
    {
        return stream.size();
    }
 
    virtual const void* getData() const
    {
        return &stream[0];
    }
};
 
class Reader : public IDataStream
{
 
public:
 
    std::vector<unsigned char> stream;
    int pointer;
 
    Reader(int size, const void* data)
    {
        pointer = 0;
        for (int i=0;i<size;i++)
        {
            stream.push_back(((unsigned char*)data)[i]);
        }
    }
 
    void* read(const unsigned int& size)
    {
        int old = pointer;
        pointer += size;
        return &stream[old];
    }
};
 
class Image
{
public:
    std::string name;
    Image(const char* name = "")
    {
        this->name = name;
    }
};
 
class ImageManager
{
public:
    std::vector<Image*> images;
 
    Image* load(const char* name)
    {
        for (Image* image : images)
        {
            if (image->name == name)
                return image;
        }
        Image* image = new Image(name);
        images.push_back(image);
        return image;
    }
};
 
class Pet
{
public:
    int health;
    Image image;
};
 
class Player
{
public:
    int health;
    Image* image;
    int weapons;
    Pet pet;
};
 
template<typename D> class TypeSerializer<int,D>
{
public:
    void serialize(int& object, IDataStream* stream, D provider) const
    {
        std::cout << "Serialize Int" <<std::endl;
        stream->write(4, (void*)&object);
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
        std::cout << "Deserialize Int" <<std::endl;
        memcpy(target, stream->read(4), 4);
    }
};
 
template<typename D> class TypeSerializer<std::string, D>
{
public:
    void serialize(std::string& object, IDataStream* stream, D provider) const
    {
        std::cout << "Serialize String" <<std::endl;
        int size = object.size();
        stream->Write(size);
        stream->write(object.size()+1, (void*)&object[0]);
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
        std::cout << "Deserialize String" <<std::endl;
        int length = 0;
        stream->Read(length);
        char* str = new char[length];
        memcpy(str, stream->read(length+1), length+1);
        (*(std::string*)target) = str;
    }
};
 
template<typename D> class TypeSerializer<Player, D>
{
public:
    void serialize(Player& object, IDataStream* stream, D provider) const
    {
        std::cout << "Serialize Player" <<std::endl;
        stream->Write(object.health);
        stream->Write(object.image, provider);
        stream->Write(object.weapons);
        stream->Write(object.pet, provider);
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
        std::cout << "Deserialize Player" <<std::endl;
        Player* player = new(target)Player();
        stream->Read(player->health);
        stream->Read(player->image, provider);
        stream->Read(player->weapons);
        stream->Read(player->pet, provider);
    }
};
 
template<typename D> class TypeSerializer<Pet, D>
{
public:
    void serialize(Pet& object, IDataStream* stream, D provider) const
    {
        std::cout << "Serialize Pet" <<std::endl;
        stream->Write(object.health);
        stream->Write(object.image, provider);
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
        std::cout << "Deserialize Pet" <<std::endl;
        Pet* player = new(target)Pet();
        stream->Read(player->health);
        stream->Read(player->image, provider);
    }
};
 
template<> class TypeSerializer<Image*, ImageManager*>
{
public:
    void serialize(Image*& object, IDataStream* stream, ImageManager* imageManager) const
    {
        std::cout << "Serialize Image*: " << object->name <<std::endl;
        stream->Write(object->name);
    }
 
    void deserialize(IDataStream* stream, void* target, ImageManager* imageManager) const
    {
        std::cout << "Deserialize Image*" <<std::endl;
        std::string name;
        stream->Read(name);
        (*(Image**)target) = imageManager->load(name.c_str());
    }
};
 
template<typename D> class TypeSerializer<Image, D>
{
public:
    void serialize(Image& object, IDataStream* stream, D provider) const
    {
        std::cout << "Serialize Image: "<< object.name <<std::endl;
        stream->Write(object.name);
    }
 
    void deserialize(IDataStream* stream, void* target, D provider) const
    {
        std::cout << "Deserialize Image" <<std::endl;
        std::string name;
        stream->Read(name);
        new(target)Image(name.c_str());
    }
};
 
int main(int argc, char* argv[])
{
    int i = 4782;
 
    IDataStream* writer = new Writer();
    writer->Write(i);
    std::cout << "Writer Size: " << writer->getSize() << std::endl;
    IDataStream* reader = new Reader(writer->getSize(), writer->getData());
    int b = 0;
    reader->Read(b);
    printf("Value: %i\n", b);
 
 
    ImageManager imageManager;
 
    Player player;
    player.health = 78;
    player.weapons = 2;
    player.image = imageManager.load("avatar.jpg");
    player.pet.health = 100;
    player.pet.image.name = "dog.jpg";
 
    writer = new Writer();
    writer->Write(player, &imageManager);
    reader = new Reader(writer->getSize(), writer->getData());
 
    printf("savedPlayerHealth: %i\n", player.health);
    printf("savedPlayerImage: %s\n", player.image->name.c_str());
    printf("savedPlayerImageP: %p\n", player.image);
    printf("savedPlayerWeapons: %i\n", player.weapons);
    printf("savedPlayerPetHealth: %i\n", player.pet.health);
    printf("savedPlayerPetImage: %s\n", player.pet.image.name.c_str());
    printf("savedPlayerPetImageP: %p\n", player.pet.image);
 
    Player loaded;
    printf("PlayerHealth: %i\n", loaded.health);
    printf("PlayerWeapons: %i\n", loaded.weapons);
    printf("PlayerImageP: %p\n", loaded.image);
    printf("PlayerPetHealth: %i\n", loaded.pet.health);
    printf("PlayerPetImage: %s\n", loaded.pet.image.name.c_str());
    printf("PlayerPetImageP: %p\n", &loaded.pet.image);
 
    reader->Read(loaded, &imageManager);
    printf("loadedPlayerHealth: %i\n", loaded.health);
    printf("loadedPlayerWeapons: %i\n", loaded.weapons);
    printf("loadedPlayerImage: %s\n", loaded.image->name.c_str());
    printf("loadedPlayerImageP: %p\n", loaded.image);
    printf("loadedPlayerPetHealth: %i\n", loaded.pet.health);
    printf("loadedPlayerPetImage: %s\n", loaded.pet.image.name.c_str());
    printf("loadedPlayerPetImageP: %p\n", &loaded.pet.image);
    return 0;
}
 
PS: you need to compile with -std=c++11 because of the default template argument
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
CuteAlien
Admin
Posts: 9628
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Networking serialization

Post by CuteAlien »

Irrlicht has as serialization mechanism for it's objects. Most classes are derived from IAttributeExchangingObject. It's using xml - which might or might not be a problem (for this case it might be fine). But it also avoids serializing files (aka meshes and textures). Instead it expects those files to exist. But if you split the problem up in synchronizing files and serializing objects as 2 separate problems it might offer a possible solution. I don't think files should be serialized as part of memory objects - they are just too different.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Post Reply