浏览代码

Fix for the issue where a host could continue to make lobby color edits as another client joined and not see the join until afterward: The lobby heartbeat now forces the host to pull every time they push, which they weren't doing previously. This requires a mechanism for ensuring edits that are made between initiating the push and completing the pull are not overwritten when the pull completes.

/main/staging/host_handshake
nathaniel.buck@unity3d.com 3 年前
当前提交
5e5fb86c
共有 4 个文件被更改,包括 43 次插入16 次删除
  1. 19
      Assets/Scripts/Game/LocalLobby.cs
  2. 12
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  3. 24
      Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
  4. 4
      Assets/Scripts/Lobby/ToLocalLobby.cs

19
Assets/Scripts/Game/LocalLobby.cs


public int MaxPlayerCount { get; set; }
public LobbyState State { get; set; }
public LobbyColor Color { get; set; }
public long State_LastEdit { get; set; }
public long Color_LastEdit { get; set; }
public LobbyData(LobbyData existing)
{

MaxPlayerCount = existing.MaxPlayerCount;
State = existing.State;
Color = existing.Color;
State_LastEdit = existing.State_LastEdit;
Color_LastEdit = existing.Color_LastEdit;
}
public LobbyData(string lobbyCode)

MaxPlayerCount = -1;
State = LobbyState.Lobby;
Color = LobbyColor.None;
State_LastEdit = 0;
Color_LastEdit = 0;
}
}

set
{
m_data.State = value;
m_data.State_LastEdit = DateTime.Now.Ticks;
OnChanged(this);
}
}

{
if (m_data.Color != value)
{ m_data.Color = value;
m_data.Color_LastEdit = DateTime.Now.Ticks;
OnChanged(this);
}
}

{
// It's possible for the host to edit the lobby in between the time they last pushed lobby data and the time their pull for new lobby data completes.
// If that happens, the edit will be lost, so instead we maintain the time of last edit to detect that case.
var pendingState = data.State;
var pendingColor = data.Color;
if (m_data.State_LastEdit > data.State_LastEdit)
pendingState = m_data.State;
if (m_data.Color_LastEdit > data.Color_LastEdit)
pendingColor = m_data.Color;
m_data.State = pendingState;
m_data.Color = pendingColor;
if (currUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();
else

12
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


dataCurr.Add(dataNew.Key, dataObj);
}
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, dataCurr, (r) => { onComplete?.Invoke(); }, null, null);
LobbyAPIInterface.UpdatePlayerAsync(m_lastKnownLobby.Id, playerId, dataCurr, (result) => {
if (result != null)
m_lastKnownLobby = result; // Store the most up-to-date lobby now since we have it, instead of waiting for the next heartbeat.
onComplete?.Invoke();
}, null, null);
}
/// <summary>

dataCurr.Add(dataNew.Key, dataObj);
}
LobbyAPIInterface.UpdateLobbyAsync(lobby.Id, dataCurr, (r) => { onComplete?.Invoke(); });
LobbyAPIInterface.UpdateLobbyAsync(lobby.Id, dataCurr, (result) => {
if (result != null)
m_lastKnownLobby = result;
onComplete?.Invoke();
});
}
/// <summary>

24
Assets/Scripts/Lobby/LobbyContentHeartbeat.cs


{
private LocalLobby m_localLobby;
private LobbyUser m_localUser;
private bool m_isAwaitingQuery = false;
private int m_awaitingQueryCount = 0;
private bool m_shouldPushData = false;
private const float k_approvalMaxTime = 10; // Used for determining if a user should timeout if they are unable to connect.

private void OnUpdate(float dt)
{
m_lifetime += dt;
if (m_isAwaitingQuery || m_localLobby == null)
if (m_awaitingQueryCount > 0 || m_localLobby == null)
return;
if (m_localUser.IsHost)
LobbyAsyncRequests.Instance.DoLobbyHeartbeat(dt);

Locator.Get.Messenger.OnReceiveMessage(MessageType.ChangeGameState, GameState.JoinMenu);
}
m_isAwaitingQuery = true; // Note that because we make async calls, if one of them fails and doesn't call our callback, this will never be reset to false.
if (m_shouldPushData)
PushDataToLobby();
else

void PushDataToLobby()
{
if (m_localUser == null)
{
m_isAwaitingQuery = false;
return; // Don't revert m_shouldPushData yet, so that we can retry.
}
{
m_awaitingQueryCount++;
else
DoPlayerDataPush();
}
m_awaitingQueryCount++;
DoPlayerDataPush();
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(RetrieveLobbyData(m_localLobby), () => { DoPlayerDataPush(); });
LobbyAsyncRequests.Instance.UpdateLobbyDataAsync(RetrieveLobbyData(m_localLobby), () => { if (--m_awaitingQueryCount <= 0) OnRetrieve(); });
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(RetrieveUserData(m_localUser), () => { m_isAwaitingQuery = false; });
LobbyAsyncRequests.Instance.UpdatePlayerDataAsync(RetrieveUserData(m_localUser), () => { if (--m_awaitingQueryCount <= 0) OnRetrieve(); });
m_isAwaitingQuery = false;
LobbyRemote lobbyRemote = LobbyAsyncRequests.Instance.CurrentLobby;
if (lobbyRemote == null) return;
bool prevShouldPush = m_shouldPushData;

data.Add("RelayCode", lobby.RelayCode);
data.Add("State", ((int)lobby.State).ToString()); // Using an int is smaller than using the enum state's name.
data.Add("Color", ((int)lobby.Color).ToString());
data.Add("State_LastEdit", lobby.Data.State_LastEdit.ToString());
data.Add("Color_LastEdit", lobby.Data.Color_LastEdit.ToString());
return data;
}

4
Assets/Scripts/Lobby/ToLocalLobby.cs


MaxPlayerCount = lobby.MaxPlayers,
RelayCode = lobby.Data?.ContainsKey("RelayCode") == true ? lobby.Data["RelayCode"].Value : null, // By providing RelayCode through the lobby data with Member visibility, we ensure a client is connected to the lobby before they could attempt a relay connection, preventing timing issues between them.
State = lobby.Data?.ContainsKey("State") == true ? (LobbyState) int.Parse(lobby.Data["State"].Value) : LobbyState.Lobby,
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None,
State_LastEdit = lobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(lobby.Data["State_LastEdit"].Value) : 0,
Color_LastEdit = lobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(lobby.Data["Color_LastEdit"].Value) : 0
};
Dictionary<string, LobbyUser> lobbyUsers = new Dictionary<string, LobbyUser>();

正在加载...
取消
保存