* 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
This commit is contained in:
Adam Frisby
2024-08-02 03:26:18 +10:00
committed by Cinder Roxley
parent 20bb76ba7c
commit e22e6c8a9c

View File

@@ -1293,6 +1293,8 @@ namespace OpenMetaverse
/// <summary>The CollisionPlane of Agent</summary>
public Vector4 CollisionPlane => collisionPlane;
public DateTime LastPositionUpdate { get; set; }
/// <summary>An <seealso cref="Vector3"/> representing the velocity of our agent</summary>
public Vector3 Velocity => velocity;
@@ -1357,6 +1359,17 @@ namespace OpenMetaverse
public Vector3 RelativePosition { get => relativePosition;
set => relativePosition = value;
}
/// <summary>
/// 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.
/// </summary>
public Vector3 RelativePositionEstimate
{
get => relativePosition + (velocity * (float)(DateTime.UtcNow - LastPositionUpdate).TotalSeconds);
set => relativePosition = value;
}
/// <summary>Current rotation of the agent as a relative rotation from
/// the simulator, or the parent object if we are sitting on something</summary>
public Quaternion RelativeRotation { get => relativeRotation;
@@ -1908,7 +1921,7 @@ namespace OpenMetaverse
/// Accept invite for to a chatterbox session
/// </summary>
/// <param name="session_id"><seealso cref="UUID"/> of session to accept invite to</param>
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<ChatSessionMember>());
}
});
// 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;
/// <summary>
/// Attempt to look up a simulator name and teleport to the discovered
/// destination
@@ -3020,7 +3029,23 @@ namespace OpenMetaverse
/// false</returns>
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
/// <remarks>This call is blocking</remarks>
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<EventQueueRunningEventArgs> 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
/// <param name="regionHandle">handle of region to teleport agent to</param>
/// <param name="position"><seealso cref="Vector3"/> position in destination sim to teleport to</param>
/// <param name="lookAt"><seealso cref="Vector3"/> direction in destination sim agent will look at</param>
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);
}
/// <summary>
@@ -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<ChatSessionMember>());
}
}
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<ChatSessionMember>());
}
}
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<ChatSessionMember>());
}
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}]";
}
}
/// <summary>Contains the data sent when a primitive opens a dialog with this agent</summary>