mfavant / tubekit

NEW PROJECT https://github.com/crust-hub/avant
MIT License
0 stars 0 forks source link

feat: Maybe , C++ 20 Coroutine Simple Actor, One Thread #34

Closed gaowanlu closed 2 months ago

gaowanlu commented 3 months ago

demo in mind

#pragma once

#include <list>
#include <string>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <thread> // using coroutine

class Player
{
public:
    uint64_t gid;
    uint64_t roleid;
    std::list<std::string> sendBox;
};

class Game
{
public:
    Game();
    void OnInit();
    void OnTick();
    Player *AddPlayer(uint64_t gid, uint64_t roleid);
    bool Game::RemovePlayer(uint64_t gid, uint64_t roleid);

    std::list<Player *> playerList;
    std::unordered_map<uint64_t, Player *> gid2Player;
    std::unordered_map<uint64_t, Player *> roleid2Player;
    std::unordered_set<uint64_t> waitSendRoleid;

    std::mutex recvBoxWriteLock;
    std::list<std::string> *recvBox{nullptr};
    std::list<std::string> recvBox1;
    std::list<std::string> recvBox2;

    bool isStoped{true};
    bool toStop{false};
    std::thread mainLoop;
};
#include "game.h"
#include <chrono>

// 可以采用 C++20 协程来写 

Game::Game()
{
    recvBoxWriteLock.lock();
    recvBox = &recvBox1;
    recvBoxWriteLock.unlock();
}

void Game::OnInit()
{
    if (isStoped == false)
    {
        return;
    }
    mainLoop = std::thread(
        [this]()
        {
            isStoped = false;
            while (toStop == false)
            {
                recvBoxWriteLock.lock();
                recvBox = recvBox == &recvBox1 ? &recvBox2 : &recvBox1;
                recvBoxWriteLock.unlock();
                // process recvBox
                recvBox->clear();

                // process player sendBox
                for (auto roleid : waitSendRoleid)
                {
                    if (roleid2Player.find(roleid) != roleid2Player.end())
                    {
                        while (!roleid2Player[roleid]->sendBox.empty())
                        {
                            // to send
                            bool sendRes = true;

                            if (sendRes)
                            {
                                roleid2Player[roleid]->sendBox.pop_front();
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }

                OnTick();

                std::this_thread::sleep_for(std::chrono::milliseconds(5));
            }
            this->isStoped = true;
        });
    mainLoop.detach();
}

void Game::OnTick()
{
}

Player *Game::AddPlayer(uint64_t gid, uint64_t roleid)
{
    if (gid2Player.find(gid) != gid2Player.end())
    {
        return nullptr;
    }
    if (roleid2Player.find(roleid) != roleid2Player.end())
    {
        return nullptr;
    }
    Player &newPlayer = *(new Player());
    playerList.push_back(&newPlayer);
    newPlayer.gid = gid;
    newPlayer.roleid = roleid;
    gid2Player[gid] = &newPlayer;
    roleid2Player[roleid] = &newPlayer;
}

bool Game::RemovePlayer(uint64_t gid, uint64_t roleid)
{
    if (gid2Player.find(gid) == gid2Player.end())
    {
        return false;
    }
    if (roleid2Player.find(roleid) == roleid2Player.end())
    {
        return false;
    }
    Player *player = gid2Player[gid];
    gid2Player.erase(gid);
    roleid2Player.erase(roleid);
}
gaowanlu commented 3 months ago

https://www.cnblogs.com/RioTian/p/17755013.html

gaowanlu commented 3 months ago

https://www.bennyhuo.com/book/cpp-coroutines/02-generator.html

gaowanlu commented 3 months ago

每个Player都是一个Entity 网关处有Player 游戏服也有Player 游戏服的每个Entity下会挂载许多协程 网关与游戏服之间用TCP IPC轮子通信 也可以有很多选择 UDP、KCP其实都不在话下

对于游戏服不断的会有从网关发来的包 有包则推给某个协程 然后对协程resume 然后协程内部自己消化 发送包到网关可能会发不出去的情况 这种直接拉链 能发生时再发 只用epoll监听一个IPC套接字就行了,围绕单线程事件循环 不断的触发协程 创建协程 销毁协程 而且游戏服可以与多个进程去IPC,单线程对于写业务代码很方便,每次只围绕一个消息进行处理

对于网关也有个Player(sockfd,conn,gid,roleid) 有一个线程单独进行与游戏服IPC worker会将收到的上行包推给IPC线程,让IPC线程发给游戏服 同时需要接收游戏服发来的包 游戏服发来的包直接向connection send可能失败,因为buffer会满 所以直接将包拉链到 网关的Player去,当可以发送时worker会process_conn 这时process_conn尝试从Player待发送队列中进行消息发送 还有一点就是怎么维护Player的队列的线程安全,加锁就导致IPC与worker水火不容了 所以可以直接向worker添加task,让conn对应的worker 将包拉链到对应Player或者直接send到conn 这样IPC线程几乎时完全非阻塞 保证了线程安全又不会出现激烈抢锁效率低下的情况

其他进程之间几乎只用进行IPC了,每个进程都是围绕事件循环 单线程 协程进行业务处理