デディケーテッドサーバーへのクライアント再接続時に、デディケーテッドサーバー側で保管していたPlayerControllerとPlayerStateを使いまわす方法での再接続について

デディケイテッドサーバーへの再接続について、クライアントが抜ける際にPlayerControllerとPlayerStateをデディケーテッドサーバー上に残して再接続を許可している時間分Logout類の処理を遅延させ、

再度該当のクライアントがLoginしてきたときには該当のPlayerControllerとPlayerStateを取得して使用出来るようにしました。

このPlayerControllerとPlayerStateはGameInstanceに保管しており、ユーザーIDをKeyとして取得出来るようにしています。

遅延させている処理は下記3つになります。

・PlayerController::PawnLeavingGame

・PlayerController::OnNetCleanup

・GameMode::Logout

クライアントの再接続時にはGameModeのLoginで該当の抜ける前に使用していたPlayerControllerを返却し、

ControlChannelのReceivedBunchにて再接続のプレイヤーに対して下記のような処理を行っています。(oldController=保管していたPlayerController)

  1. const FString& errorMessage = gameMode->GameSession->ApproveLogin(options);
  2. if (errorMessage.IsEmpty())
  3. {
  4. Connection->PlayerController = oldController;
  5. oldController->NetConnection = Connection;
  6. Connection->SetClientLoginState(EClientLoginState::ReceivedJoin);
  7. oldController->SetReplicates(true);
  8. if (oldController->GetPawn()) { oldController->Possess(oldController->GetPawn()); }
  9. Connection->SetClientLoginState(EClientLoginState::ReceivedJoin);
  10. // if we’re in the middle of a transition or the client is in the wrong world, tell it to travel
  11. FString LevelName;
  12. FSeamlessTravelHandler& SeamlessTravelHandler = GEngine->SeamlessTravelHandlerForWorld(Connection->Driver->World);
  13. if (SeamlessTravelHandler.IsInTransition())
  14. {
  15. // tell the client to go to the destination map
  16. LevelName = SeamlessTravelHandler.GetDestinationMapName();
  17. }
  18. else if (!Connection->PlayerController->HasClientLoadedCurrentWorld())
  19. {
  20. // tell the client to go to our current map
  21. FString NewLevelName = GetOutermost()->GetName();
  22. UE_LOG(LogNet, Log, TEXT(“Client joined but was sent to another level. Asking client to travel to: ‘%s’”), *NewLevelName);
  23. LevelName = NewLevelName;
  24. }
  25. if (LevelName != TEXT(“”))
  26. {
  27. Connection->PlayerController->ClientTravel(LevelName, TRAVEL_Relative, true);
  28. }
  29. // @TODO FIXME - TEMP HACK? - clear queue on join
  30. Connection->QueuedBits = 0;
  31. }

その後、添付ファイルにあるコールスタックでデディケーテッドサーバーがクラッシュします。

PlayerControllerやPlayerStateに関しましては、現状PlayerControllerやPlayerState、その他そこに紐づくComponentやCharacter等非常にパラメーターが多く、使いまわさない場合の対応コストが高いためなるべく使いまわせるようにしたいと考えております。

そのため、DEV COMMUNITYのDeveloper Assistant AIに相談して進めていましたが、

解決しなかったため何か知見や解決方法がございましたらご教授いただけますと幸いです。

[Attachment Removed]

お世話になっております。

まず、基本的にクライアントの切断・再接続時には退避しておいた PlayerState 内の情報を元にプレイヤー関係のアクターについて再初期化を行うことが想定されているようです。

外部のドキュメントで恐縮ですが、このあたりの流れについては以下が分かりやすいかと思います。

https://wizardcell.com/unreal/persistent-data/#persisting-data-across-disconnects

再接続にそなえて一部のアクターを保存するという試みに関しては以下のスレッドが存在します。

[Content removed]

詳しくは(もしまだであれば)ご自身でお読みいただければと思いますが、ざっくりと抜き出しますと

・再接続にそなえて Pawn のみを存続させるか PlayerController をも存続させるかという方針についてのアドバイスを求める質問

・試したことがないので具体的なアドバイスはできないが、いずれにしても複雑な問題に直面することが予想されるとの回答

・そのうえで Pawn のみを存続させる方針の方が見込みがありそうとも

再接続にまつわる処理フローの改変については上記スレッドの内容を継承しこれ以上に踏み込んだ回答を致しかねます点をご理解頂きたいのですが、

局所的な問題としてクラッシュについては、添付して頂いたコールスタックを見たところ

UNetDriver::DestroyedStartupOrDormantActors の Iterator を初期化する部分でのトラブルによるもののようです。

この変数が実際にどのような状態であるか、そして不正な状態になる流れが無いかを確認されるのがよろしいかと思います。

よろしくお願いいたします。

[Attachment Removed]