public static void OnTcpTransportDetached(Session session) { Log.Info("TCP transport detached: session_id={0}", session.Id); // TCP 연결이 닫혔으나 세션은 유효합니다. 이 세션이 매칭 요청을 했다면 // 매칭을 취소합니다. 클라이언트는 TCP 연결이 끊어졌을 때 매칭을 다시 시도하게 // 해야 합니다. Event.EventFunction event_fn = new Event.EventFunction(() => { MatchmakingHelper.CancelMatchmaking(session); }); Event.Invoke(event_fn, session.Id); }
public static void OnLogout( SessionResponse.ResponseResult error, SessionResponse response, bool caused_by_session_close) { // 로그아웃 이후 처리를 담당합니다. Log.Info("OnLogout: session={0}, result={1}, caused_by={2},, data={3}", response.session.Id, (error == SessionResponse.ResponseResult.OK ? "ok" : "failed"), (caused_by_session_close ? "session close" : "action"), response.data.ToString()); Event.EventFunction event_fn = new Event.EventFunction(() => { MatchmakingHelper.CancelMatchmaking(response.session); // 세션 닫힘 이벤트가 아니라면 로그아웃 요청으로 이 콜백을 호출한 경우므로 // 메시지를 보냅니다. if (!caused_by_session_close) { SendMyMessage(response.session, kLogoutMessage, response.error_code, response.error_message, response.data); } }); Event.Invoke(event_fn, response.session.Id); }
static void OnMatchCompleted(string account_id, funapi.Matchmaking.Match match, funapi.Matchmaking.MatchResult result, Session session, SessionResponse.SessionResponseHandler handler) { // // 매치 결과를 받는 콜백 핸들러입니다. 매치를 요청한 각 플레이어를 대상으로 // 핸들러를 호출합니다. // 매치를 정상적으로 성사한 경우: match_id 로 직렬화 한 이벤트에서 실행합니다. // 매치 성사에 실패한 경우: 직렬화하지 않은 이벤트에서 실행합니다. // // 매치메이킹에 참여하는 사람이 많지 않거나 matchmaking_server_wrapper.cs 에서 // 정의한 매치 조건이 까다로운 경우, 클라이언트가 매치메이킹을 요청하는 시점과 // 매치가 성사되는 이 시점의 차가 커질 수 있습니다. // // 따라서 클라이언트는 매치메이킹 요청을 보낸 후 이 핸들러에서 메시지를 보내기 // 전까지 다른 행동을 할 수 있도록 만들어야 합니다. // Log.Info("OnMatchCompleted: account_id={0}, result={1}", account_id, result); if (result != funapi.Matchmaking.MatchResult.kSuccess) { // funapi.Matchmaking.Client.MatchResult 결과에 따라 어떻게 처리할 지 결정합니다. if (result == funapi.Matchmaking.MatchResult.kError) { // 엔진 내부 에러입니다. // 일반적으로 RPC 서비스를 사용할 수 없을 경우 발생합니다. handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 500, "Internal server error.", new JObject())); } else if (result == funapi.Matchmaking.MatchResult.kAlreadyRequested) { // 이미 이 account_id 로 매치메이킹 요청을 했습니다. 매치 타입이 다르더라도 // ID 가 같다면 실패합니다. handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Already requested.", new JObject())); } else if (result == funapi.Matchmaking.MatchResult.kTimeout) { // 매치메이킹 요청을 지정한 시간 안에 수행하지 못했습니다. // 더 넓은 범위의 매치 재시도 또는 클라이언트에게 단순 재시도를 요구할 수 있습니다. handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Timed out.", new JObject())); } else { // 아이펀 엔진에서 추후 MatchResult 를 더 추가할 경우를 대비해 // 이곳에 로그를 기록합니다. Log.Warning("not supported error: result={0}", result); handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 500, "Unknown error.", new JObject())); } return; } // 매치 결과 ID 입니다. Guid match_id = match.MatchId; // 매치메이킹 서버에서 정의한 JSON 컨텍스트 입니다. // 매치메이킹 요청(StartMatchmaking2) 시 인자로 넣는 context 와는 다른 // context 라는 점에 주의하세요. JObject match_context = match.Context; // 매치메이킹 요청 시 지정한 매치 타입입니다. long match_type = match.MatchType; // 이 매치에 참여한 플레이어 정보입니다. string player_ss = "["; for (int i = 0; i < match.Players.Count; ++i) { if (i != 0) { player_ss += ", "; } player_ss += String.Format("account_id={0}, user_data={1}", match.Players[i].Id, match.Players[i].Context.ToString()); } player_ss += "]"; Log.Info("Matchmaking succeed: match_id={0}, match_context={1}, match_type={2}, players={3}", match_id, match_context.ToString(), match_type, player_ss); // 매치메이킹에 성공했습니다. 매치메이킹 서버(matchmaking_server_wrapper.cc)에서 // 매치에 성공한 사람들을 모아 데디케이티드 서버 생성을 시작합니다. // (CheckMatchRequirements 함수에서 kMatchComplete 를 반환한 후입니다) // 이 시점에서는 단순히 히스토리만 초기화하고 메시지는 보내지 않습니다. // (데디케이티드 서버 생성 후 최종적으로 성공 메시지를 보냅니다) Event.EventFunction event_fn = new Event.EventFunction(() => { ClearMatchHistory(session); }); Event.Invoke(event_fn, session.Id); }
// 세션을 정리합니다. public static void FreeUser(Session session, Session.EncodingScheme encoding) { // 유저를 정리하기 위한 Context 를 읽어옵니다. string id; string opponent_id; session.GetFromContext("id", out id); session.GetFromContext("opponent", out opponent_id); // Session Context 를 초기화 합니다. session.Context = new JObject(); // 로그아웃하고 세션을 종료합니다. if (id != string.Empty) { AccountManager.LogoutCallback logout_cb = (string param_account_id, Session param_session, bool param_result) => { Log.InfoIf(param_result, "Logged out(local) by session close: id={0}", param_account_id); }; AccountManager.SetLoggedOutAsync(id, logout_cb); } // 대전 상대가 있는 경우, 상대가 승리한 것으로 처리하고 로비서버로 보냅니다. if (opponent_id == string.Empty) { return; } Session opponent_session = AccountManager.FindLocalSession(opponent_id); if (opponent_session == null) { return; } Event.EventFunction update = () => { User user = User.FetchById(id); User opponent_user = User.FetchById(opponent_id); Event.AssertNoRollback(); // 편의상 아래 함수에서 ORM 데이터 업데이트 처리도 합니다. Leaderboard.OnWin(opponent_user); Leaderboard.OnLose(user); if (encoding == Session.EncodingScheme.kJsonEncoding) { opponent_session.SendMessage("result", Utility.MakeResponse("win")); } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); FunMessage funmsg = new FunMessage(); GameResultMessage msg = new GameResultMessage(); msg.result = "win"; funmsg.AppendExtension_game_result(msg); opponent_session.SendMessage("result", funmsg); } Common.Redirect(opponent_session, "lobby"); }; Event.Invoke(update); }