diff --git a/OpenSim/Framework/AvatarAppearance.cs b/OpenSim/Framework/AvatarAppearance.cs index ba6d87dac2..b7a0adface 100644 --- a/OpenSim/Framework/AvatarAppearance.cs +++ b/OpenSim/Framework/AvatarAppearance.cs @@ -561,45 +561,59 @@ namespace OpenSim.Framework if (attachpoint == 0) return false; - if (item == UUID.Zero) + lock (m_attachments) { - lock (m_attachments) + if (item == UUID.Zero) { if (m_attachments.ContainsKey(attachpoint)) { m_attachments.Remove(attachpoint); return true; } + + return false; + } + + // When a user logs in, the attachment item ids are pulled from persistence in the Avatars table. However, + // the asset ids are not saved. When the avatar enters a simulator the attachments are set again. If + // we simply perform an item check here then the asset ids (which are now present) are never set, and NPC attachments + // later fail unless the attachment is detached and reattached. + // + // Therefore, we will carry on with the set if the existing attachment has no asset id. + AvatarAttachment existingAttachment = GetAttachmentForItem(item); + if (existingAttachment != null) + { +// m_log.DebugFormat( +// "[AVATAR APPEARANCE]: Found existing attachment for {0}, asset {1} at point {2}", +// existingAttachment.ItemID, existingAttachment.AssetID, existingAttachment.AttachPoint); + + if (existingAttachment.AssetID != UUID.Zero && existingAttachment.AttachPoint == (attachpoint & 0x7F)) + { + m_log.DebugFormat( + "[AVATAR APPEARANCE]: Ignoring attempt to attach an already attached item {0} at point {1}", + item, attachpoint); + + return false; + } + else + { + // Remove it here so that the later append does not add a second attachment but we still update + // the assetID + DetachAttachment(existingAttachment.ItemID); + } } - return false; - } - - // When a user logs in, the attachment item ids are pulled from persistence in the Avatars table. However, - // the asset ids are not saved. When the avatar enters a simulator the attachments are set again. If - // we simply perform an item check here then the asset ids (which are now present) are never set, and NPC attachments - // later fail unless the attachment is detached and reattached. - // - // Therefore, we will carry on with the set if the existing attachment has no asset id. - AvatarAttachment existingAttachment = GetAttachmentForItem(item); - if (existingAttachment != null - && existingAttachment.AssetID != UUID.Zero - && existingAttachment.AttachPoint == (attachpoint & 0x7F)) - { - // m_log.DebugFormat("[AVATAR APPEARANCE] attempt to attach an already attached item {0}",item); - return false; - } - - // check if this is an append or a replace, 0x80 marks it as an append - if ((attachpoint & 0x80) > 0) - { - // strip the append bit - int point = attachpoint & 0x7F; - AppendAttachment(new AvatarAttachment(point, item, asset)); - } - else - { - ReplaceAttachment(new AvatarAttachment(attachpoint,item, asset)); + // check if this is an append or a replace, 0x80 marks it as an append + if ((attachpoint & 0x80) > 0) + { + // strip the append bit + int point = attachpoint & 0x7F; + AppendAttachment(new AvatarAttachment(point, item, asset)); + } + else + { + ReplaceAttachment(new AvatarAttachment(attachpoint,item, asset)); + } } return true; @@ -648,6 +662,10 @@ namespace OpenSim.Framework int index = kvp.Value.FindIndex(delegate(AvatarAttachment a) { return a.ItemID == itemID; }); if (index >= 0) { +// m_log.DebugFormat( +// "[AVATAR APPEARANCE]: Detaching attachment {0}, index {1}, point {2}", +// m_attachments[kvp.Key][index].ItemID, index, m_attachments[kvp.Key][index].AttachPoint); + // Remove it from the list of attachments at that attach point m_attachments[kvp.Key].RemoveAt(index); diff --git a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs index 109a58f741..6a68322531 100644 --- a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs @@ -359,8 +359,9 @@ Asset service request failures: {3}" + Environment.NewLine, inPacketsPerSecond, outPacketsPerSecond, pendingDownloads, pendingUploads, unackedBytes, totalFrameTime, netFrameTime, physicsFrameTime, otherFrameTime, agentFrameTime, imageFrameTime)); + /* 20130319 RA: For the moment, disable the dump of 'scene' catagory as they are mostly output by + * the two formatted printouts above. SortedDictionary> sceneStats; - if (StatsManager.TryGetStats("scene", out sceneStats)) { foreach (KeyValuePair> kvp in sceneStats) @@ -374,6 +375,7 @@ Asset service request failures: {3}" + Environment.NewLine, } } } + */ /* sb.Append(Environment.NewLine); diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index 96e8f3504e..08e7cb7446 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -369,8 +369,7 @@ namespace OpenSim.Region.CoreModules.Asset AssetBase asset = null; string filename = GetFileName(id); - - if (File.Exists(filename)) + while (File.Exists(filename)) { FileStream stream = null; try @@ -381,6 +380,8 @@ namespace OpenSim.Region.CoreModules.Asset asset = (AssetBase)bformatter.Deserialize(stream); m_DiskHits++; + + break; } catch (System.Runtime.Serialization.SerializationException e) { @@ -393,12 +394,24 @@ namespace OpenSim.Region.CoreModules.Asset // {different version of AssetBase} -- we should attempt to // delete it and re-cache File.Delete(filename); + + break; + } + catch (IOException e) + { + // This is a sharing violation: File exists but can't be opened because it's + // being written + Thread.Sleep(100); + + continue; } catch (Exception e) { m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); + + break; } finally { diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index c94d152749..f62512eb5a 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs @@ -573,8 +573,8 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments } // m_log.DebugFormat( -// "[ATTACHMENTS MODULE]: Detaching object {0} {1} for {2} in {3}", -// so.Name, so.LocalId, sp.Name, m_scene.Name); +// "[ATTACHMENTS MODULE]: Detaching object {0} {1} (FromItemID {2}) for {3} in {4}", +// so.Name, so.LocalId, so.FromItemID, sp.Name, m_scene.Name); // Scripts MUST be snapshotted before the object is // removed from the scene because doing otherwise will diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs index dee8ce33d3..a8fe045a29 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs @@ -829,7 +829,13 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests sceneB, config, new CapabilitiesModule(), etmB, attModB, new BasicInventoryAccessModule()); UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(sceneA, 0x1); - ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, ua1.PrincipalID, sh.SceneManager); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + TestClient tc = new TestClient(acd, sceneA, sh.SceneManager); + List destinationTestClients = new List(); + EntityTransferHelpers.SetUpInformClientOfNeighbour(tc, destinationTestClients); + + ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, tc, acd, sh.SceneManager); beforeTeleportSp.AbsolutePosition = new Vector3(30, 31, 32); InventoryItemBase attItem = CreateAttachmentItem(sceneA, ua1.PrincipalID, "att", 0x10, 0x20); @@ -848,7 +854,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)beforeTeleportSp.ControllingClient).CompleteTeleportClientSide(); + destinationTestClients[0].CompleteMovement(); // Check attachments have made it into sceneB ScenePresence afterTeleportSceneBSp = sceneB.GetScenePresence(ua1.PrincipalID); diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs index 864f33e127..51fe1ea9af 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -361,7 +361,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory public void QueueAppearanceSave(UUID agentid) { - // m_log.WarnFormat("[AVFACTORY]: Queue appearance save for {0}", agentid); +// m_log.DebugFormat("[AVFACTORY]: Queueing appearance save for {0}", agentid); // 10000 ticks per millisecond, 1000 milliseconds per second long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_savetime * 1000 * 10000); @@ -658,7 +658,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory return; } - // m_log.WarnFormat("[AVFACTORY] avatar {0} save appearance",agentid); +// m_log.DebugFormat("[AVFACTORY]: Saving appearance for avatar {0}", agentid); // This could take awhile since it needs to pull inventory // We need to do it at the point of save so that there is a sufficient delay for any upload of new body part/shape @@ -667,6 +667,14 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory // multiple save requests. SetAppearanceAssets(sp.UUID, sp.Appearance); +// List attachments = sp.Appearance.GetAttachments(); +// foreach (AvatarAttachment att in attachments) +// { +// m_log.DebugFormat( +// "[AVFACTORY]: For {0} saving attachment {1} at point {2}", +// sp.Name, att.ItemID, att.AttachPoint); +// } + m_scene.AvatarService.SetAppearance(agentid, sp.Appearance); // Trigger this here because it's the final step in the set/queue/save process for appearance setting. diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs index 4c3f1ccbbb..174642db57 100644 --- a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs @@ -256,7 +256,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat // If camera is moved into client, then camera position can be used // MT: No, it can't, as chat is heard from the avatar position, not // the camera position. - s.ForEachRootScenePresence( + s.ForEachScenePresence( delegate(ScenePresence presence) { if (destination != UUID.Zero && presence.UUID != destination) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index b2c9564863..25334b9f05 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -167,6 +167,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!DisableInterRegionTeleportCancellation) client.OnTeleportCancel += OnClientCancelTeleport; + + client.OnConnectionClosed += OnConnectionClosed; } public virtual void Close() {} @@ -185,6 +187,18 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #region Agent Teleports + private void OnConnectionClosed(IClientAPI client) + { + if (client.IsLoggingOut) + { + m_entityTransferStateMachine.UpdateInTransit(client.AgentId, AgentTransferState.Aborting); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport request from {0} in {1} due to simultaneous logout", + client.Name, Scene.Name); + } + } + private void OnClientCancelTeleport(IClientAPI client) { m_entityTransferStateMachine.UpdateInTransit(client.AgentId, AgentTransferState.Cancelling); @@ -520,12 +534,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[ENTITY TRANSFER MODULE]: Failed validation of all attachments for teleport of {0} from {1} to {2}. Continuing.", sp.Name, sp.Scene.RegionInfo.RegionName, finalDestination.RegionName); -// if (!sp.ValidateAttachments()) -// { -// sp.ControllingClient.SendTeleportFailed("Inconsistent attachment state"); -// return; -// } - string reason; string version; if (!Scene.SimulationService.QueryAccess( @@ -588,6 +596,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } // Let's create an agent there if one doesn't exist yet. + // NOTE: logout will always be false for a non-HG teleport. bool logout = false; if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { @@ -608,6 +617,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } + else if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after CreateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } // Past this point we have to attempt clean up if the teleport fails, so update transfer state. m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); @@ -630,11 +647,13 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (m_eqModule != null) { + // The EnableSimulator message makes the client establish a connection with the destination + // simulator by sending the initial UseCircuitCode UDP packet to the destination containing the + // correct circuit code. m_eqModule.EnableSimulator(destinationHandle, endPoint, sp.UUID); - // ES makes the client send a UseCircuitCode message to the destination, - // which triggers a bunch of things there. - // So let's wait + // XXX: Is this wait necessary? We will always end up waiting on UpdateAgent for the destination + // simulator to confirm that it has established communication with the viewer. Thread.Sleep(200); // At least on LL 3.3.4 for teleports between different regions on the same simulator this appears @@ -645,6 +664,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } else { + // XXX: This is a little misleading since we're information the client of its avatar destination, + // which may or may not be a neighbour region of the source region. This path is probably little + // used anyway (with EQ being the one used). But it is currently being used for test code. sp.ControllingClient.InformClientOfNeighbour(destinationHandle, endPoint); } } @@ -662,14 +684,37 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer //sp.ControllingClient.SendTeleportProgress(teleportFlags, "Updating agent..."); + // We will check for an abort before UpdateAgent since UpdateAgent will require an active viewer to + // establish th econnection to the destination which makes it return true. + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} before UpdateAgent", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + + // A common teleport failure occurs when we can send CreateAgent to the + // destination region but the viewer cannot establish the connection (e.g. due to network issues between + // the viewer and the destination). In this case, UpdateAgent timesout after 10 seconds, although then + // there's a further 10 second wait whilst we attempt to tell the destination to delete the agent in Fail(). if (!UpdateAgent(reg, finalDestination, agent, sp)) { - // Region doesn't take it + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after UpdateAgent due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: UpdateAgent failed on teleport of {0} to {1} from {2}. Returning avatar to source region.", + "[ENTITY TRANSFER MODULE]: UpdateAgent failed on teleport of {0} to {1} from {2}. Keeping avatar in source region.", sp.Name, finalDestination.RegionName, sp.Scene.RegionInfo.RegionName); - Fail(sp, finalDestination, logout); + Fail(sp, finalDestination, logout, "Connection between viewer and destination region could not be established."); return; } @@ -679,7 +724,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after UpdateAgent on client request", sp.Name, finalDestination.RegionName, sp.Scene.Name); - CleanupAbortedInterRegionTeleport(sp, finalDestination); + CleanupFailedInterRegionTeleport(sp, finalDestination); return; } @@ -688,6 +733,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}", capsPath, sp.Scene.RegionInfo.RegionName, sp.Name); + // We need to set this here to avoid an unlikely race condition when teleporting to a neighbour simulator, + // where that neighbour simulator could otherwise request a child agent create on the source which then + // closes our existing agent which is still signalled as root. + sp.IsChildAgent = true; + if (m_eqModule != null) { m_eqModule.TeleportFinishEvent(destinationHandle, 13, endPoint, 0, teleportFlags, capsPath, sp.UUID); @@ -698,19 +748,25 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer teleportFlags, capsPath); } - // Let's set this to true tentatively. This does not trigger OnChildAgent - sp.IsChildAgent = true; - // TeleportFinish makes the client send CompleteMovementIntoRegion (at the destination), which // trigers a whole shebang of things there, including MakeRoot. So let's wait for confirmation // that the client contacted the destination before we close things here. if (!m_entityTransferStateMachine.WaitForAgentArrivedAtDestination(sp.UUID)) { + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Aborting) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Aborted teleport of {0} to {1} from {2} after WaitForAgentArrivedAtDestination due to previous client close.", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + m_log.WarnFormat( "[ENTITY TRANSFER MODULE]: Teleport of {0} to {1} from {2} failed due to no callback from destination region. Returning avatar to source region.", sp.Name, finalDestination.RegionName, sp.Scene.RegionInfo.RegionName); - Fail(sp, finalDestination, logout); + Fail(sp, finalDestination, logout, "Destination region did not signal teleport completion."); return; } @@ -733,8 +789,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Now let's make it officially a child agent sp.MakeChildAgent(); -// sp.Scene.CleanDroppedAttachments(); - // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone if (NeedsClosing(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) @@ -774,7 +828,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// /// /// - protected virtual void CleanupAbortedInterRegionTeleport(ScenePresence sp, GridRegion finalDestination) + protected virtual void CleanupFailedInterRegionTeleport(ScenePresence sp, GridRegion finalDestination) { m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); @@ -793,12 +847,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// /// /// - protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) + /// Human readable reason for teleport failure. Will be sent to client. + protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout, string reason) { - CleanupAbortedInterRegionTeleport(sp, finalDestination); + CleanupFailedInterRegionTeleport(sp, finalDestination); sp.ControllingClient.SendTeleportFailed( - string.Format("Problems connecting to destination {0}", finalDestination.RegionName)); + string.Format( + "Problems connecting to destination {0}, reason: {1}", finalDestination.RegionName, reason)); + sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); } diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs index 8b2cd09bb2..e90338390f 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs @@ -51,11 +51,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// This is a state machine. /// /// [Entry] => Preparing - /// Preparing => { Transferring || Cancelling || CleaningUp || [Exit] } - /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp } - /// Cancelling => CleaningUp - /// ReceivedAtDestination => CleaningUp + /// Preparing => { Transferring || Cancelling || CleaningUp || Aborting || [Exit] } + /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp || Aborting } + /// Cancelling => CleaningUp || Aborting + /// ReceivedAtDestination => CleaningUp || Aborting /// CleaningUp => [Exit] + /// Aborting => [Exit] /// /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp /// However, any state can transition to CleaningUp if the teleport has failed. @@ -66,7 +67,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer Transferring, // The agent is in the process of being transferred to a destination ReceivedAtDestination, // The destination has notified us that the agent has been successfully received CleaningUp, // The agent is being changed to child/removed after a transfer - Cancelling // The user has cancelled the teleport but we have yet to act upon this. + Cancelling, // The user has cancelled the teleport but we have yet to act upon this. + Aborting // The transfer is aborting. Unlike Cancelling, no compensating actions should be performed } /// @@ -134,7 +136,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Illegal to try and update an agent that's not actually in transit. if (!m_agentsInTransit.ContainsKey(id)) { - if (newState != AgentTransferState.Cancelling) + if (newState != AgentTransferState.Cancelling && newState != AgentTransferState.Aborting) failureMessage = string.Format( "Agent with ID {0} is not registered as in transit in {1}", id, m_mod.Scene.RegionInfo.RegionName); @@ -145,7 +147,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { oldState = m_agentsInTransit[id]; - if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) + if (newState == AgentTransferState.Aborting) + { + transitionOkay = true; + } + else if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) { transitionOkay = true; } @@ -292,8 +298,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // There should be no race condition here since no other code should be removing the agent transfer or // changing the state to another other than Transferring => ReceivedAtDestination. - while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0) + + while (count-- > 0) { + lock (m_agentsInTransit) + { + if (m_agentsInTransit[id] == AgentTransferState.ReceivedAtDestination) + break; + } + // m_log.Debug(" >>> Waiting... " + count); Thread.Sleep(100); } diff --git a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs b/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs index 3c8e0efa05..2fe90265c3 100755 --- a/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs +++ b/OpenSim/Region/CoreModules/Framework/Statistics/Logging/LogWriter.cs @@ -136,7 +136,7 @@ namespace OpenSim.Region.CoreModules.Framework.Statistics.Logging { lock (m_logFileWriteLock) { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; if (m_logFile == null || now > m_logFileEndTime) { if (m_logFile != null) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 92d0aff298..6b031ae0fe 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -2990,8 +2990,22 @@ namespace OpenSim.Region.Framework.Scenes sp.IsChildAgent = false; sp.IsLoggingIn = true; + // We leave a 5 second pause before attempting to rez attachments to avoid a clash with + // version 3 viewers that maybe doing their own attachment rezzing related to their current + // outfit folder on startup. If these operations do clash, then the symptoms are invisible + // attachments until one zooms in on the avatar. + // + // We do not pause if we are launching on the same thread anyway in order to avoid pointlessly + // delaying any attachment related regression tests. if (AttachmentsModule != null) - Util.FireAndForget(delegate(object o) { AttachmentsModule.RezAttachments(sp); }); + Util.FireAndForget( + o => + { + if (Util.FireAndForgetMethod != FireAndForgetMethod.None) + Thread.Sleep(5000); + + AttachmentsModule.RezAttachments(sp); + }); } } else diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index d041d8bd95..5ed7b67c49 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -1529,6 +1529,15 @@ namespace OpenSim.Region.Framework.Scenes } + // XXX: If we force an update here, then multiple attachments do appear correctly on a destination region + // If we do it a little bit earlier (e.g. when converting the child to a root agent) then this does not work. + // This may be due to viewer code or it may be something we're not doing properly simulator side. + lock (m_attachments) + { + foreach (SceneObjectGroup sog in m_attachments) + sog.ScheduleGroupForFullUpdate(); + } + // m_log.DebugFormat( // "[SCENE PRESENCE]: Completing movement of {0} into region {1} took {2}ms", // client.Name, Scene.RegionInfo.RegionName, (DateTime.Now - startTime).Milliseconds); diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs index 81a2fcc74d..8775949ff4 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs @@ -94,7 +94,12 @@ namespace OpenSim.Region.Framework.Scenes.Tests // SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA, eqmA); SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); - ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA, sh.SceneManager); + List destinationTestClients = new List(); + EntityTransferHelpers.SetUpInformClientOfNeighbour(tc, destinationTestClients); + + ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, tc, acd, sh.SceneManager); originalSp.AbsolutePosition = new Vector3(128, 32, 10); // originalSp.Flying = true; diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs index 8dd1f3d5c3..de4458dde3 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs @@ -26,7 +26,10 @@ */ using System; -using System.Reflection; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; using Nini.Config; using NUnit.Framework; using OpenMetaverse; @@ -40,8 +43,6 @@ using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; using OpenSim.Region.CoreModules.World.Permissions; using OpenSim.Tests.Common; using OpenSim.Tests.Common.Mock; -using System.IO; -using System.Text; namespace OpenSim.Region.Framework.Scenes.Tests { @@ -68,7 +69,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] - public void TestSameRegionTeleport() + public void TestSameRegion() { TestHelpers.InMethod(); // log4net.Config.XmlConfigurator.Configure(); @@ -106,10 +107,10 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] - public void TestSameSimulatorSeparatedRegionsTeleport() + public void TestSameSimulatorIsolatedRegions() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); UUID userId = TestHelpers.ParseTail(0x1); @@ -141,9 +142,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); sp.AbsolutePosition = new Vector3(30, 31, 32); - // XXX: A very nasty hack to tell the client about the destination scene without having to crank the whole - // UDP stack (?) -// ((TestClient)sp.ControllingClient).TeleportTargetScene = sceneB; + List destinationTestClients = new List(); + EntityTransferHelpers.SetUpInformClientOfNeighbour((TestClient)sp.ControllingClient, destinationTestClients); sceneA.RequestTeleportLocation( sp.ControllingClient, @@ -152,7 +152,10 @@ namespace OpenSim.Region.Framework.Scenes.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)sp.ControllingClient).CompleteTeleportClientSide(); + // SetupInformClientOfNeighbour() will have handled the callback into the target scene to setup the child + // agent. This call will now complete the movement of the user into the destination and upgrade the agent + // from child to root. + destinationTestClients[0].CompleteMovement(); Assert.That(sceneA.GetScenePresence(userId), Is.Null); @@ -177,7 +180,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// Test teleport procedures when the target simulator returns false when queried about access. /// [Test] - public void TestSameSimulatorSeparatedRegionsQueryAccessFails() + public void TestSameSimulatorIsolatedRegions_DeniedOnQueryAccess() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -261,7 +264,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// Test teleport procedures when the target simulator create agent step is refused. /// [Test] - public void TestSameSimulatorSeparatedRegionsCreateAgentFails() + public void TestSameSimulatorIsolatedRegions_DeniedOnCreateAgent() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -333,12 +336,100 @@ namespace OpenSim.Region.Framework.Scenes.Tests // TestHelpers.DisableLogging(); } + /// + /// Test teleport when the destination region does not process (or does not receive) the connection attempt + /// from the viewer. + /// + /// + /// This could be quite a common case where the source region can connect to a remove destination region + /// (for CreateAgent) but the viewer cannot reach the destination region due to network issues. + /// [Test] - public void TestSameSimulatorNeighbouringRegionsTeleport() + public void TestSameSimulatorIsolatedRegions_DestinationDidNotProcessViewerConnection() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); + UUID userId = TestHelpers.ParseTail(0x1); + Vector3 preTeleportPosition = new Vector3(30, 31, 32); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("EntityTransferModule", etmA.Name); + config.Configs["Modules"].Set("SimulationServices", lscm.Name); + + config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + config.Configs["EntityTransfer"].Set("wait_for_callback", false); + +// config.AddConfig("Startup"); +// config.Configs["Startup"].Set("serverside_object_permissions", true); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA ); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new object[] { new PermissionsModule(), etmB }); + + // Shared scene modules + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); + sp.AbsolutePosition = preTeleportPosition; + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // FIXME: Not setting up InformClientOfNeighbour on the TestClient means that it does not initiate + // communication with the destination region. But this is a very non-obvious way of doing it - really we + // should be forced to expicitly set this up. + + Assert.That(sceneB.GetScenePresence(userId), Is.Null); + + ScenePresence sceneASp = sceneA.GetScenePresence(userId); + Assert.That(sceneASp, Is.Not.Null); + Assert.That(sceneASp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneA.RegionInfo.RegionName)); + Assert.That(sceneASp.AbsolutePosition, Is.EqualTo(preTeleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + + [Test] + public void TestSameSimulatorNeighbouringRegions() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + UUID userId = TestHelpers.ParseTail(0x1); EntityTransferModule etmA = new EntityTransferModule(); @@ -366,10 +457,14 @@ namespace OpenSim.Region.Framework.Scenes.Tests Vector3 teleportPosition = new Vector3(10, 11, 12); Vector3 teleportLookAt = new Vector3(20, 21, 22); - ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, userId, sh.SceneManager); - originalSp.AbsolutePosition = new Vector3(30, 31, 32); + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA, sh.SceneManager); + List destinationTestClients = new List(); + EntityTransferHelpers.SetUpInformClientOfNeighbour(tc, destinationTestClients); + + ScenePresence beforeSceneASp = SceneHelpers.AddScenePresence(sceneA, tc, acd, sh.SceneManager); + beforeSceneASp.AbsolutePosition = new Vector3(30, 31, 32); - ScenePresence beforeSceneASp = sceneA.GetScenePresence(userId); Assert.That(beforeSceneASp, Is.Not.Null); Assert.That(beforeSceneASp.IsChildAgent, Is.False); @@ -377,10 +472,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(beforeSceneBSp, Is.Not.Null); Assert.That(beforeSceneBSp.IsChildAgent, Is.True); - // XXX: A very nasty hack to tell the client about the destination scene without having to crank the whole - // UDP stack (?) -// ((TestClient)beforeSceneASp.ControllingClient).TeleportTargetScene = sceneB; - + // In this case, we will not receieve a second InformClientOfNeighbour since the viewer already knows + // about the neighbour region it is teleporting to. sceneA.RequestTeleportLocation( beforeSceneASp.ControllingClient, sceneB.RegionInfo.RegionHandle, @@ -388,7 +481,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests teleportLookAt, (uint)TeleportFlags.ViaLocation); - ((TestClient)beforeSceneASp.ControllingClient).CompleteTeleportClientSide(); + destinationTestClients[0].CompleteMovement(); ScenePresence afterSceneASp = sceneA.GetScenePresence(userId); Assert.That(afterSceneASp, Is.Not.Null); diff --git a/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs index d97e3b3cb8..0333747fff 100644 --- a/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs +++ b/OpenSim/Region/OptionalModules/Avatar/Attachments/AttachmentsCommandModule.cs @@ -176,16 +176,13 @@ namespace OpenSim.Region.OptionalModules.Avatar.Attachments // " {0,-36} {1,-10} {2,-36} {3,-14} {4,-15}\n", // attachmentObject.Name, attachmentObject.LocalId, attachmentObject.FromItemID, // (AttachmentPoint)attachmentObject.AttachmentPoint, attachmentObject.RootPart.AttachedPos); - ct.Rows.Add( - new ConsoleDisplayTableRow( - new List() - { - attachmentObject.Name, - attachmentObject.LocalId.ToString(), - attachmentObject.FromItemID.ToString(), - ((AttachmentPoint)attachmentObject.AttachmentPoint).ToString(), - attachmentObject.RootPart.AttachedPos.ToString() - })); + + ct.AddRow( + attachmentObject.Name, + attachmentObject.LocalId, + attachmentObject.FromItemID, + ((AttachmentPoint)attachmentObject.AttachmentPoint), + attachmentObject.RootPart.AttachedPos); // } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs b/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs index 3a27d2c605..77ea3edae2 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs @@ -286,7 +286,7 @@ public override void SetShapeCollisionMargin(BulletShape shape, float margin) { BulletShapeUnman shapeu = shape as BulletShapeUnman; if (shapeu != null && shapeu.HasPhysicalShape) - BSAPICPP.SetShapeCollisionMargin2(shapeu.ptr, margin); + BSAPICPP.SetShapeCollisionMargin(shapeu.ptr, margin); } public override BulletShape BuildCapsuleShape(BulletWorld world, float radius, float height, Vector3 scale) @@ -1420,7 +1420,7 @@ public static extern IntPtr BuildNativeShape2(IntPtr world, ShapeData shapeData) public static extern bool IsNativeShape2(IntPtr shape); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] -public static extern void SetShapeCollisionMargin2(IntPtr shape, float margin); +public static extern void SetShapeCollisionMargin(IntPtr shape, float margin); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern IntPtr BuildCapsuleShape2(IntPtr world, float radius, float height, Vector3 scale); diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs index 38596fa012..5549984d65 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs @@ -1201,8 +1201,9 @@ namespace OpenSim.Region.Physics.BulletSPlugin VehicleAddForce(appliedGravity); - VDetailLog("{0}, MoveLinear,applyGravity,vehGrav={1},collid={2},appliedForce={3}", - Prim.LocalID, m_VehicleGravity, Prim.IsColliding, appliedGravity); + VDetailLog("{0}, MoveLinear,applyGravity,vehGrav={1},collid={2},fudge={3},mass={4},appliedForce={3}", + Prim.LocalID, m_VehicleGravity, + Prim.IsColliding, BSParam.VehicleGroundGravityFudge, m_vehicleMass, appliedGravity); } // ======================================================================= diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs index 77bdacb458..4d89a88778 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs @@ -39,6 +39,20 @@ public static class BSParam { private static string LogHeader = "[BULLETSIM PARAMETERS]"; + // Tuning notes: + // From: http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=6575 + // Contact points can be added even if the distance is positive. The constraint solver can deal with + // contacts with positive distances as well as negative (penetration). Contact points are discarded + // if the distance exceeds a certain threshold. + // Bullet has a contact processing threshold and a contact breaking threshold. + // If the distance is larger than the contact breaking threshold, it will be removed after one frame. + // If the distance is larger than the contact processing threshold, the constraint solver will ignore it. + + // This is separate/independent from the collision margin. The collision margin increases the object a bit + // to improve collision detection performance and accuracy. + // =================== + // From: + // Level of Detail values kept as float because that's what the Meshmerizer wants public static float MeshLOD { get; private set; } public static float MeshCircularLOD { get; private set; } @@ -74,9 +88,11 @@ public static class BSParam public static bool ShouldRemoveZeroWidthTriangles { get; private set; } public static float TerrainImplementation { get; private set; } + public static int TerrainMeshMagnification { get; private set; } public static float TerrainFriction { get; private set; } public static float TerrainHitFraction { get; private set; } public static float TerrainRestitution { get; private set; } + public static float TerrainContactProcessingThreshold { get; private set; } public static float TerrainCollisionMargin { get; private set; } public static float DefaultFriction { get; private set; } @@ -446,6 +462,10 @@ public static class BSParam (float)BSTerrainPhys.TerrainImplementation.Mesh, (s) => { return TerrainImplementation; }, (s,v) => { TerrainImplementation = v; } ), + new ParameterDefn("TerrainMeshMagnification", "Number of times the 256x256 heightmap is multiplied to create the terrain mesh" , + 3, + (s) => { return TerrainMeshMagnification; }, + (s,v) => { TerrainMeshMagnification = v; } ), new ParameterDefn("TerrainFriction", "Factor to reduce movement against terrain surface" , 0.3f, (s) => { return TerrainFriction; }, @@ -458,6 +478,10 @@ public static class BSParam 0f, (s) => { return TerrainRestitution; }, (s,v) => { TerrainRestitution = v; /* TODO: set on real terrain */ } ), + new ParameterDefn("TerrainContactProcessingThreshold", "Distance from terrain to stop processing collisions" , + 0.0f, + (s) => { return TerrainContactProcessingThreshold; }, + (s,v) => { TerrainContactProcessingThreshold = v; /* TODO: set on real terrain */ } ), new ParameterDefn("TerrainCollisionMargin", "Margin where collision checking starts" , 0.08f, (s) => { return TerrainCollisionMargin; }, diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs index a465613b8a..2cbbe9a3fd 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs @@ -947,9 +947,9 @@ public class BSPrim : BSPhysObject ZeroMotion(true); // Set various physical properties so other object interact properly - MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, false); PhysicsScene.PE.SetFriction(PhysBody, Friction); PhysicsScene.PE.SetRestitution(PhysBody, Restitution); + PhysicsScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); // Mass is zero which disables a bunch of physics stuff in Bullet UpdatePhysicalMassProperties(0f, false); diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs index 2e54a93e7c..05c147d93f 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs @@ -687,7 +687,7 @@ public sealed class BSShapeCollection : IDisposable } else { - PhysicsScene.Logger.ErrorFormat("{0} All mesh triangles degenerate. Prim {1} at {2} in {3}", + PhysicsScene.Logger.DebugFormat("{0} All mesh triangles degenerate. Prim {1} at {2} in {3}", LogHeader, prim.PhysObjectName, prim.RawPosition, PhysicsScene.Name); } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs index e8040d856b..a60946dbbd 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs @@ -263,6 +263,7 @@ public sealed class BSTerrainManager : IDisposable if (MegaRegionParentPhysicsScene == null) { + // This terrain is not part of the mega-region scheme. Create vanilla terrain. BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords); m_terrains.Add(terrainRegionBase, newTerrainPhys); diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs index 57a5ff2a6f..a9cd8a1578 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs @@ -76,11 +76,26 @@ public sealed class BSTerrainMesh : BSTerrainPhys m_sizeX = (int)(maxCoords.X - minCoords.X); m_sizeY = (int)(maxCoords.Y - minCoords.Y); - if (!BSTerrainMesh.ConvertHeightmapToMesh(PhysicsScene, initialMap, - m_sizeX, m_sizeY, - (float)m_sizeX, (float)m_sizeY, - Vector3.Zero, 1.0f, - out indicesCount, out indices, out verticesCount, out vertices)) + bool meshCreationSuccess = false; + if (BSParam.TerrainMeshMagnification == 1) + { + // If a magnification of one, use the old routine that is tried and true. + meshCreationSuccess = BSTerrainMesh.ConvertHeightmapToMesh(PhysicsScene, + initialMap, m_sizeX, m_sizeY, // input size + Vector3.Zero, // base for mesh + out indicesCount, out indices, out verticesCount, out vertices); + } + else + { + // Other magnifications use the newer routine + meshCreationSuccess = BSTerrainMesh.ConvertHeightmapToMesh2(PhysicsScene, + initialMap, m_sizeX, m_sizeY, // input size + BSParam.TerrainMeshMagnification, + physicsScene.TerrainManager.DefaultRegionSize, + Vector3.Zero, // base for mesh + out indicesCount, out indices, out verticesCount, out vertices); + } + if (!meshCreationSuccess) { // DISASTER!! PhysicsScene.DetailLog("{0},BSTerrainMesh.create,failedConversionOfHeightmap", ID); @@ -88,6 +103,7 @@ public sealed class BSTerrainMesh : BSTerrainPhys // Something is very messed up and a crash is in our future. return; } + PhysicsScene.DetailLog("{0},BSTerrainMesh.create,meshed,indices={1},indSz={2},vertices={3},vertSz={4}", ID, indicesCount, indices.Length, verticesCount, vertices.Length); @@ -112,11 +128,13 @@ public sealed class BSTerrainMesh : BSTerrainPhys // Something is very messed up and a crash is in our future. return; } + physicsScene.PE.SetShapeCollisionMargin(m_terrainShape, BSParam.TerrainCollisionMargin); // Set current terrain attributes PhysicsScene.PE.SetFriction(m_terrainBody, BSParam.TerrainFriction); PhysicsScene.PE.SetHitFraction(m_terrainBody, BSParam.TerrainHitFraction); PhysicsScene.PE.SetRestitution(m_terrainBody, BSParam.TerrainRestitution); + PhysicsScene.PE.SetContactProcessingThreshold(m_terrainBody, BSParam.TerrainContactProcessingThreshold); PhysicsScene.PE.SetCollisionFlags(m_terrainBody, CollisionFlags.CF_STATIC_OBJECT); // Static objects are not very massive. @@ -184,9 +202,7 @@ public sealed class BSTerrainMesh : BSTerrainPhys // Return 'true' if successfully created. public static bool ConvertHeightmapToMesh( BSScene physicsScene, float[] heightMap, int sizeX, int sizeY, // parameters of incoming heightmap - float extentX, float extentY, // zero based range for output vertices Vector3 extentBase, // base to be added to all vertices - float magnification, // number of vertices to create between heightMap coords out int indicesCountO, out int[] indicesO, out int verticesCountO, out float[] verticesO) { @@ -207,17 +223,15 @@ public sealed class BSTerrainMesh : BSTerrainPhys // of the heightmap. try { - // One vertice per heightmap value plus the vertices off the top and bottom edge. + // One vertice per heightmap value plus the vertices off the side and bottom edge. int totalVertices = (sizeX + 1) * (sizeY + 1); vertices = new float[totalVertices * 3]; int totalIndices = sizeX * sizeY * 6; indices = new int[totalIndices]; - float magX = (float)sizeX / extentX; - float magY = (float)sizeY / extentY; if (physicsScene != null) - physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh,totVert={1},totInd={2},extentBase={3},magX={4},magY={5}", - BSScene.DetailLogZero, totalVertices, totalIndices, extentBase, magX, magY); + physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh,totVert={1},totInd={2},extentBase={3}", + BSScene.DetailLogZero, totalVertices, totalIndices, extentBase); float minHeight = float.MaxValue; // Note that sizeX+1 vertices are created since there is land between this and the next region. for (int yy = 0; yy <= sizeY; yy++) @@ -230,8 +244,8 @@ public sealed class BSTerrainMesh : BSTerrainPhys if (xx == sizeX) offset -= 1; float height = heightMap[offset]; minHeight = Math.Min(minHeight, height); - vertices[verticesCount + 0] = (float)xx * magX + extentBase.X; - vertices[verticesCount + 1] = (float)yy * magY + extentBase.Y; + vertices[verticesCount + 0] = (float)xx + extentBase.X; + vertices[verticesCount + 1] = (float)yy + extentBase.Y; vertices[verticesCount + 2] = height + extentBase.Z; verticesCount += 3; } @@ -270,5 +284,158 @@ public sealed class BSTerrainMesh : BSTerrainPhys return ret; } + + private class HeightMapGetter + { + private float[] m_heightMap; + private int m_sizeX; + private int m_sizeY; + public HeightMapGetter(float[] pHeightMap, int pSizeX, int pSizeY) + { + m_heightMap = pHeightMap; + m_sizeX = pSizeX; + m_sizeY = pSizeY; + } + // The heightmap is extended as an infinite plane at the last height + public float GetHeight(int xx, int yy) + { + int offset = 0; + // Extend the height with the height from the last row or column + if (yy >= m_sizeY) + if (xx >= m_sizeX) + offset = (m_sizeY - 1) * m_sizeX + (m_sizeX - 1); + else + offset = (m_sizeY - 1) * m_sizeX + xx; + else + if (xx >= m_sizeX) + offset = yy * m_sizeX + (m_sizeX - 1); + else + offset = yy * m_sizeX + xx; + + return m_heightMap[offset]; + } + } + + // Convert the passed heightmap to mesh information suitable for CreateMeshShape2(). + // Version that handles magnification. + // Return 'true' if successfully created. + public static bool ConvertHeightmapToMesh2( BSScene physicsScene, + float[] heightMap, int sizeX, int sizeY, // parameters of incoming heightmap + int magnification, // number of vertices per heighmap step + Vector3 extent, // dimensions of the output mesh + Vector3 extentBase, // base to be added to all vertices + out int indicesCountO, out int[] indicesO, + out int verticesCountO, out float[] verticesO) + { + bool ret = false; + + int indicesCount = 0; + int verticesCount = 0; + int[] indices = new int[0]; + float[] vertices = new float[0]; + + HeightMapGetter hmap = new HeightMapGetter(heightMap, sizeX, sizeY); + + // The vertices dimension of the output mesh + int meshX = sizeX * magnification; + int meshY = sizeY * magnification; + // The output size of one mesh step + float meshXStep = extent.X / meshX; + float meshYStep = extent.Y / meshY; + + // Create an array of vertices that is meshX+1 by meshY+1 (note the loop + // from zero to <= meshX). The triangle indices are then generated as two triangles + // per heightmap point. There are meshX by meshY of these squares. The extra row and + // column of vertices are used to complete the triangles of the last row and column + // of the heightmap. + try + { + // Vertices for the output heightmap plus one on the side and bottom to complete triangles + int totalVertices = (meshX + 1) * (meshY + 1); + vertices = new float[totalVertices * 3]; + int totalIndices = meshX * meshY * 6; + indices = new int[totalIndices]; + + if (physicsScene != null) + physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,inSize={1},outSize={2},totVert={3},totInd={4},extentBase={5}", + BSScene.DetailLogZero, new Vector2(sizeX, sizeY), new Vector2(meshX, meshY), + totalVertices, totalIndices, extentBase); + + float minHeight = float.MaxValue; + // Note that sizeX+1 vertices are created since there is land between this and the next region. + // Loop through the output vertices and compute the mediun height in between the input vertices + for (int yy = 0; yy <= meshY; yy++) + { + for (int xx = 0; xx <= meshX; xx++) // Hint: the "<=" means we go around sizeX + 1 times + { + float offsetY = (float)yy * (float)sizeY / (float)meshY; // The Y that is closest to the mesh point + int stepY = (int)offsetY; + float fractionalY = offsetY - (float)stepY; + float offsetX = (float)xx * (float)sizeX / (float)meshX; // The X that is closest to the mesh point + int stepX = (int)offsetX; + float fractionalX = offsetX - (float)stepX; + + // physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,xx={1},yy={2},offX={3},stepX={4},fractX={5},offY={6},stepY={7},fractY={8}", + // BSScene.DetailLogZero, xx, yy, offsetX, stepX, fractionalX, offsetY, stepY, fractionalY); + + // get the four corners of the heightmap square the mesh point is in + float heightUL = hmap.GetHeight(stepX , stepY ); + float heightUR = hmap.GetHeight(stepX + 1, stepY ); + float heightLL = hmap.GetHeight(stepX , stepY + 1); + float heightLR = hmap.GetHeight(stepX + 1, stepY + 1); + + // bilinear interplolation + float height = heightUL * (1 - fractionalX) * (1 - fractionalY) + + heightUR * fractionalX * (1 - fractionalY) + + heightLL * (1 - fractionalX) * fractionalY + + heightLR * fractionalX * fractionalY; + + // physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,heightUL={1},heightUR={2},heightLL={3},heightLR={4},heightMap={5}", + // BSScene.DetailLogZero, heightUL, heightUR, heightLL, heightLR, height); + + minHeight = Math.Min(minHeight, height); + + vertices[verticesCount + 0] = (float)xx * meshXStep + extentBase.X; + vertices[verticesCount + 1] = (float)yy * meshYStep + extentBase.Y; + vertices[verticesCount + 2] = height + extentBase.Z; + verticesCount += 3; + } + } + // The number of vertices generated + verticesCount /= 3; + + // Loop through all the heightmap squares and create indices for the two triangles for that square + for (int yy = 0; yy < meshY; yy++) + { + for (int xx = 0; xx < meshX; xx++) + { + int offset = yy * (meshX + 1) + xx; + // Each vertices is presumed to be the upper left corner of a box of two triangles + indices[indicesCount + 0] = offset; + indices[indicesCount + 1] = offset + 1; + indices[indicesCount + 2] = offset + meshX + 1; // accounting for the extra column + indices[indicesCount + 3] = offset + 1; + indices[indicesCount + 4] = offset + meshX + 2; + indices[indicesCount + 5] = offset + meshX + 1; + indicesCount += 6; + } + } + + ret = true; + } + catch (Exception e) + { + if (physicsScene != null) + physicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh. For={1}/{2}, e={3}", + LogHeader, physicsScene.RegionName, extentBase, e); + } + + indicesCountO = indicesCount; + indicesO = indices; + verticesCountO = verticesCount; + verticesO = vertices; + + return ret; + } } } diff --git a/OpenSim/Tests/Common/Helpers/EntityTransferHelpers.cs b/OpenSim/Tests/Common/Helpers/EntityTransferHelpers.cs new file mode 100644 index 0000000000..6cc7ff259d --- /dev/null +++ b/OpenSim/Tests/Common/Helpers/EntityTransferHelpers.cs @@ -0,0 +1,91 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text; +using log4net; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Tests.Common +{ + public static class EntityTransferHelpers + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Set up correct handling of the InformClientOfNeighbour call from the source region that triggers the + /// viewer to setup a connection with the destination region. + /// + /// + /// + /// A list that will be populated with any TestClients set up in response to + /// being informed about a destination region. + /// + public static void SetUpInformClientOfNeighbour(TestClient tc, List neighbourTcs) + { + // XXX: Confusingly, this is also used for non-neighbour notification (as in teleports that do not use the + // event queue). + + tc.OnTestClientInformClientOfNeighbour += (neighbourHandle, neighbourExternalEndPoint) => + { + uint x, y; + Utils.LongToUInts(neighbourHandle, out x, out y); + x /= Constants.RegionSize; + y /= Constants.RegionSize; + + m_log.DebugFormat( + "[TEST CLIENT]: Processing inform client of neighbour located at {0},{1} at {2}", + x, y, neighbourExternalEndPoint); + + // In response to this message, we are going to make a teleport to the scene we've previous been told + // about by test code (this needs to be improved). + AgentCircuitData newAgent = tc.RequestClientInfo(); + + Scene neighbourScene; + SceneManager.Instance.TryGetScene(x, y, out neighbourScene); + + TestClient neighbourTc = new TestClient(newAgent, neighbourScene, SceneManager.Instance); + neighbourTcs.Add(neighbourTc); + neighbourScene.AddNewClient(neighbourTc, PresenceType.User); + }; + } + } +} \ No newline at end of file diff --git a/OpenSim/Tests/Common/Helpers/SceneHelpers.cs b/OpenSim/Tests/Common/Helpers/SceneHelpers.cs index dc20f13555..bdd90939e7 100644 --- a/OpenSim/Tests/Common/Helpers/SceneHelpers.cs +++ b/OpenSim/Tests/Common/Helpers/SceneHelpers.cs @@ -531,6 +531,31 @@ namespace OpenSim.Tests.Common /// /// public static ScenePresence AddScenePresence(Scene scene, AgentCircuitData agentData, SceneManager sceneManager) + { + return AddScenePresence(scene, new TestClient(agentData, scene, sceneManager), agentData, sceneManager); + } + + /// + /// Add a root agent. + /// + /// + /// This function + /// + /// 1) Tells the scene that an agent is coming. Normally, the login service (local if standalone, from the + /// userserver if grid) would give initial login data back to the client and separately tell the scene that the + /// agent was coming. + /// + /// 2) Connects the agent with the scene + /// + /// This function performs actions equivalent with notifying the scene that an agent is + /// coming and then actually connecting the agent to the scene. The one step missed out is the very first + /// + /// + /// + /// + /// + public static ScenePresence AddScenePresence( + Scene scene, IClientAPI client, AgentCircuitData agentData, SceneManager sceneManager) { // We emulate the proper login sequence here by doing things in four stages @@ -541,7 +566,7 @@ namespace OpenSim.Tests.Common lpsc.m_PresenceService.LoginAgent(agentData.AgentID.ToString(), agentData.SessionID, agentData.SecureSessionID); // Stages 1 & 2 - ScenePresence sp = IntroduceClientToScene(scene, sceneManager, agentData, TeleportFlags.ViaLogin); + ScenePresence sp = IntroduceClientToScene(scene, client, agentData, TeleportFlags.ViaLogin); // Stage 3: Complete the entrance into the region. This converts the child agent into a root agent. sp.CompleteMovement(sp.ControllingClient, true); @@ -558,11 +583,11 @@ namespace OpenSim.Tests.Common /// neighbours and where no teleporting takes place. /// /// - /// /// /// private static ScenePresence IntroduceClientToScene( - Scene scene, SceneManager sceneManager, AgentCircuitData agentData, TeleportFlags tf) + Scene scene, IClientAPI client, AgentCircuitData agentData, TeleportFlags tf) { string reason; @@ -571,10 +596,9 @@ namespace OpenSim.Tests.Common Console.WriteLine("NewUserConnection failed: " + reason); // Stage 2: add the new client as a child agent to the scene - TestClient client = new TestClient(agentData, scene, sceneManager); scene.AddNewClient(client, PresenceType.User); - return scene.GetScenePresence(agentData.AgentID); + return scene.GetScenePresence(client.AgentId); } public static ScenePresence AddChildScenePresence(Scene scene, UUID agentId) @@ -583,7 +607,8 @@ namespace OpenSim.Tests.Common acd.child = true; // XXX: ViaLogin may not be correct for child agents - return IntroduceClientToScene(scene, null, acd, TeleportFlags.ViaLogin); + TestClient client = new TestClient(acd, scene, null); + return IntroduceClientToScene(scene, client, acd, TeleportFlags.ViaLogin); } /// diff --git a/OpenSim/Tests/Common/Mock/TestClient.cs b/OpenSim/Tests/Common/Mock/TestClient.cs index e892a53889..4a1380d922 100644 --- a/OpenSim/Tests/Common/Mock/TestClient.cs +++ b/OpenSim/Tests/Common/Mock/TestClient.cs @@ -46,8 +46,6 @@ namespace OpenSim.Tests.Common.Mock EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.AutoReset, "Crossing"); - private TestClient TeleportSceneClient; - private Scene m_scene; private SceneManager m_sceneManager; @@ -60,7 +58,9 @@ namespace OpenSim.Tests.Common.Mock public List SentImagePacketPackets { get; private set; } public List SentImageNotInDatabasePackets { get; private set; } + // Test client specific events - for use by tests to implement some IClientAPI behaviour. public event Action OnReceivedMoveAgentIntoRegion; + public event Action OnTestClientInformClientOfNeighbour; // disable warning: public events, part of the public API #pragma warning disable 67 @@ -604,23 +604,8 @@ namespace OpenSim.Tests.Common.Mock public virtual void InformClientOfNeighbour(ulong neighbourHandle, IPEndPoint neighbourExternalEndPoint) { - m_log.DebugFormat("[TEST CLIENT]: Processing inform client of neighbour"); - - // In response to this message, we are going to make a teleport to the scene we've previous been told - // about by test code (this needs to be improved). - AgentCircuitData newAgent = RequestClientInfo(); - - // Stage 2: add the new client as a child agent to the scene - uint x, y; - Utils.LongToUInts(neighbourHandle, out x, out y); - x /= Constants.RegionSize; - y /= Constants.RegionSize; - - Scene neighbourScene; - m_sceneManager.TryGetScene(x, y, out neighbourScene); - - TeleportSceneClient = new TestClient(newAgent, neighbourScene, m_sceneManager); - neighbourScene.AddNewClient(TeleportSceneClient, PresenceType.User); + if (OnTestClientInformClientOfNeighbour != null) + OnTestClientInformClientOfNeighbour(neighbourHandle, neighbourExternalEndPoint); } public virtual void SendRegionTeleport(ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, @@ -635,12 +620,6 @@ namespace OpenSim.Tests.Common.Mock // CompleteTeleportClientSide(); } - public void CompleteTeleportClientSide() - { - TeleportSceneClient.CompleteMovement(); - //TeleportTargetScene.AgentCrossing(newAgent.AgentID, new Vector3(90, 90, 90), false); - } - public virtual void SendTeleportFailed(string reason) { m_log.DebugFormat("[TEST CLIENT]: Teleport failed with reason {0}", reason);