/// <summary>Used only for visual output of the Relay connection info. The obfuscated Relay server IP is obtained during allocation in the RelayUtpSetup.</summary>
OnUpdate(m_updatePeriod);// Using m_updatePeriod will be incorrect on the first update for a new subscriber, due to the staggering. However, we don't expect UpdateSlow subscribers to require precision, and this is less verbose than tracking per-subscriber.
}
OnUpdate(Time.deltaTime);
/// <summary>
/// Each frame, advance all subscribers. Any that have hit their period should then act, though if they take too long they could be removed.
/// </summary>
Stopwatchstopwatch=newStopwatch();
m_nextActiveSubIndex=System.Math.Max(0,System.Math.Min(m_subscribers.Count-1,m_nextActiveSubIndex));// Just a backup.
if(onUpdate==null||onUpdate.Target==null)// In case something forgets to Unsubscribe when it dies.
{Remove(m_nextActiveSubIndex,$"Did not Unsubscribe from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
return;
}
if(onUpdate.Method.ToString().Contains("<"))// Detect an anonymous or lambda or local method that cannot be Unsubscribed, by checking for a character that can't exist in a declared method name.
{Remove(m_nextActiveSubIndex,$"Removed anonymous from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
for(ints=m_subscribers.Count-1;s>=0;s--)// Iterate in reverse in case we need to remove something.
if(!m_doNotRemoveIfTooLong)
Remove(m_nextActiveSubIndex,$"UpdateSlow subscriber took too long, removing: {onUpdate.Target} : {onUpdate.Method}");
else
varsub=m_subscribers[s];
sub.periodCurrent+=Time.deltaTime;
if(sub.periodCurrent>sub.period)
Debug.LogWarning($"UpdateSlow subscriber took too long: {onUpdate.Target} : {onUpdate.Method}");
Increment();
Stopwatchstopwatch=newStopwatch();
UpdateMethodonUpdate=sub.updateMethod;
if(onUpdate==null)// In case something forgets to Unsubscribe when it dies.
{Remove(s,$"Did not Unsubscribe from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
continue;
}
if(onUpdate.Target==null)// Detect a local function that cannot be Unsubscribed since it could go out of scope.
{Remove(s,$"Removed local function from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
continue;
}
if(onUpdate.Method.ToString().Contains("<"))// Detect an anonymous function that cannot be Unsubscribed, by checking for a character that can't exist in a declared method name.
{Remove(s,$"Removed anonymous from UpdateSlow: {onUpdate.Target} : {onUpdate.Method}");
Locator.Get.UpdateSlow.Subscribe(UpdateLobby);// Shouldn't need to unsubscribe since this instance won't be replaced.
Locator.Get.UpdateSlow.Subscribe(UpdateLobby,0.5f);// Shouldn't need to unsubscribe since this instance won't be replaced. 0.5s is arbitrary; the rate limits are tracked later.
#region Lobby API calls are rate limited, and some other operations might want an alert when the rate limits have passed.
// Note that some APIs limit to 1 call per N seconds, while others limit to M calls per N seconds. We'll treat all APIs as though they limited to 1 call per N seconds.
m_pendingOperations.Dequeue()?.Invoke();// Note: If this ends up enqueuing a bunch of operations, we might need to batch them and/or ensure they don't all execute at once.
if(type==RequestType.Join)
returnm_rateLimitJoin;
returnm_rateLimitQuery;
privateRateLimitCooldownm_rateLimitQuery=newRateLimitCooldown(1.5f);// Used for both the lobby list UI and the in-lobby updating. In the latter case, updates can be cached.
/// <param name="onListRetrieved">If called with null, retrieval was unsuccessful. Else, this will be given a list of contents to display, as pairs of a lobby code and a display string for that lobby.</param>
m_isHandlingPending=false;// (Backup in case a pending operation hit an exception.)
if(m_timeSinceLastCall>=m_cooldownTime)
{
IsInCooldown=false;
if(!m_isInCooldown)// It's possible that by setting IsInCooldown, something called CanCall immediately, in which case we want to stay on UpdateSlow.
{
Locator.Get.UpdateSlow.Unsubscribe(OnUpdate);// Note that this is after IsInCooldown is set, to prevent an Observer from kicking off CanCall again immediately.
m_isHandlingPending=true;
while(m_pendingOperations.Count>0)
m_pendingOperations.Dequeue()?.Invoke();// Note: If this ends up enqueuing many operations, we might need to batch them and/or ensure they don't all execute at once.
m_isHandlingPending=false;
}
}
}
publicoverridevoidCopyObserved(RateLimitCooldownoldObserved){/* This behavior isn't needed; we're just here for the OnChanged event management. */}
m_shouldPushData=true;// Ensure the initial presence of a new player is pushed to the lobby; otherwise, when a non-host joins, the LocalLobby never receives their data until they push something new.
ReceiveNetworkEvents(m_networkDriver);// Just on the first execution, make sure to handle any events that accumulated while completing the connection.
m_networkDriver.ScheduleUpdate().Complete();// This pumps all messages, which pings the Relay allocation and keeps it alive. It should be called no more often than ReceiveNetworkEvents.
ReceiveNetworkEvents(m_networkDriver);// This reads the message queue which was just updated.
while((cmd=driver.PopEvent(outconn,outstrm))!=NetworkEvent.Type.Empty)// NetworkConnection also has PopEvent, but NetworkDriver.PopEvent automatically includes new connections.
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
privateList<Subscriber>m_activeSubscribers=newList<Subscriber>();// For cleaning up, in case an Assert prevents a Subscriber from taking care of itself.
privateconstfloatk_period=1.5f;
/// <summary>Trivial Subscriber to do some action every UpdateSlow.</summary>
Assert.AreEqual(1.5f,sub.prevDt,"Slow update should have received the full time delta.");
Assert.AreNotEqual(period,sub.prevDt,"Slow update should have received the actual amount of time that passed, not necessarily its period.");
Assert.True(sub.prevDt-period<0.05f&&sub.prevDt-period>0,"The time delta received by slow update should match the actual time since their previous update.");
yieldreturnnewWaitForSeconds(k_period);
yieldreturnnewWaitForSeconds(period);
Assert.AreEqual(1.5f,sub.prevDt,"Slow update should have received the full time delta again.");
Assert.AreNotEqual(period,sub.prevDt,"Slow update should have received the full time delta, not just its period, again.");
Assert.True(sub.prevDt-period<0.05f&&sub.prevDt-period>0,"The time delta received by slow update should match the actual time since their previous update, again.");
Assert.AreEqual(1.5f,sub.prevDt,"Slow update should have received the full time delta with two subscribers.");
Assert.AreEqual(1.5f,sub2.prevDt,"Slow update should have received the full time delta on the second subscriber as well.");
Assert.True(sub.prevDt-period<0.05f&&sub.prevDt-period>0,"Slow update on the first subscriber should have received the full time delta with two subscribers.");
Assert.True(sub2.prevDt-period2<0.05f&&sub2.prevDt-period2>0,"Slow update on the second subscriber should receive the actual time, even if its period is shorter.");