From e22e6c8a9c81f5465a28bcc45b22c448f254f2fe Mon Sep 17 00:00:00 2001 From: Adam Frisby Date: Fri, 2 Aug 2024 03:26:18 +1000 Subject: [PATCH] * Improves the reliability of teleports, especially while the Event Queue is down. * Adds some functions to check if a message is a normal agent message, or a group message. * Adds new RelativePositionEstimate to complement RelativePosition - RelativePosition actually ends up drifting badly. RelativePositionEstimate instead calculates each time it is requested, using known velocity, and is far more accurate in practice. * Adds an override to ChatEventArgs.ToString to dump information about the chat. * Improves reliability of Group Messages * Teleports previously used GetGridRegion which is sometimes unreliable, request the full map, once, if it fails. The results will be cached. * Adds AgentManager.LastPositionUpdate to tell you when the avatars position was last updated --- LibreMetaverse/AgentManager.cs | 167 ++++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 34 deletions(-) diff --git a/LibreMetaverse/AgentManager.cs b/LibreMetaverse/AgentManager.cs index 680ba766..36386cd1 100644 --- a/LibreMetaverse/AgentManager.cs +++ b/LibreMetaverse/AgentManager.cs @@ -1293,6 +1293,8 @@ namespace OpenMetaverse /// The CollisionPlane of Agent public Vector4 CollisionPlane => collisionPlane; + public DateTime LastPositionUpdate { get; set; } + /// An representing the velocity of our agent public Vector3 Velocity => velocity; @@ -1357,6 +1359,17 @@ namespace OpenMetaverse public Vector3 RelativePosition { get => relativePosition; set => relativePosition = value; } + /// + /// Calculates the relative position of the agent, with velocity and + /// the time since the last update factored in. This is an estimate, + /// and could 'overshoot'; however it is much more likely to be correct + /// than RelativePosition while an agent is moving. + /// + public Vector3 RelativePositionEstimate + { + get => relativePosition + (velocity * (float)(DateTime.UtcNow - LastPositionUpdate).TotalSeconds); + set => relativePosition = value; + } /// Current rotation of the agent as a relative rotation from /// the simulator, or the parent object if we are sitting on something public Quaternion RelativeRotation { get => relativeRotation; @@ -1908,7 +1921,7 @@ namespace OpenMetaverse /// Accept invite for to a chatterbox session /// /// of session to accept invite to - public void ChatterBoxAcceptInvite(UUID session_id) + public async Task ChatterBoxAcceptInvite(UUID session_id) { if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null) { @@ -1919,18 +1932,12 @@ namespace OpenMetaverse if (cap != null) { ChatSessionAcceptInvitation acceptInvite = new ChatSessionAcceptInvitation {SessionID = session_id}; - - Task req = Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, acceptInvite.Serialize(), - CancellationToken.None, null); - req.ContinueWith(t => - { - lock (GroupChatSessions.Dictionary) - { - if (!GroupChatSessions.ContainsKey(session_id)) - GroupChatSessions.Add(session_id, new List()); - } - }); - + + // Wait for successful acceptance, so that: + // 1) error in server response can get caught by caller, and + // 2) session_id is included in GroupChatSessions when OnInstantMessage runs. + // (Since msg.GroupIM isn't used any more, the handler can tell if it has a group message by testing whether Self.GroupChatSessions.ContainsKey(e.IM.IMSessionID).) + await Client.HttpCapsClient.PostRequestAsync(cap, OSDFormat.Xml, acceptInvite.Serialize(), CancellationToken.None, null); } else { @@ -3009,6 +3016,8 @@ namespace OpenMetaverse return Teleport(simName, position, new Vector3(0, 1.0f, 0)); } + private bool _requestedMaps = false; + /// /// Attempt to look up a simulator name and teleport to the discovered /// destination @@ -3020,7 +3029,23 @@ namespace OpenMetaverse /// false public bool Teleport(string simName, Vector3 position, Vector3 lookAt) { - if (Client.Network.CurrentSim == null) { return false; } + if (string.IsNullOrEmpty(simName)) + { + TeleportMessage = $"Invalid simulator name"; + teleportStatus = TeleportStatus.Failed; + OnTeleport(new TeleportEventArgs(TeleportMessage, teleportStatus, TeleportFlags.Default)); + Logger.Log("Teleport failed; " + TeleportMessage, Helpers.LogLevel.Warning, Client); + return false; + } + + if (Client.Network.CurrentSim == null) + { + TeleportMessage = $"Not in a current simulator, cannot teleport to {simName}"; + teleportStatus = TeleportStatus.Failed; + OnTeleport(new TeleportEventArgs(TeleportMessage, teleportStatus, TeleportFlags.Default)); + Logger.Log("Teleport failed; " + TeleportMessage, Helpers.LogLevel.Warning, Client); + return false; + } if (teleportStatus == TeleportStatus.Progress) { @@ -3031,10 +3056,25 @@ namespace OpenMetaverse teleportStatus = TeleportStatus.None; + // Dodgy Hack - Requesting individual regions isn't always reliable. + if (!Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out var region)) + { + if (!_requestedMaps) + { + _requestedMaps = true; + Client.Grid.RequestMainlandSims(GridLayerType.Objects); + int max = 10; + while (!Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out region) && max-- > 0) + { + Thread.Sleep(1000); + } + } + } + if (simName != Client.Network.CurrentSim.Name) { // Teleporting to a foreign sim - if (Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out var region)) + if (Client.Grid.GetGridRegion(simName, GridLayerType.Objects, out region)) { return Teleport(region.RegionHandle, position, lookAt); } @@ -3042,6 +3082,8 @@ namespace OpenMetaverse { TeleportMessage = $"Unable to resolve simulator named: {simName}"; teleportStatus = TeleportStatus.Failed; + OnTeleport(new TeleportEventArgs(TeleportMessage, teleportStatus, TeleportFlags.Default)); + Logger.Log("Teleport failed; " + TeleportMessage, Helpers.LogLevel.Warning, Client); return false; } } @@ -3074,34 +3116,36 @@ namespace OpenMetaverse /// This call is blocking public bool Teleport(ulong regionHandle, Vector3 position, Vector3 lookAt) { - if (teleportStatus == TeleportStatus.Progress) - { - Logger.Log("Teleport already in progress while attempting to teleport.", - Helpers.LogLevel.Info, Client); - return false; - } - if (Client.Network.CurrentSim == null || Client.Network.CurrentSim.Caps == null || !Client.Network.CurrentSim.Caps.IsEventQueueRunning) { + Logger.Log("Event queue is down, reconnecting before attempting a teleport.", Helpers.LogLevel.Info, Client); // Wait a bit to see if the event queue comes online AutoResetEvent queueEvent = new AutoResetEvent(false); - void QueueCallback(object sender, EventQueueRunningEventArgs e) + EventHandler queueCallback = + delegate(object sender, EventQueueRunningEventArgs e) + { + if (e.Simulator == Client.Network.CurrentSim) + queueEvent.Set(); + }; + + if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null) { - if (e.Simulator == Client.Network.CurrentSim) { queueEvent.Set(); } + Logger.Log("Event queue restart requested.", Helpers.LogLevel.Info, Client); + Client.Network.CurrentSim.Caps.EventQueue.RestartIfDead(); } - Client.Network.EventQueueRunning += QueueCallback; + Client.Network.EventQueueRunning += queueCallback; queueEvent.WaitOne(10 * 1000, false); - Client.Network.EventQueueRunning -= QueueCallback; + Client.Network.EventQueueRunning -= queueCallback; } teleportStatus = TeleportStatus.None; teleportEvent.Reset(); - RequestTeleport(regionHandle, position, lookAt); + RequestTeleport(regionHandle, position, lookAt, true); teleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false); @@ -3111,6 +3155,8 @@ namespace OpenMetaverse { TeleportMessage = "Teleport timed out."; teleportStatus = TeleportStatus.Failed; + + Logger.Log("Teleport has timed out.", Helpers.LogLevel.Info, Client); } return (teleportStatus == TeleportStatus.Finished); @@ -3132,10 +3178,10 @@ namespace OpenMetaverse /// handle of region to teleport agent to /// position in destination sim to teleport to /// direction in destination sim agent will look at - public void RequestTeleport(ulong regionHandle, Vector3 position, Vector3 lookAt) + public void RequestTeleport(ulong regionHandle, Vector3 position, Vector3 lookAt, bool ignoreCapsStatus = false) { - if (Client.Network?.CurrentSim?.Caps != null && - Client.Network.CurrentSim.Caps.IsEventQueueRunning) + if (ignoreCapsStatus || (Client.Network?.CurrentSim?.Caps != null && + Client.Network.CurrentSim.Caps.IsEventQueueRunning)) { TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket { @@ -3158,6 +3204,7 @@ namespace OpenMetaverse } else { + Logger.Log("Event queue is not running, teleport abandoned.", Helpers.LogLevel.Info, Client); TeleportMessage = "CAPS event queue is not running"; teleportEvent.Set(); teleportStatus = TeleportStatus.Failed; @@ -3528,8 +3575,8 @@ namespace OpenMetaverse } }; - Client.Network.SendPacket(move, simulator); + Logger.Log("Sending complete agent movement to " + simulator.Handle + " / " + simulator.Name, Helpers.LogLevel.Info, Client); } /// @@ -3957,10 +4004,39 @@ namespace OpenMetaverse message.Offline = (InstantMessageOnline)im.MessageBlock.Offline; message.BinaryBucket = im.MessageBlock.BinaryBucket; + if (IsGroupMessage(message)) + { + lock (GroupChatSessions.Dictionary) + { + if (!GroupChatSessions.ContainsKey(message.IMSessionID)) + GroupChatSessions.Add(message.IMSessionID, new List()); + } + } + OnInstantMessage(new InstantMessageEventArgs(message, simulator)); } } + public bool IsGroupMessage(InstantMessage message) + { + if (message.GroupIM) + return true; + + lock (Client.Groups.GroupName2KeyCache.Dictionary) + { + if (Client.Groups.GroupName2KeyCache.ContainsKey(message.IMSessionID)) + return true; + } + + lock (GroupChatSessions.Dictionary) + { + if (GroupChatSessions.ContainsKey(message.IMSessionID)) + return true; + } + + return false; + } + protected void OfflineMessageHandlerCallback(HttpResponseMessage response, byte[] data, Exception error) { if (error != null) @@ -4019,6 +4095,15 @@ namespace OpenMetaverse ? msg["binary_bucket"].AsBinary() : new byte[] { 0 }; message.GroupIM = msg.ContainsKey("from_group") && msg["from_group"].AsBoolean(); + if (message.GroupIM) + { + lock (GroupChatSessions.Dictionary) + { + if (!GroupChatSessions.ContainsKey(message.IMSessionID)) + GroupChatSessions.Add(message.IMSessionID, new List()); + } + } + OnInstantMessage(new InstantMessageEventArgs(message, null)); } } @@ -4162,6 +4247,7 @@ namespace OpenMetaverse AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet; relativePosition = movement.Data.Position; + LastPositionUpdate = DateTime.UtcNow; Movement.Camera.LookDirection(movement.Data.LookAt); simulator.Handle = movement.Data.RegionHandle; simulator.SimVersion = Utils.BytesToString(movement.SimData.ChannelVersion); @@ -4439,6 +4525,7 @@ namespace OpenMetaverse flags = (TeleportFlags)local.Info.TeleportFlags; teleportStatus = TeleportStatus.Finished; relativePosition = local.Info.Position; + LastPositionUpdate = DateTime.UtcNow; Movement.Camera.LookDirection(local.Info.LookAt); // This field is apparently not used for anything //local.Info.LocationID; @@ -4576,7 +4663,7 @@ namespace OpenMetaverse IPEndPoint endPoint = new IPEndPoint(crossed.IP, crossed.Port); - Logger.DebugLog($"Crossed in to new region area, attempting to connect to {endPoint}", Client); + Logger.Log($"Crossed in to new region area, attempting to connect to {endPoint}", Helpers.LogLevel.Info, Client); Simulator oldSim = Client.Network.CurrentSim; Simulator newSim = Client.Network.Connect(endPoint, crossed.RegionHandle, true, crossed.SeedCapability, @@ -4611,7 +4698,7 @@ namespace OpenMetaverse Uri seedCap = new Uri(Utils.BytesToString(crossing.RegionData.SeedCapability)); IPEndPoint endPoint = new IPEndPoint(crossing.RegionData.SimIP, crossing.RegionData.SimPort); - Logger.DebugLog($"Crossed in to new region area, attempting to connect to {endPoint}", Client); + Logger.Log($"Crossed in to new region area, attempting to connect to {endPoint}", Helpers.LogLevel.Info, Client); Simulator oldSim = Client.Network.CurrentSim; Simulator newSim = Client.Network.Connect(endPoint, crossing.RegionData.RegionHandle, true, seedCap); @@ -4782,9 +4869,15 @@ namespace OpenMetaverse BinaryBucket = msg.BinaryBucket }; + lock (GroupChatSessions.Dictionary) + { + if (!GroupChatSessions.ContainsKey(msg.IMSessionID)) + GroupChatSessions.Add(msg.IMSessionID, new List()); + } + try { - ChatterBoxAcceptInvite(msg.IMSessionID); + ChatterBoxAcceptInvite(msg.IMSessionID).Wait(); } catch (Exception ex) { @@ -5075,6 +5168,12 @@ namespace OpenMetaverse Position = position; OwnerID = ownerid; } + + public override string ToString() + { + return + $"[ChatEvent: Sim={Simulator}, Message={Message}, AudibleLevel={AudibleLevel}, Type={Type}, SourceType={SourceType}, FromName={FromName}, SourceID={SourceID}, Position={Position}, OwnerID={OwnerID}]"; + } } /// Contains the data sent when a primitive opens a dialog with this agent