1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-06 12:20:30 +00:00

[MP] Add MultiplayerPeer disconnect_peer, close.

Update ENet, WebRTC, and WebSocket to support peer disconnection and
unify the close function.
This commit is contained in:
Fabio Alessandrelli
2022-10-22 17:49:40 +02:00
parent 028db9f2b5
commit 39f3d9d59e
13 changed files with 188 additions and 201 deletions

View File

@@ -146,92 +146,24 @@ void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event
incoming_packets.push_back(packet);
}
bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
switch (p_type) {
case ENetConnection::EVENT_CONNECT: {
if (is_refusing_new_connections()) {
p_event.peer->reset();
return false;
}
// Client joined with invalid ID, probably trying to exploit us.
if (p_event.data < 2 || peers.has((int)p_event.data)) {
p_event.peer->reset();
return false;
}
int id = p_event.data;
p_event.peer->set_meta(SNAME("_net_id"), id);
peers[id] = p_event.peer;
emit_signal(SNAME("peer_connected"), id);
return false;
void ENetMultiplayerPeer::_disconnect_inactive_peers() {
HashSet<int> to_drop;
for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.value->is_active()) {
continue;
}
case ENetConnection::EVENT_DISCONNECT: {
int id = p_event.peer->get_meta(SNAME("_net_id"));
if (!peers.has(id)) {
// Never fully connected.
return false;
}
emit_signal(SNAME("peer_disconnected"), id);
peers.erase(id);
return false;
}
case ENetConnection::EVENT_RECEIVE: {
int32_t source = p_event.peer->get_meta(SNAME("_net_id"));
_store_packet(source, p_event);
return false;
}
default:
return true;
to_drop.insert(E.key);
}
}
bool ENetMultiplayerPeer::_parse_client_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
switch (p_type) {
case ENetConnection::EVENT_CONNECT: {
connection_status = CONNECTION_CONNECTED;
emit_signal(SNAME("peer_connected"), 1);
emit_signal(SNAME("connection_succeeded"));
return false;
for (const int &P : to_drop) {
peers.erase(P);
if (hosts.has(P)) {
hosts.erase(P);
}
case ENetConnection::EVENT_DISCONNECT: {
if (connection_status == CONNECTION_CONNECTED) {
// Client just disconnected from server.
emit_signal(SNAME("server_disconnected"));
} else {
emit_signal(SNAME("connection_failed"));
}
close_connection();
return true;
if (active_mode == MODE_CLIENT) {
ERR_CONTINUE(P != TARGET_PEER_SERVER);
emit_signal(SNAME("server_disconnected"));
}
case ENetConnection::EVENT_RECEIVE: {
_store_packet(1, p_event);
return false;
}
default:
return true;
}
}
bool ENetMultiplayerPeer::_parse_mesh_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event, int p_peer_id) {
switch (p_type) {
case ENetConnection::EVENT_CONNECT:
p_event.peer->reset();
return false;
case ENetConnection::EVENT_DISCONNECT:
if (peers.has(p_peer_id)) {
emit_signal(SNAME("peer_disconnected"), p_peer_id);
peers.erase(p_peer_id);
}
hosts.erase(p_peer_id);
return true;
case ENetConnection::EVENT_RECEIVE: {
_store_packet(p_peer_id, p_event);
return false;
} break;
default:
// Nothing to do
return true;
emit_signal(SNAME("peer_disconnected"), P);
}
}
@@ -240,74 +172,96 @@ void ENetMultiplayerPeer::poll() {
_pop_current_packet();
_disconnect_inactive_peers();
switch (active_mode) {
case MODE_CLIENT: {
if (peers.has(1) && !peers[1]->is_active()) {
if (connection_status == CONNECTION_CONNECTED) {
// Client just disconnected from server.
emit_signal(SNAME("server_disconnected"));
} else {
emit_signal(SNAME("connection_failed"));
}
close_connection();
if (!peers.has(1)) {
close();
return;
}
ENetConnection::Event event;
ENetConnection::EventType ret = hosts[0]->service(0, event);
if (ret == ENetConnection::EVENT_ERROR) {
return;
}
do {
if (_parse_client_event(ret, event)) {
return;
if (ret == ENetConnection::EVENT_CONNECT) {
connection_status = CONNECTION_CONNECTED;
emit_signal(SNAME("peer_connected"), 1);
emit_signal(SNAME("connection_succeeded"));
} else if (ret == ENetConnection::EVENT_DISCONNECT) {
if (connection_status == CONNECTION_CONNECTED) {
// Client just disconnected from server.
emit_signal(SNAME("server_disconnected"));
emit_signal(SNAME("peer_disconnected"), 1);
} else {
emit_signal(SNAME("connection_failed"));
}
close();
} else if (ret == ENetConnection::EVENT_RECEIVE) {
_store_packet(1, event);
} else if (ret != ENetConnection::EVENT_NONE) {
close(); // Error.
}
} while (hosts[0]->check_events(ret, event) > 0);
} while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
} break;
case MODE_SERVER: {
for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (!(E.value->is_active())) {
emit_signal(SNAME("peer_disconnected"), E.value->get_meta(SNAME("_net_id")));
peers.erase(E.key);
}
}
ENetConnection::Event event;
ENetConnection::EventType ret = hosts[0]->service(0, event);
if (ret == ENetConnection::EVENT_ERROR) {
return;
}
do {
if (_parse_server_event(ret, event)) {
return;
if (ret == ENetConnection::EVENT_CONNECT) {
if (is_refusing_new_connections()) {
event.peer->reset();
continue;
}
// Client joined with invalid ID, probably trying to exploit us.
if (event.data < 2 || peers.has((int)event.data)) {
event.peer->reset();
continue;
}
int id = event.data;
event.peer->set_meta(SNAME("_net_id"), id);
peers[id] = event.peer;
emit_signal(SNAME("peer_connected"), id);
} else if (ret == ENetConnection::EVENT_DISCONNECT) {
int id = event.peer->get_meta(SNAME("_net_id"));
if (!peers.has(id)) {
// Never fully connected.
continue;
}
emit_signal(SNAME("peer_disconnected"), id);
peers.erase(id);
} else if (ret == ENetConnection::EVENT_RECEIVE) {
int32_t source = event.peer->get_meta(SNAME("_net_id"));
_store_packet(source, event);
} else if (ret != ENetConnection::EVENT_NONE) {
close(); // Error
}
} while (hosts[0]->check_events(ret, event) > 0);
} while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
} break;
case MODE_MESH: {
for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (!(E.value->is_active())) {
emit_signal(SNAME("peer_disconnected"), E.key);
peers.erase(E.key);
if (hosts.has(E.key)) {
hosts.erase(E.key);
}
}
}
HashSet<int> to_drop;
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
ENetConnection::Event event;
ENetConnection::EventType ret = E.value->service(0, event);
if (ret == ENetConnection::EVENT_ERROR) {
if (peers.has(E.key)) {
emit_signal(SNAME("peer_disconnected"), E.key);
peers.erase(E.key);
}
hosts.erase(E.key);
continue;
}
do {
if (_parse_mesh_event(ret, event, E.key)) {
if (ret == ENetConnection::EVENT_CONNECT) {
event.peer->reset();
} else if (ret == ENetConnection::EVENT_RECEIVE) {
_store_packet(E.key, event);
} else if (ret == ENetConnection::EVENT_NONE) {
break; // Keep polling the others.
} else {
to_drop.insert(E.key); // Error or disconnect.
break; // Keep polling the others.
}
} while (E.value->check_events(ret, event) > 0);
}
for (const int &P : to_drop) {
if (peers.has(P)) {
emit_signal(SNAME("peer_disconnected"), P);
peers.erase(P);
}
hosts.erase(P);
}
} break;
default:
return;
@@ -322,29 +276,41 @@ bool ENetMultiplayerPeer::is_server_relay_supported() const {
return active_mode == MODE_SERVER || active_mode == MODE_CLIENT;
}
void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) {
void ENetMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) {
ERR_FAIL_COND(!_is_active() || !peers.has(p_peer));
peers[p_peer]->peer_disconnect(0); // Will be removed during next poll.
if (active_mode == MODE_CLIENT || active_mode == MODE_SERVER) {
hosts[0]->flush();
} else {
ERR_FAIL_COND(!hosts.has(p_peer));
hosts[p_peer]->flush();
}
if (p_force) {
peers.erase(p_peer);
if (hosts.has(p_peer)) {
hosts.erase(p_peer);
}
if (active_mode == MODE_CLIENT) {
hosts.clear(); // Avoid flushing again.
close();
}
}
}
void ENetMultiplayerPeer::close() {
if (!_is_active()) {
return;
}
_pop_current_packet();
bool peers_disconnected = false;
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.value.is_valid() && E.value->get_state() == ENetPacketPeer::STATE_CONNECTED) {
E.value->peer_disconnect_now(unique_id);
peers_disconnected = true;
E.value->peer_disconnect_now(0);
}
}
if (peers_disconnected) {
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
E.value->flush();
}
if (wait_usec > 0) {
OS::get_singleton()->delay_usec(wait_usec); // Wait for disconnection packets to send
}
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
E.value->flush();
}
active_mode = MODE_NONE;
@@ -516,7 +482,6 @@ void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("create_mesh", "unique_id"), &ENetMultiplayerPeer::create_mesh);
ClassDB::bind_method(D_METHOD("add_mesh_peer", "peer_id", "host"), &ENetMultiplayerPeer::add_mesh_peer);
ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &ENetMultiplayerPeer::close_connection, DEFVAL(100));
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip);
ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host);
@@ -531,7 +496,7 @@ ENetMultiplayerPeer::ENetMultiplayerPeer() {
ENetMultiplayerPeer::~ENetMultiplayerPeer() {
if (_is_active()) {
close_connection();
close();
}
}