From 8510f57ad48db5f97dacc2a9be63c64e62477d14 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sun, 17 Mar 2013 18:44:09 -0700 Subject: [PATCH 01/20] BulletSim: add terrain contact processing threshold parameter. Initialize contact processing threshold for static object as well as mesh terrain. --- .../Region/Physics/BulletSPlugin/BSParam.cs | 19 +++++++++++++++++++ .../Region/Physics/BulletSPlugin/BSPrim.cs | 2 +- .../Physics/BulletSPlugin/BSTerrainManager.cs | 1 + .../Physics/BulletSPlugin/BSTerrainMesh.cs | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs index 77bdacb458..cb0d92946a 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; } @@ -77,6 +91,7 @@ public static class BSParam 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; } @@ -458,6 +473,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/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..c9a75ae915 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs @@ -112,11 +112,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. From 8360223fedc5a5521878806f40bdb0c3244241cc Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Mon, 18 Mar 2013 23:58:21 -0700 Subject: [PATCH 02/20] BulletSim: code to generate a higher resolution terrain mesh. Parameter TerrainMeshMagnification controls number of vertices generated per heightmap point. Default is 3. --- .../Physics/BulletSPlugin/BSAPIUnman.cs | 4 +- .../Physics/BulletSPlugin/BSDynamics.cs | 5 +- .../Region/Physics/BulletSPlugin/BSParam.cs | 5 + .../Physics/BulletSPlugin/BSTerrainMesh.cs | 193 ++++++++++++++++-- 4 files changed, 189 insertions(+), 18 deletions(-) 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 cb0d92946a..4d89a88778 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs @@ -88,6 +88,7 @@ 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; } @@ -461,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; }, diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs index c9a75ae915..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); @@ -186,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) { @@ -209,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++) @@ -232,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; } @@ -272,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; + } } } From 1dd2d432f1b0221c6180f1367badbcc3d29c478d Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Tue, 19 Mar 2013 11:40:10 -0700 Subject: [PATCH 03/20] For those people collecting and comparing logfiles from servers scattered around the world, change LogWriter to use DateTime.UtcNow rather than DateTime.Now. --- .../CoreModules/Framework/Statistics/Logging/LogWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From c2e4f8aed5dee4a35679d646326537a15153f19d Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Tue, 19 Mar 2013 12:37:44 -0700 Subject: [PATCH 04/20] For the moment, disable the output of the 'scene' statistics in SimExtraStatsCollector and thus for the command 'show stats' because it is ugly and most of the information is already output in the formatted printout that appears before. --- OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); From 364816421985c052521cf7b444e124760c0a1025 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 19 Mar 2013 21:44:18 +0000 Subject: [PATCH 05/20] Prevent multiple instances of the same item ID being appended to an AvatarAppearance It looks like this was happening when AttachmentsModule.RezAttachments was doing a secondary set of each attachment to update with the asset ID (initially they only have the inventory ID). However, with multi-attach this was appending a second copy of the same attachment rather than updating the data that was already there. This commit requires both simulator and service to be updated. --- OpenSim/Framework/AvatarAppearance.cs | 78 ++++++++++++------- .../Avatar/Attachments/AttachmentsModule.cs | 4 +- .../AvatarFactory/AvatarFactoryModule.cs | 12 ++- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/OpenSim/Framework/AvatarAppearance.cs b/OpenSim/Framework/AvatarAppearance.cs index 95e9667f6b..494ae5e7a6 100644 --- a/OpenSim/Framework/AvatarAppearance.cs +++ b/OpenSim/Framework/AvatarAppearance.cs @@ -459,45 +459,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; @@ -547,6 +561,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/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs index 72ba3cf4ce..ad17aa9ab8 100644 --- a/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Attachments/AttachmentsModule.cs @@ -547,8 +547,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/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs index 00d1fd887c..ff5bf9f99c 100644 --- a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -326,7 +326,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); @@ -529,7 +529,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 @@ -538,6 +538,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. From 90b9121e66934795ab2d59f149e9e65eadc09e75 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Tue, 19 Mar 2013 17:15:24 -0700 Subject: [PATCH 06/20] BulletSim: change 'degenerate mesh' message from Error to Debug because there seem to be lots of sculpties with this problem while the condition really doesn't change region operation. --- OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs index 219372be65..b16bc10b9c 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs @@ -685,7 +685,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); } } From 3cb1c23554439dc1f9ea2911a27d16de986e2ffd Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 01:46:50 +0000 Subject: [PATCH 07/20] Force a viewer object update for attachments at the end of the final Scene.CompleteMovement() in order to make all multi-attachments appear on the destination region. For some reason, sending updates before this will not have this effect. This may be something related to some viewers (e.g. LL 3.3.4) or something OpenSimulator isn't getting quite right. --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 82bb759bae..6d96c936a5 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -1326,6 +1326,15 @@ namespace OpenSim.Region.Framework.Scenes friendsModule.SendFriendsOnlineIfNeeded(ControllingClient); } + // 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); From c0ff5635ba696d1701a6ce0356f247c69a7a52b9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 02:00:56 +0000 Subject: [PATCH 08/20] Fix "show attachments" command probably broken in commit addab12 (Wed Jan 2 21:38:00 2013) This break was not connected with the recent attachment code changes. --- .../Attachments/AttachmentsCommandModule.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) 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); // } } From 8de933ab07a0f9e8291d705a9a03d5b1ba60caa9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 02:09:25 +0000 Subject: [PATCH 09/20] Insert a short delay on the simulator side rezzing of attachments in order to fix viewer 3 issues if its own rezzing actions on login collide with the simulator side actions. This resolves issues (at least in my tests with LL 3.3.4) where this can make attachments invisible until one zooms in on the avatar. This doesn't affect version 1 viewers since this delay is shorter than the login delay. This doesn't increase the login time since this part of the process was already being performed asynchronously. This may be a temporary solution. --- OpenSim/Region/Framework/Scenes/Scene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 3c91c5bc9f..89eca32dd3 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -2832,7 +2832,7 @@ namespace OpenSim.Region.Framework.Scenes sp.IsChildAgent = false; if (AttachmentsModule != null) - Util.FireAndForget(delegate(object o) { AttachmentsModule.RezAttachments(sp); }); + Util.FireAndForget(o => { Thread.Sleep(5000); AttachmentsModule.RezAttachments(sp); }); } } else From 36651bed71ce1011c376078943a4fef7e8a9ada6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 23:01:16 +0000 Subject: [PATCH 10/20] On the later forms of teleport failure, tell the user if this was because viewer couldn't/didn't connect with destination or if destination didn't signal teleport completion. Also adds regression test for the case where the viewer couldn't connect with the destination region. Also refactoring of regression test support code associated with entity transfer in order to make this test possible and the code less obscure. --- .../Tests/AttachmentsModuleTests.cs | 10 +- .../EntityTransfer/EntityTransferModule.cs | 36 +++-- .../Tests/ScenePresenceCrossingTests.cs | 9 +- .../Tests/ScenePresenceTeleportTests.cs | 135 +++++++++++++++--- OpenSim/Tests/Common/Helpers/SceneHelpers.cs | 37 ++++- OpenSim/Tests/Common/Mock/TestClient.cs | 29 +--- 6 files changed, 185 insertions(+), 71 deletions(-) diff --git a/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs b/OpenSim/Region/CoreModules/Avatar/Attachments/Tests/AttachmentsModuleTests.cs index 4cd03da2c6..c8c594d99d 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/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 9b1b69aaa4..58a6654c6e 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -517,12 +517,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( @@ -583,6 +577,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)) { @@ -625,11 +620,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 @@ -640,6 +637,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); } } @@ -657,14 +657,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer //sp.ControllingClient.SendTeleportProgress(teleportFlags, "Updating agent..."); + // 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 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; } @@ -705,7 +708,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[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; } @@ -788,12 +791,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); 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/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs index 81a2fcc74d..b67b8b96e1 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs @@ -66,7 +66,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests public void TestCrossOnSameSimulator() { TestHelpers.InMethod(); -// TestHelpers.EnableLogging(); + TestHelpers.EnableLogging(); UUID userId = TestHelpers.ParseTail(0x1); @@ -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..d6bc3130a3 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 TestSameSimulatorSeparatedRegions() { 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 TestSameSimulatorSeparatedRegions_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 TestSameSimulatorSeparatedRegions_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 TestSameSimulatorSeparatedRegions_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/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 a448cc52b1..2d4fef16de 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 @@ -595,23 +595,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, @@ -626,12 +611,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); From 16af5b87f888821d60b8068dde4acac58a6066f7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 23:08:35 +0000 Subject: [PATCH 11/20] Add file missing from last commit 36651be --- .../Common/Helpers/EntityTransferHelpers.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 OpenSim/Tests/Common/Helpers/EntityTransferHelpers.cs 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 From b1cd1d917e25e34c2efda65ab52164269863da87 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 23:12:13 +0000 Subject: [PATCH 12/20] minor: don't bother with the pause before rezzing attachments if we are running regression tests (fire and forget calls launched on the same thread). Also adds code comments as to why this pause exists. --- OpenSim/Region/Framework/Scenes/Scene.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 89eca32dd3..c9c0662228 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -2831,8 +2831,22 @@ namespace OpenSim.Region.Framework.Scenes // XXX: This is convoluted. sp.IsChildAgent = false; + // 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(o => { Thread.Sleep(5000); AttachmentsModule.RezAttachments(sp); }); + Util.FireAndForget( + o => + { + if (Util.FireAndForgetMethod != FireAndForgetMethod.None) + Thread.Sleep(5000); + + AttachmentsModule.RezAttachments(sp); + }); } } else From 4de530af45c86a003676a10d6b037a5acec3646c Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Wed, 20 Mar 2013 23:13:12 +0000 Subject: [PATCH 13/20] minor: disable logging on regression TestCrossOnSameSimulator() that I accidentally left on a few commits ago. --- .../Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs index b67b8b96e1..8775949ff4 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceCrossingTests.cs @@ -66,7 +66,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests public void TestCrossOnSameSimulator() { TestHelpers.InMethod(); - TestHelpers.EnableLogging(); +// TestHelpers.EnableLogging(); UUID userId = TestHelpers.ParseTail(0x1); From 46c833810ccd9dd0c3273803e208dc85bb0860a9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 21 Mar 2013 00:46:08 +0000 Subject: [PATCH 14/20] On a teleport, lock m_agentsInTransit whilst we grab the value to check for completion just to be sure we're not using a thread cached version. --- .../EntityTransfer/EntityTransferStateMachine.cs | 9 ++++++++- .../Framework/Scenes/Tests/ScenePresenceTeleportTests.cs | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs index 24d81d9480..7314727cda 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs @@ -292,8 +292,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/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs index d6bc3130a3..de4458dde3 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceTeleportTests.cs @@ -107,7 +107,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests } [Test] - public void TestSameSimulatorSeparatedRegions() + public void TestSameSimulatorIsolatedRegions() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -180,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 TestSameSimulatorSeparatedRegions_DeniedOnQueryAccess() + public void TestSameSimulatorIsolatedRegions_DeniedOnQueryAccess() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -264,7 +264,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// Test teleport procedures when the target simulator create agent step is refused. /// [Test] - public void TestSameSimulatorSeparatedRegions_DeniedOnCreateAgent() + public void TestSameSimulatorIsolatedRegions_DeniedOnCreateAgent() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); @@ -345,7 +345,7 @@ namespace OpenSim.Region.Framework.Scenes.Tests /// (for CreateAgent) but the viewer cannot reach the destination region due to network issues. /// [Test] - public void TestSameSimulatorSeparatedRegions_DestinationDidNotProcessViewerConnection() + public void TestSameSimulatorIsolatedRegions_DestinationDidNotProcessViewerConnection() { TestHelpers.InMethod(); // TestHelpers.EnableLogging(); From e23a0dcc5dd5f2869fe0da39452e075cec34199b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 21 Mar 2013 23:37:23 +0000 Subject: [PATCH 15/20] minor: On teleport, signal a child agent before we send the viewer TeleportFinish in order to avoid a theoretical race condition when teleporting to a neighbour. If we do this after TeleportFinish, then it's possible for a neighbour destination to request the source to create a child agent whilst its still treated as root. This closes the original presence which we don't really want to do. This is probably okay (albeit with warnings on the console) but afaics there's no reason not to move the child agent signal. --- .../Framework/EntityTransfer/EntityTransferModule.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 58a6654c6e..82358d8c7b 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -686,6 +686,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); @@ -696,9 +701,6 @@ 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. @@ -731,8 +733,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)) From 476a7d3eee1afc42ac501ac265d63f8fe52e27a6 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 22 Mar 2013 00:03:10 +0000 Subject: [PATCH 16/20] Implement chat across region borders since we can tell if avatars in neighbouring regions are in range. --- OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs index 6d62ff0f92..9032dd4967 100644 --- a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs @@ -240,7 +240,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat { // This should use ForEachClient, but clients don't have a position. // If camera is moved into client, then camera position can be used - s.ForEachRootScenePresence( + s.ForEachScenePresence( delegate(ScenePresence presence) { if (TrySendChatMessage( From 7471bc77757ce0b294d04f0eb552dbc5ea749793 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 22 Mar 2013 01:00:13 +0000 Subject: [PATCH 17/20] At strategic points in the teleport process, if the client has simultaneously logged out then do not continue. This aims to reduce any side effects if the process tries to complete after the client has logged back in (e.g. it was delayed due to a slow destination region response). This introduces a new Aborting entity transfer state which signals that the teleport should be stopped but no compensating actions performed. --- .../EntityTransfer/EntityTransferModule.cs | 59 ++++++++++++++++++- .../EntityTransferStateMachine.cs | 20 ++++--- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 82358d8c7b..5713e886d9 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); @@ -590,6 +604,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } + Thread.Sleep(30000); + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) { m_log.DebugFormat( @@ -598,6 +614,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); @@ -657,12 +681,32 @@ 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)) { + 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}. Keeping avatar in source region.", sp.Name, finalDestination.RegionName, sp.Scene.RegionInfo.RegionName); @@ -677,7 +721,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; } @@ -706,6 +750,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // 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); @@ -772,7 +825,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); @@ -794,7 +847,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// 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( diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs index 7314727cda..fc029163b6 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; } From 1f17ef6d3ce8d3db22d4179cbbc1aee3503610ea Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 22 Mar 2013 01:06:56 +0000 Subject: [PATCH 18/20] Take out a testing sleep I accidentally left in the teleport code from last commit 7471bc7 --- .../Framework/EntityTransfer/EntityTransferModule.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 5713e886d9..26267e27a1 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -604,8 +604,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - Thread.Sleep(30000); - if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) { m_log.DebugFormat( From c4d9a23f2666d0284f2e65d1ee757bc17c3f56ad Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 22 Mar 2013 01:42:47 +0000 Subject: [PATCH 19/20] Following on from 476a7d3e (which wasn't enough), make chat across regions on different simulators work. Also resolves an issue of multiple chatting if the originating simulators had more than one region and they were neighbours --- .../CoreModules/Avatar/Chat/ChatModule.cs | 87 +++++++------------ 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs index 9032dd4967..58f747b7d7 100644 --- a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs @@ -50,7 +50,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat private int m_saydistance = 20; private int m_shoutdistance = 100; private int m_whisperdistance = 10; - private List m_scenes = new List(); internal object m_syncy = new object(); @@ -82,18 +81,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat public virtual void AddRegion(Scene scene) { - if (!m_enabled) return; + if (!m_enabled) + return; - lock (m_syncy) - { - if (!m_scenes.Contains(scene)) - { - m_scenes.Add(scene); - scene.EventManager.OnNewClient += OnNewClient; - scene.EventManager.OnChatFromWorld += OnChatFromWorld; - scene.EventManager.OnChatBroadcast += OnChatBroadcast; - } - } + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnChatFromWorld += OnChatFromWorld; + scene.EventManager.OnChatBroadcast += OnChatBroadcast; m_log.InfoFormat("[CHAT]: Initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, m_whisperdistance, m_saydistance, m_shoutdistance); @@ -105,18 +98,12 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat public virtual void RemoveRegion(Scene scene) { - if (!m_enabled) return; + if (!m_enabled) + return; - lock (m_syncy) - { - if (m_scenes.Contains(scene)) - { - scene.EventManager.OnNewClient -= OnNewClient; - scene.EventManager.OnChatFromWorld -= OnChatFromWorld; - scene.EventManager.OnChatBroadcast -= OnChatBroadcast; - m_scenes.Remove(scene); - } - } + scene.EventManager.OnNewClient -= OnNewClient; + scene.EventManager.OnChatFromWorld -= OnChatFromWorld; + scene.EventManager.OnChatBroadcast -= OnChatBroadcast; } public virtual void Close() @@ -191,7 +178,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat UUID ownerID = UUID.Zero; UUID targetID = c.TargetUUID; string message = c.Message; - IScene scene = c.Scene; + Scene scene = (Scene)c.Scene; Vector3 fromPos = c.Position; Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); @@ -201,13 +188,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat switch (sourceType) { case ChatSourceType.Agent: - if (!(scene is Scene)) - { - m_log.WarnFormat("[CHAT]: scene {0} is not a Scene object, cannot obtain scene presence for {1}", - scene.RegionInfo.RegionName, c.Sender.AgentId); - return; - } - ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId); + ScenePresence avatar = scene.GetScenePresence(c.Sender.AgentId); fromPos = avatar.AbsolutePosition; fromName = avatar.Name; fromID = c.Sender.AgentId; @@ -234,36 +215,33 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat HashSet receiverIDs = new HashSet(); - foreach (Scene s in m_scenes) + if (targetID == UUID.Zero) { - if (targetID == UUID.Zero) - { - // This should use ForEachClient, but clients don't have a position. - // If camera is moved into client, then camera position can be used - s.ForEachScenePresence( - delegate(ScenePresence presence) - { - if (TrySendChatMessage( - presence, fromPos, regionPos, fromID, ownerID, fromName, c.Type, message, sourceType, false)) - receiverIDs.Add(presence.UUID); - } - ); - } - else - { - // This is a send to a specific client eg from llRegionSayTo - // no need to check distance etc, jand send is as say - ScenePresence presence = s.GetScenePresence(targetID); - if (presence != null && !presence.IsChildAgent) + // This should use ForEachClient, but clients don't have a position. + // If camera is moved into client, then camera position can be used + scene.ForEachScenePresence( + delegate(ScenePresence presence) { if (TrySendChatMessage( - presence, fromPos, regionPos, fromID, ownerID, fromName, ChatTypeEnum.Say, message, sourceType, true)) + presence, fromPos, regionPos, fromID, ownerID, fromName, c.Type, message, sourceType, false)) receiverIDs.Add(presence.UUID); } + ); + } + else + { + // This is a send to a specific client eg from llRegionSayTo + // no need to check distance etc, jand send is as say + ScenePresence presence = scene.GetScenePresence(targetID); + if (presence != null && !presence.IsChildAgent) + { + if (TrySendChatMessage( + presence, fromPos, regionPos, fromID, ownerID, fromName, ChatTypeEnum.Say, message, sourceType, true)) + receiverIDs.Add(presence.UUID); } } - (scene as Scene).EventManager.TriggerOnChatToClients( + scene.EventManager.TriggerOnChatToClients( fromID, receiverIDs, message, c.Type, fromPos, fromName, sourceType, ChatAudibleLevel.Fully); } @@ -348,9 +326,6 @@ namespace OpenSim.Region.CoreModules.Avatar.Chat UUID fromAgentID, UUID ownerID, string fromName, ChatTypeEnum type, string message, ChatSourceType src, bool ignoreDistance) { - // don't send stuff to child agents - if (presence.IsChildAgent) return false; - Vector3 fromRegionPos = fromPos + regionPos; Vector3 toRegionPos = presence.AbsolutePosition + new Vector3(presence.Scene.RegionInfo.RegionLocX * Constants.RegionSize, From aba66c71122a2ca9a3c2fb807b360e9fff8b9026 Mon Sep 17 00:00:00 2001 From: Melanie Thielker Date: Sat, 23 Mar 2013 13:12:41 +0100 Subject: [PATCH 20/20] Make sharing errors not spew and let the cache retry the files --- .../CoreModules/Asset/FlotsamAssetCache.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index a0f1e8c560..ec388f40cc 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -369,7 +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 @@ -380,6 +380,8 @@ namespace OpenSim.Region.CoreModules.Asset asset = (AssetBase)bformatter.Deserialize(stream); m_DiskHits++; + + break; } catch (System.Runtime.Serialization.SerializationException e) { @@ -392,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.ErrorFormat( "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); + + break; } finally {