summaryrefslogtreecommitdiffstats
path: root/src/network/room_member.cpp
blob: e1a0dfdabf54c8c5307672377d1b2f73d199e09e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <atomic>
#include <mutex>
#include <thread>
#include "common/assert.h"
#include "enet/enet.h"
#include "network/room_member.h"

namespace Network {

constexpr u32 ConnectionTimeoutMs = 5000;

class RoomMember::RoomMemberImpl {
public:
    ENetHost* client = nullptr; ///< ENet network interface.
    ENetPeer* server = nullptr; ///< The server peer the client is connected to

    std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
    void SetState(const State new_state);
    bool IsConnected() const;

    std::string nickname; ///< The nickname of this member.

    std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
    /// Thread that receives and dispatches network packets
    std::unique_ptr<std::thread> receive_thread;
    void ReceiveLoop();
    void StartLoop();
};

// RoomMemberImpl
void RoomMember::RoomMemberImpl::SetState(const State new_state) {
    state = new_state;
    // TODO(B3N30): Invoke the callback functions
}

bool RoomMember::RoomMemberImpl::IsConnected() const {
    return state == State::Joining || state == State::Joined;
}

void RoomMember::RoomMemberImpl::ReceiveLoop() {
    // Receive packets while the connection is open
    while (IsConnected()) {
        std::lock_guard<std::mutex> lock(network_mutex);
        ENetEvent event;
        if (enet_host_service(client, &event, 1000) > 0) {
            if (event.type == ENET_EVENT_TYPE_RECEIVE) {
                switch (event.packet->data[0]) {
                // TODO(B3N30): Handle the other message types
                case IdNameCollision:
                    SetState(State::NameCollision);
                    enet_packet_destroy(event.packet);
                    enet_peer_disconnect(server, 0);
                    enet_peer_reset(server);
                    return;
                    break;
                case IdMacCollision:
                    SetState(State::MacCollision);
                    enet_packet_destroy(event.packet);
                    enet_peer_disconnect(server, 0);
                    enet_peer_reset(server);
                    return;
                    break;
                default:
                    break;
                }
                enet_packet_destroy(event.packet);
            }
        }
    }
};

void RoomMember::RoomMemberImpl::StartLoop() {
    receive_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::ReceiveLoop, this);
}

// RoomMember
RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {
    room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
    ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
}

RoomMember::~RoomMember() {
    ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
    enet_host_destroy(room_member_impl->client);
}

RoomMember::State RoomMember::GetState() const {
    return room_member_impl->state;
}

void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
                      u16 client_port) {
    ENetAddress address{};
    enet_address_set_host(&address, server_addr);
    address.port = server_port;

    room_member_impl->server =
        enet_host_connect(room_member_impl->client, &address, NumChannels, 0);

    if (!room_member_impl->server) {
        room_member_impl->SetState(State::Error);
        return;
    }

    ENetEvent event{};
    int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
    if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
        room_member_impl->nickname = nick;
        room_member_impl->SetState(State::Joining);
        room_member_impl->StartLoop();
        // TODO(B3N30): Send a join request with the nickname to the server
    } else {
        room_member_impl->SetState(State::CouldNotConnect);
    }
}

bool RoomMember::IsConnected() const {
    return room_member_impl->IsConnected();
}

void RoomMember::Leave() {
    ASSERT_MSG(room_member_impl->receive_thread != nullptr, "Must be in a room to leave it.");
    {
        std::lock_guard<std::mutex> lock(room_member_impl->network_mutex);
        enet_peer_disconnect(room_member_impl->server, 0);
        room_member_impl->SetState(State::Idle);
    }
    room_member_impl->receive_thread->join();
    room_member_impl->receive_thread.reset();
    enet_peer_reset(room_member_impl->server);
}

} // namespace Network