Merge pull request #71534 from Faless/mp/4.x_nested_spawn

[MP] Fix nested spawning during "ready".
This commit is contained in:
Rémi Verschelde 2023-01-17 10:38:11 +01:00 committed by GitHub
commit 8a00992ab6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 17 deletions

View file

@ -199,10 +199,6 @@ void MultiplayerSpawner::_notification(int p_what) {
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E.key));
ERR_CONTINUE(!node);
node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit));
// This is unlikely, but might still crash the engine.
if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) {
node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready));
}
get_multiplayer()->object_configuration_remove(node, this);
}
tracked_nodes.clear();
@ -244,11 +240,11 @@ void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_s
if (!tracked_nodes.has(oid)) {
tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id);
p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT);
p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT);
_spawn_notify(p_node->get_instance_id());
}
}
void MultiplayerSpawner::_node_ready(ObjectID p_id) {
void MultiplayerSpawner::_spawn_notify(ObjectID p_id) {
get_multiplayer()->object_configuration_add(ObjectDB::get_instance(p_id), this);
}

View file

@ -77,7 +77,7 @@ private:
void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID);
void _node_added(Node *p_node);
void _node_exit(ObjectID p_id);
void _node_ready(ObjectID p_id);
void _spawn_notify(ObjectID p_id);
Vector<String> _get_spawnable_scenes() const;
void _set_spawnable_scenes(const Vector<String> &p_scenes);

View file

@ -125,6 +125,20 @@ void SceneReplicationInterface::on_reset() {
}
void SceneReplicationInterface::on_network_process() {
// Prevent endless stalling in case of unforseen spawn errors.
if (spawn_queue.size()) {
ERR_PRINT("An error happened during last spawn, this usually means the 'ready' signal was not emitted by the spawned node.");
for (const ObjectID &oid : spawn_queue) {
Node *node = get_id_as<Node>(oid);
ERR_CONTINUE(!node);
if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready))) {
node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready));
}
}
spawn_queue.clear();
}
// Process timed syncs.
uint64_t msec = OS::get_singleton()->get_ticks_msec();
for (KeyValue<int, PeerInfo> &E : peers_info) {
const HashSet<ObjectID> to_sync = E.value.sync_nodes;
@ -144,19 +158,41 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
// Track node.
const ObjectID oid = node->get_instance_id();
TrackedNode &tobj = _track(oid);
// Spawn state needs to be callected after "ready", but the spawn order follows "enter_tree".
ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);
tobj.spawner = spawner->get_instance_id();
spawned_nodes.insert(oid);
if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) {
if (tobj.net_id == 0) {
tobj.net_id = ++last_net_id;
}
_update_spawn_visibility(0, oid);
}
spawn_queue.insert(oid);
node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready).bind(oid), Node::CONNECT_ONE_SHOT);
return OK;
}
void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) {
ERR_FAIL_COND(!spawn_queue.has(p_oid)); // Bug.
// If we are a nested spawn, we need to wait until the parent is ready.
if (p_oid != *(spawn_queue.begin())) {
return;
}
for (const ObjectID &oid : spawn_queue) {
ERR_CONTINUE(!tracked_nodes.has(oid));
TrackedNode &tobj = tracked_nodes[oid];
MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tobj.spawner);
ERR_CONTINUE(!spawner);
spawned_nodes.insert(oid);
if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) {
if (tobj.net_id == 0) {
tobj.net_id = ++last_net_id;
}
_update_spawn_visibility(0, oid);
}
}
spawn_queue.clear();
}
Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);

View file

@ -51,7 +51,6 @@ private:
bool operator==(const ObjectID &p_other) { return id == p_other; }
_FORCE_INLINE_ MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; }
TrackedNode() {}
TrackedNode(const ObjectID &p_id) { id = p_id; }
TrackedNode(const ObjectID &p_id, uint32_t p_net_id) {
@ -75,7 +74,10 @@ private:
HashSet<ObjectID> spawned_nodes;
HashSet<ObjectID> sync_nodes;
// Pending spawn information.
// Pending local spawn information (handles spawning nested nodes during ready).
HashSet<ObjectID> spawn_queue;
// Pending remote spawn information.
ObjectID pending_spawn;
int pending_spawn_remote = 0;
const uint8_t *pending_buffer = nullptr;
@ -89,6 +91,7 @@ private:
TrackedNode &_track(const ObjectID &p_id);
void _untrack(const ObjectID &p_id);
void _node_ready(const ObjectID &p_oid);
void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec);
Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len);