File Game2DLayerNetwork.c

File List > engine > src > gameframework > layers > Game2D > Game2DLayerNetwork.c

Go to the documentation of this file

#include <string.h>
#include "Game2DLayerNetwork.h"
#include "Log.h"
#include "Network.h"
#include "BinarySerializer.h"
#include "Network.h"
#include "AssertLib.h"
#include "DynArray.h"
#include "Entities.h"
#include "Game2DLayer.h"
#include "NetworkID.h"
#include "GameFramework.h"
#include "Entities.h"
#include <stdbool.h>


struct GameThreadClientConnection
{
    bool connected;
};

static struct GameThreadClientConnection gClientsConnections[3] = { {.connected=false}, {.connected=false}, {.connected=false} };
static struct NetworkQueueItem gDequeueBuffer[128];
static PacketExtensionNoArgsFn gExtendRequestLvlData = NULL;
static VECTOR(struct UserGame2DRPC) gUserRPCs = NULL;


void G2D_RegisterUserRPC(struct UserGame2DRPC* rpc)
{
    if(!gUserRPCs)
    {
        gUserRPCs = NEW_VECTOR(struct UserGame2DRPC);
    }
    //EASSERT(rpc->rpcType - G2DRPC_LastEngineRPC == VectorSize(gUserRPCs)); /* You need to register the RPCs in the right order, the order they appear in your games enum */
    gUserRPCs = VectorPush(gUserRPCs, rpc);
}

static struct NetworkQueueItem* GetLatestStateUpdate(VECTOR(struct NetworkQueueItem) dequeuedStateUpdates)
{
    struct NetworkQueueItem* latest = NULL;
    if(VectorSize(dequeuedStateUpdates))
    {
        latest = &dequeuedStateUpdates[0];
        /* we're only interested in the latest one.  */
        for(int i=0; i<VectorSize(dequeuedStateUpdates); i++)
        {
            if(dequeuedStateUpdates[i].sequenceNumber > latest->sequenceNumber)
            {
                latest = &dequeuedStateUpdates[i];
            }
        }
        /* free the rest. */
        for(int i=0; i<VectorSize(dequeuedStateUpdates); i++)
        {
            if(&dequeuedStateUpdates[i] != latest)
            {
                free(dequeuedStateUpdates[i].pData);
            }
        }   
    }
    return latest;
}

struct StateUpdateItr_Ctx
{
    struct GameLayer2DData* pData;
    struct BinarySerializer* bs;
};

static bool StateUpdateItr(struct Entity2D* pEnt, int i, void* pUser)
{
    struct StateUpdateItr_Ctx* ctx = pUser;
    if(pEnt->bSerializeInNetworkUpdate)
    {
        Et2D_SerializeEntities(&ctx->pData->entities, ctx->bs, ctx->pData, pEnt->inDrawLayer);
    }
}

static void ApplyStateUpdate(struct GameLayer2DData* pData, struct BinarySerializer* bs)
{
    Et2D_SerializeEntities(&pData->entities, bs, pData, -1);
}

void G2D_PollNetworkQueueServer(struct GameFrameworkLayer* pLayer, float deltaT)
{
    struct GameLayer2DData* pData = pLayer->userData;

    struct NetworkConnectionEvent nce;
    while(NW_DequeueConnectionEvent(&nce))
    {
        switch(nce.type)
        {
        case NCE_ClientConnected:
            Log_Info("Client %i connected", nce.client);
            gClientsConnections[nce.client].connected = true;
            break;
        case NCE_ClientDisconnected:
            Log_Info("Client %i disconnected", nce.client);
            gClientsConnections[nce.client].connected = false;
            break;
        }
    }

    static VECTOR(struct NetworkQueueItem) dequeuedStateUpdates = NULL;
    if(!dequeuedStateUpdates)
    {
        dequeuedStateUpdates = NEW_VECTOR(struct NetworkQueueItem);
        dequeuedStateUpdates = VectorResize(dequeuedStateUpdates, 4);
    }

    struct NetworkQueueItem nqi;
    while(NW_DequeueData(&nqi))
    {
        u8* pBody = NULL;
        int headerSize = 0;
        bool bFreePacket = true;
        enum G2DPacketType type = G2D_ParsePacket(nqi.pData, &pBody, &headerSize);
        switch(type)
        {
        case G2DPacket_RequestLevelData:
            {
                Log_Info("recieved level data request from client %i,", nqi.client);
                /* handle request */
                if(pData->levelDataRequestHandlerExtender)
                {
                    struct BinarySerializer bs;
                    BS_CreateForLoadFromBuffer(pBody, nqi.pDataSize - headerSize, &bs);
                    pData->levelDataRequestHandlerExtender(pData, &bs);
                    BS_Finish(&bs); /* does nothing if BS_CreateForLoadFromBuffer but added anyway */
                }
                Log_Info("sending level data response to client %i,", nqi.client);
                /* write response */
                struct BinarySerializer bs;
                BS_CreateForSaveToNetwork(&bs, nqi.client);
                BS_SerializeU32(G2DPacket_LevelDataResponseData, &bs);

                if(pData->levelDataPacketExtender)
                {
                    pData->levelDataPacketExtender(pData, &bs);
                }

                G2D_SaveLevelDataInternal(pData, &bs);
                BS_Finish(&bs);
            }
            break;
        case G2DPacket_LevelDataResponseData:
            EASSERT(false);
            break;
        case G2DPacket_RPC:
            G2D_DoRPC(pLayer, pLayer->userData, pBody, nqi.client);
            break;
        case G2DPacket_WorldState:
            dequeuedStateUpdates = VectorPush(dequeuedStateUpdates, &nqi);
            bFreePacket = false;
            break;
        default:
            Log_Error("Game2DLayer server recieved unknown packet type (%i)");
            break;
        }
        if(bFreePacket)
        {
            free(nqi.pData);
        }
    }
    struct NetworkQueueItem* pNqi = GetLatestStateUpdate(dequeuedStateUpdates);
    if(pNqi)
    {
        /* apply the latest state update */
        u8* pBody = NULL;
        int headerSize = 0;
        enum G2DPacketType type = G2D_ParsePacket(nqi.pData, &pBody, &headerSize);
        EASSERT(type == G2DPacket_WorldState);
        struct BinarySerializer bs;
        BS_CreateForLoadFromBuffer(pBody, pNqi->pDataSize - headerSize, &bs);
        bs.ctx = SCTX_ToNetworkUpdate;
        ApplyStateUpdate(pData, &bs);
        BS_Finish(&bs); /* a pointless no-op */
        free(pNqi->pData);
        dequeuedStateUpdates = VectorClear(dequeuedStateUpdates);
    }

}

void G2D_PollNetworkQueueClient(struct GameFrameworkLayer* pLayer, float deltaT)
{
    struct GameLayer2DData* pData = pLayer->userData;

    struct NetworkConnectionEvent nce;

    static VECTOR(struct NetworkQueueItem) dequeuedStateUpdates = NULL;
    if(!dequeuedStateUpdates)
    {
        dequeuedStateUpdates = NEW_VECTOR(struct NetworkQueueItem);
        dequeuedStateUpdates = VectorResize(dequeuedStateUpdates, 4);
    }

    while(NW_DequeueConnectionEvent(&nce))
    {
        switch(nce.type)
        {
        case NCE_ClientConnected:
            Log_Info("Client %i connected", nce.client);
            break;
        case NCE_ClientDisconnected:
            Log_Info("Client %i disconnected", nce.client);
            break;
        }
    }

    struct NetworkQueueItem nqi;
    while(NW_DequeueData(&nqi))
    {
        u8* pBody = NULL;
        int headerSize = 0;
        bool bFreePacket = true;
        enum G2DPacketType type = G2D_ParsePacket(nqi.pData, &pBody, &headerSize);
        switch(type)
        {
        case G2DPacket_RequestLevelData:
            EASSERT(false);
            break;
        case G2DPacket_LevelDataResponseData:
            EASSERT(false);
            break;
        case G2DPacket_RPC:
            G2D_DoRPC(pLayer, pLayer->userData, pBody, nqi.client);
            break;
        case G2DPacket_WorldState:
            dequeuedStateUpdates = VectorPush(dequeuedStateUpdates, &nqi);
            bFreePacket = false;
            break;
        }
        if(bFreePacket)
        {
            free(nqi.pData);
        }
    }
    struct NetworkQueueItem* pNqi = GetLatestStateUpdate(dequeuedStateUpdates);
    if(pNqi)
    {
        /* apply the latest state update */
        u8* pBody = NULL;
        int headerSize = 0;
        enum G2DPacketType type = G2D_ParsePacket(nqi.pData, &pBody, &headerSize);
        EASSERT(type == G2DPacket_WorldState);
        struct BinarySerializer bs;
        BS_CreateForLoadFromBuffer(pBody, pNqi->pDataSize - headerSize, &bs);
        bs.ctx = SCTX_ToNetworkUpdate;
        ApplyStateUpdate(pData, &bs);
        BS_Finish(&bs); /* a pointless no-op */
        free(pNqi->pData);
        dequeuedStateUpdates = VectorClear(dequeuedStateUpdates);
    }
}

void G2D_Extend_RequestLevelDataMessage(PacketExtensionNoArgsFn fn)
{
    gExtendRequestLvlData = fn;
}

void G2D_Enqueue_RequestLevelData()
{
    EASSERT(NW_GetRole() == GR_Client);
    struct BinarySerializer bs;
    BS_CreateForSaveToNetwork(&bs, -1);
    BS_SerializeU32(G2DPacket_RequestLevelData, &bs);
    if(gExtendRequestLvlData)
    {
        gExtendRequestLvlData(&bs);
    }
    BS_Finish(&bs);
}

enum G2DPacketType G2D_ParsePacket(u8* pPacket, u8** pOutBody, int* outHeaderSize)
{
    *pOutBody = pPacket + sizeof(enum G2DPacketType);
    *outHeaderSize = sizeof(enum G2DPacketType);
    return *((enum G2DPacketType*)pPacket);
}

struct Game2DLayerWorldstatePacketHeader
{
    u16 numEntities;
    u16 entityLayer; /* assumes all entities on one layer */
};


void G2D_Enqueue_Worldstate_Packet(struct GameLayer2DData* pData, int clientI)
{
    struct BinarySerializer bs;
    BS_CreateForSaveToNetwork(&bs, clientI);
    bs.ctx = SCTX_ToNetworkUpdate;
    BS_SerializeI32(G2DPacket_WorldState, &bs);
    Et2D_SerializeEntities(&pData->entities, &bs, pData, -1);
    BS_Finish(&bs);
}

enum Game2DRPCType G2D_ParseRPCPacket(u8* packet, u8** pOutBody)
{
    enum Game2DRPCType type = *((enum Game2DRPCType*)packet);
    *pOutBody = packet + sizeof(enum Game2DRPCType);
    return type;
}

struct IsNetIdTakenIteratorContext
{
    bool bReturnVal;
    int proposedNetId;
};

bool IsNetIDTakenIterator(struct Entity2D* pEnt, int i, void* pUser)
{
    struct IsNetIdTakenIteratorContext* pCtx = pUser;
    pCtx->bReturnVal = false;
    if(pEnt->networkID == pCtx->proposedNetId)
    {
        pCtx->bReturnVal = true;
        return false;
    }
    return true;
}

static bool G2D_IsNetIDTaken(int netID, struct Entity2DCollection* pCollection)
{
    struct IsNetIdTakenIteratorContext ctx = 
    {
        .bReturnVal = false,
        .proposedNetId = netID
    };
    Et2D_IterateEntities(pCollection, &IsNetIDTakenIterator, &ctx);
    return ctx.bReturnVal;
}

struct FindWithNetIdIteratorContext
{
    int netID;
    struct Entity2D* pOutEnt;
};

static bool FindWithNetIDIterator(struct Entity2D* pEnt, int i, void* pUser)
{
    struct FindWithNetIdIteratorContext* pCTX = pUser;
    if(pEnt->networkID == pCTX->netID)
    {
        pCTX->pOutEnt = pEnt;
        return false;
    }
    return true;
}

struct Entity2D* G2D_FindEntityWithNetID(struct Entity2DCollection* pCollection, int netID)
{
    struct FindWithNetIdIteratorContext ctx = 
    {
        .netID = netID,
        .pOutEnt = NULL
    };
    Et2D_IterateEntities(pCollection, FindWithNetIDIterator, &ctx);
    return ctx.pOutEnt;
}

void G2D_SendRPC(int client, enum Game2DRPCType type, void* pRPCData)
{
    struct BinarySerializer bs;
    BS_CreateForSaveToNetwork(&bs, client);
    BS_SerializeU32(G2DPacket_RPC, &bs);
    BS_SerializeU32(type, &bs);
    switch(type)
    {
    case G2DRPC_CreateEntity:
        {
            struct CreateEntity_RPC* pData = pRPCData;
            BS_SerializeU32(1, &bs); /* version */
            BS_SerializeI32(pData->pEnt->inDrawLayer, &bs);
            Et2D_SerializeEntityV1Base(pData->pEnt, &bs, pData->pData);
        }
        break;
    case G2DRPC_AdjustNetworkID:
        {
            struct AdjustNetID_RPC* pData = pRPCData;
            BS_SerializeI32(pData->oldNetID, &bs);
            BS_SerializeI32(pData->newNetID, &bs);
        }
        break;
    case G2DRPC_DestroyEntity:
        {
            struct DeleteEntity_RPC* pData = pRPCData;
            BS_SerializeI32(pData->netID, &bs);
        }
        break;
    default:
        if(gUserRPCs)
        {
            if(type - G2DRPC_LastEngineRPC < VectorSize(gUserRPCs))
            {
                gUserRPCs[type].ctor(&bs, pRPCData);
            }
            else
            {
                Log_Error("G2D_SendRPC unknown type out of range of user RPCs: %i", type);
            }
        }
        else
        {
            Log_Error("G2D_SendRPC (no user rpcs registered) unknown type out of range of user RPCs: %i", type);
        }
        break;
    }
    BS_Finish(&bs);
}

void G2D_DoRPC(struct GameFrameworkLayer* pLayer, struct GameLayer2DData* pData, u8* pRPCData, int client)
{
    u8* rpcPacketBody = NULL;
    enum Game2DRPCType type = G2D_ParseRPCPacket(pRPCData, &rpcPacketBody);
    struct BinarySerializer bs;
    BS_CreateForLoadFromBuffer(rpcPacketBody, 
        100000000, /* just an arbitrary large size: TODO: use actual size */ 
        &bs
    );
    switch(type)
    {
    case G2DRPC_CreateEntity:
        {
            Log_Info("Create entity RPC");
            u32 version = 0;
            BS_DeSerializeU32(&version, &bs);
            switch (version)
            {
            case 1:
                {
                    struct Entity2D ent;
                    int layer = 0;
                    memset(&ent, 0, sizeof(struct Entity2D));
                    BS_DeSerializeI32(&layer, &bs);
                    Et2D_DeserializeEntityV1Base(&pData->entities, &bs, pData, layer, &ent);
                    if(client >= 0)
                    {
                        EASSERT(NW_GetRole() == GR_ClientServer);
                        /* check if net ID is taken */
                        if(G2D_IsNetIDTaken(ent.networkID, &pData->entities))
                        {
                            int oldNetID = ent.networkID;
                            ent.networkID = NetID_GetID();
                            /* send an rpc back to client here to make it change its net ID to match this one */
                            struct AdjustNetID_RPC data = 
                            {
                                .newNetID = ent.networkID,
                                .oldNetID = oldNetID
                            };
                            Log_Info("Create entity: Adjusting net ID from %i to %i", data.oldNetID, data.newNetID);
                            G2D_SendRPC(client, G2DRPC_AdjustNetworkID, &data);
                        }
                        /* relay rpc to other clients */
                        for(int i=0; i<3; i++)
                        {
                            if(i != client && gClientsConnections[i].connected)
                            {
                                /* send RPC here */
                                struct CreateEntity_RPC rpc = 
                                {
                                    .pData = pData,
                                    .pEnt = &ent
                                };
                                G2D_SendRPC(i, G2DRPC_CreateEntity, &rpc);
                            }
                        } 
                    }

                    /* TODO: perhaps init-ing entities should take place at a given point in the loop */
                    HEntity2D hEnt = Et2D_AddEntityNoNewNetID(&pData->entities, &ent);
                    struct Entity2D* pEnt = Et2D_GetEntity(&pData->entities, hEnt);
                    Log_Info("%p new entity from rpc. net ID: %i. entity ID: %i", pEnt, pEnt->networkID, pEnt->thisEntity);
                    pEnt->init(pEnt, pLayer, pData->pDrawContext, NULL);
                }
                break;
            default:
                Log_Error("Create entity RPC, unknown packet schema version");
                break;
            }
        }
        break;
    case G2DRPC_AdjustNetworkID:
        {
            EASSERT(NW_GetRole() == GR_Client);
            i32 oldEntID = -1;
            i32 newEntID = -1;
            BS_DeSerializeI32(&oldEntID, &bs);
            BS_DeSerializeI32(&newEntID, &bs);
            NetID_DeserializedNewID(newEntID);

            if(G2D_IsNetIDTaken(newEntID, &pData->entities))
            {
                Log_Error("NEW net ID %i TAKEN!", newEntID);
            }
            Log_Info("Adjusting net id from %i to %i", oldEntID, newEntID);
            struct Entity2D* pEnt = G2D_FindEntityWithNetID(&pData->entities, oldEntID);
            if(pEnt)
            {
                pEnt->networkID = newEntID;
            }
            else
            {
                Log_Error("G2DRPC_AdjustNetworkID - no entity could be found with network id: %i", oldEntID);
            }
        }
        break;
    case G2DRPC_DestroyEntity:
        {
            i32 ent2DestroyNetID = -1;
            BS_DeSerializeI32(&ent2DestroyNetID, &bs);
            struct Entity2D* pEnt = G2D_FindEntityWithNetID(&pData->entities, ent2DestroyNetID);
            if(pEnt)
            {
                Et2D_DestroyEntity(pData->pLayer, &pData->entities, pEnt->thisEntity);
            }
            else
            {
                Log_Error("G2DRPC_DestroyEntity - no entity could be found with network id: %i", ent2DestroyNetID);
            }
        }
        break;
    default:
        {
            if(gUserRPCs)
            {
                if(type - G2DRPC_LastEngineRPC < VectorSize(gUserRPCs))
                {
                    gUserRPCs[type].handler(&bs, pData, client);
                }
                else
                {
                    Log_Error("G2D_DoRPC unknown type out of range of user RPCs: %i", type);
                }
            }
            else
            {
                Log_Error("G2D_DoRPC (no user rpcs registered) unknown type out of range of user RPCs: %i", type);
            }
        }
        break;
    }
    BS_Finish(&bs); /* should do nothing */
}

bool G2D_IsClientConnected(int i)
{
    return gClientsConnections[i].connected;
}