Search code examples
c#unity-game-enginesteamworks-api

"Object reference not set to an instance of an object" with my IntPtr into my struct - C#


I'm working on my multiplayer game (with FacepunchStudio implementation) on Unity, and I have a weird behaviour with my unmarshalling.

For send data between players, I use the 'SteamSocketManager' and I need to use Marshal. So, I have created 2 struct:

public struct Data
{
    public TypeInfo typeData;
    public IntPtr data;
}

public struct PlayerInfo
{
    public uint idNetwork;
    public CommandType typeCommand;
    public Vector3 position, rotation;
    public float xAnimator, yAnimator;
}

PlayerInfo: it's for all player information.

Data: it's to make a generic structure for all data between the server and clients.

When a player is connected to the socket, the server sends a request for creating the player locally and create a 'remote' of the player to the other players.

public override void OnConnected(Connection connection, ConnectionInfo data)
    {
        base.OnConnected(connection, data);
        Debug.Log($"{data.Identity} has joined the game");
        
        // Prepare the player experience
        uint idUser = connection.Id;
        Vector3 playerPosition = new Vector3(UnityEngine.Random.Range(-area, area), 1, UnityEngine.Random.Range(area, area));
        Debug.Log($"new pos: {playerPosition}");

        // @ 1 > Create a new PlayerInfo for the server
        PlayerInfo infoPlayer = new PlayerInfo()
        {
            idNetwork = idUser,
            typeCommand = CommandType.Remote,
            position = playerPosition,
            rotation = new Vector3(0,0,0),
            xAnimator = 0,
            yAnimator = 0
        };
        players.Add(idUser, infoPlayer);
        int sizePlayerInformation = Marshal.SizeOf(infoPlayer);

        // @ 2 > Send the 'Remote' PlayerInfo to the other players
        int sizeMarcel = Marshal.SizeOf(infoPlayer);
        IntPtr infoRemotePtr = Marshal.AllocHGlobal(sizeMarcel);
        Marshal.StructureToPtr(infoPlayer, infoRemotePtr, false);
        Data remoteD = new Data
        {
            typeData = TypeInfo.PlayerInfo,
            data = infoRemotePtr
        };
        IntPtr dataRemote = Marshal.AllocHGlobal(Marshal.SizeOf(remoteD));
        Marshal.StructureToPtr(remoteD, dataRemote, false);
        SteamManager.Instance.RelaySocketMessageReceived(dataRemote, Marshal.SizeOf(remoteD), idUser);

        // @ 3 > Send the 'Create' Player to the new player
        infoPlayer.typeCommand = CommandType.Create;
        int sizeCreatePlayerData = Marshal.SizeOf(infoPlayer);
        IntPtr infoCompressed = Marshal.AllocHGlobal(sizeCreatePlayerData);
        Marshal.StructureToPtr(infoPlayer, infoCompressed, false);
        Data dataInfoPlayer = new Data
        {
            typeData = TypeInfo.PlayerInfo,
            data = infoCompressed
        };
        IntPtr dataCreate = Marshal.AllocHGlobal(Marshal.SizeOf(dataInfoPlayer));
        Marshal.StructureToPtr(dataInfoPlayer, dataCreate, false);
        Result sc = connection.SendMessage(dataCreate, Marshal.SizeOf(dataInfoPlayer), SendType.Reliable);
        if (sc != Result.OK)
        {
            Result retry = connection.SendMessage(dataCreate, Marshal.SizeOf(dataInfoPlayer), SendType.Reliable);
            if (retry != Result.OK) Debug.LogError("EET BRO, CA MARCHE PAS KENNY");
        }
        Debug.Log(dataCreate);
    }

And when the player receives a new message from the server, this message is unmarshall by this Method: Data data = MultiPlayerSocket.UncompressData(messageIntPtr);

After, I check if the type of the data is a 'PlayerInfo'.

If is true: a unmarshall this data into a PlayerInfo struct, and a execute all actions wanted.

public void ProcessMessageFromSocketServer(IntPtr messageIntPtr, int dataBlockSize)
    {
        try
        {
            // Data decompressing
            print($"<< DECOMPRESS +{messageIntPtr}");
            Data data = MultiPlayerSocket.UncompressData(messageIntPtr);
            print("data.data = " + data.data);

            // Do something with received message
            if (data.typeData == TypeInfo.PlayerInfo)
            {
                print("Execute Player Info");
                print(data.data + "|| " + data.typeData);
                PlayerInfo player = (PlayerInfo)Marshal.PtrToStructure(data.data, typeof(PlayerInfo));
                print($"DATA DECOMPRESSED (Player == )");
                print("Player: "+ player+ "|| idNet: "+player.idNetwork);
                ExecutePlayerDataCommand(player);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Unable to process message from socket server \n\n --[ERROR({e.Source})]-- \n\n "+e.Message);
        }
    }

All this system works if you are connected in 'Owner' of the Lobby. But if you are connected just in 'Client', a have this error:

Litelo invited you to his lobby.
Client joined the lobby
SocketIP_Local: 3938562153
wouhu, connection right here '$-$ 
<< DECOMPRESS +2249505835568
data.data = 2387748430064
Execute Player Info
2387748430064|| PlayerInfo
Unable to process message from socket server 

 --[ERROR()]-- 

 Object reference not set to an instance of an object
Connection Got A Message 2249505835568

the problem seems to from

PlayerInfo player = (PlayerInfo)Marshal.PtrToStructure(data.data, typeof(PlayerInfo));

Anybody can help me to solve this problem ?


Solution

  • This won't work:

            IntPtr infoCompressed = Marshal.AllocHGlobal(sizeCreatePlayerData);
            Marshal.StructureToPtr(infoPlayer, infoCompressed, false);
            Data dataInfoPlayer = new Data
            {
                typeData = TypeInfo.PlayerInfo,
                data = infoCompressed
            };
    

    This creates an unmanaged pointer to a data structure and then adds that pointer(!) to the transmission object. That pointer has no meaning on the other side. It's interesting you "only" get a NullReferenceException, as this could give you errors that are much worse. Side note: You're leaking the unmanaged memory.

    Why don't you just use a standard serialization, using Json or XML? Your data structures aren't that complicated.