From e99a7d879ecf59737e9916a9eb229698ef866627 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 16 Jan 2014 00:05:04 +0000 Subject: [PATCH 01/49] Remove old IInterRegionComms and references. This hasn't been used since 2009 and was superseded by ISimulationService --- .../Simulation/LocalSimulationConnector.cs | 4 +- .../Simulation/RemoteSimulationConnector.cs | 4 +- .../Framework/Interfaces/IInterregionComms.cs | 111 ------------------ OpenSim/Region/Framework/Scenes/Scene.cs | 12 -- 4 files changed, 4 insertions(+), 127 deletions(-) delete mode 100644 OpenSim/Region/Framework/Interfaces/IInterregionComms.cs diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs index 678f3dc3a5..4dcb99f0ac 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/LocalSimulationConnector.cs @@ -174,7 +174,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation #endregion - #region ISimulation + #region ISimulationService public IScene GetScene(UUID regionId) { @@ -353,7 +353,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; } - #endregion /* IInterregionComms */ + #endregion #region Misc diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs index f45f560a96..cc014307af 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Simulation/RemoteSimulationConnector.cs @@ -146,7 +146,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation #endregion - #region IInterregionComms + #region ISimulationService public IScene GetScene(UUID regionId) { @@ -279,6 +279,6 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation return false; } - #endregion /* IInterregionComms */ + #endregion } } diff --git a/OpenSim/Region/Framework/Interfaces/IInterregionComms.cs b/OpenSim/Region/Framework/Interfaces/IInterregionComms.cs deleted file mode 100644 index 2d6287f87d..0000000000 --- a/OpenSim/Region/Framework/Interfaces/IInterregionComms.cs +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 OpenMetaverse; -using OpenSim.Framework; -using OpenSim.Region.Framework.Scenes; - -namespace OpenSim.Region.Framework.Interfaces -{ - public delegate bool ChildAgentUpdateReceived(AgentData data); - - public interface IInterregionCommsOut - { - #region Agents - - bool SendCreateChildAgent(ulong regionHandle, AgentCircuitData aCircuit, uint teleportFlags, out string reason); - - /// - /// Full child agent update. - /// - /// - /// - /// - bool SendChildAgentUpdate(ulong regionHandle, AgentData data); - - /// - /// Short child agent update, mostly for position. - /// - /// - /// - /// - bool SendChildAgentUpdate(ulong regionHandle, AgentPosition data); - - bool SendRetrieveRootAgent(ulong regionHandle, UUID id, out IAgentData agent); - - /// - /// Message from receiving region to departing region, telling it got contacted by the client. - /// When sent over REST, it invokes the opaque uri. - /// - /// - /// - /// - /// - bool SendReleaseAgent(ulong regionHandle, UUID id, string uri); - - /// - /// Close agent. - /// - /// - /// - /// - bool SendCloseAgent(ulong regionHandle, UUID id); - - #endregion Agents - - #region Objects - - /// - /// Create an object in the destination region. This message is used primarily for prim crossing. - /// - /// - /// - /// - /// - bool SendCreateObject(ulong regionHandle, SceneObjectGroup sog, bool isLocalCall); - - /// - /// Create an object from the user's inventory in the destination region. - /// This message is used primarily by clients. - /// - /// - /// - /// - /// - bool SendCreateObject(ulong regionHandle, UUID userID, UUID itemID); - - #endregion Objects - - } - - // This may not be needed, but having it here for now. - public interface IInterregionCommsIn - { - event ChildAgentUpdateReceived OnChildAgentUpdate; - } - -} diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 7772f9476b..567ce2ad03 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -4405,18 +4405,6 @@ namespace OpenSim.Region.Framework.Scenes return sp; } - public virtual bool IncomingRetrieveRootAgent(UUID id, out IAgentData agent) - { - agent = null; - ScenePresence sp = GetScenePresence(id); - if ((sp != null) && (!sp.IsChildAgent)) - { - sp.IsChildAgent = true; - return sp.CopyAgent(out agent); - } - - return false; - } /// /// Authenticated close (via network) /// From 21bc799a17f6f0315256d901f3ec226a39f93a51 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Thu, 9 Jan 2014 18:34:10 +0200 Subject: [PATCH 02/49] Fixed offline IM --- OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs index 6731923c3b..d36f9a40f4 100644 --- a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs +++ b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -100,7 +100,7 @@ namespace OpenSim.OfflineIM return false; } - string imXml = string.Empty; + string imXml; using (MemoryStream mstream = new MemoryStream()) { XmlWriterSettings settings = new XmlWriterSettings(); @@ -110,13 +110,9 @@ namespace OpenSim.OfflineIM { m_serializer.Serialize(writer, im); writer.Flush(); - - mstream.Position = 0; - using (StreamReader sreader = new StreamReader(mstream)) - { - imXml = sreader.ReadToEnd(); - } } + + imXml = Util.UTF8.GetString(mstream.ToArray()); } OfflineIMData data = new OfflineIMData(); From 46c2791fe2f9ea92535d3933602e24dcba8f96f9 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 29 Oct 2013 16:03:58 +0200 Subject: [PATCH 03/49] In the offline message table, store the sender. This data is useful for preventing abuse (e.g., someone who sends too many messages), or for deleting message if their sender has been deleted. --- .../Remote/OfflineIMServiceRobustConnector.cs | 3 +-- OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs | 3 ++- OpenSim/Data/IOfflineIMData.cs | 3 ++- OpenSim/Data/MySQL/Resources/IM_Store.migrations | 12 +++++++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs index 32c24db42d..6158abc5ba 100644 --- a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs +++ b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -96,7 +96,6 @@ namespace OpenSim.OfflineIM string method = request["METHOD"].ToString(); request.Remove("METHOD"); - m_log.DebugFormat("[OfflineIM.V2.Handler]: {0}", method); switch (method) { case "GET": diff --git a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs index d36f9a40f4..df0c53f084 100644 --- a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs +++ b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs @@ -91,7 +91,7 @@ namespace OpenSim.OfflineIM { reason = string.Empty; - // TODO Check limits + // Check limits UUID principalID = new UUID(im.toAgentID); long count = m_Database.GetCount("PrincipalID", principalID.ToString()); if (count >= MAX_IM) @@ -117,6 +117,7 @@ namespace OpenSim.OfflineIM OfflineIMData data = new OfflineIMData(); data.PrincipalID = principalID; + data.FromID = new UUID(im.fromAgentID); data.Data = new Dictionary(); data.Data["Message"] = imXml; diff --git a/OpenSim/Data/IOfflineIMData.cs b/OpenSim/Data/IOfflineIMData.cs index e780304ec8..58501a3b7d 100644 --- a/OpenSim/Data/IOfflineIMData.cs +++ b/OpenSim/Data/IOfflineIMData.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * @@ -34,6 +34,7 @@ namespace OpenSim.Data public class OfflineIMData { public UUID PrincipalID; + public UUID FromID; public Dictionary Data; } diff --git a/OpenSim/Data/MySQL/Resources/IM_Store.migrations b/OpenSim/Data/MySQL/Resources/IM_Store.migrations index 7cfcd43888..f73475ee33 100644 --- a/OpenSim/Data/MySQL/Resources/IM_Store.migrations +++ b/OpenSim/Data/MySQL/Resources/IM_Store.migrations @@ -21,4 +21,14 @@ INSERT INTO `im_offline` SELECT * from `diva_im_offline`; DROP TABLE `diva_im_offline`; DELETE FROM `migrations` WHERE name='diva_im_Store'; -COMMIT; \ No newline at end of file +COMMIT; + +:VERSION 3 # -------------------------- + +BEGIN; + +ALTER TABLE `im_offline` + ADD `FromID` char(36) NOT NULL default '' AFTER `PrincipalID`, + ADD KEY `FromID` (`FromID`); + +COMMIT; From 2d9d6fe922c99e79489b19b18ac33338012137ff Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 29 Oct 2013 16:38:03 +0200 Subject: [PATCH 04/49] Can delete the Offline Messages sent to/from a user. This is useful if the user is deleted. --- .../Addons/OfflineIM/OfflineIMRegionModule.cs | 5 +++++ .../Remote/OfflineIMServiceRemoteConnector.cs | 8 ++++++++ .../Remote/OfflineIMServiceRobustConnector.cs | 17 +++++++++++++++++ .../OfflineIM/Service/OfflineIMService.cs | 7 +++++++ .../Services/Interfaces/IOfflineIMService.cs | 7 +++++++ 5 files changed, 44 insertions(+) diff --git a/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs b/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs index 050ebd20f0..5ef068a251 100644 --- a/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs +++ b/OpenSim/Addons/OfflineIM/OfflineIMRegionModule.cs @@ -261,6 +261,11 @@ namespace OpenSim.OfflineIM return m_OfflineIMService.StoreMessage(im, out reason); } + public void DeleteMessages(UUID userID) + { + m_OfflineIMService.DeleteMessages(userID); + } + #endregion } } diff --git a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs index 69feb762f9..f6b17e5a5d 100644 --- a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs +++ b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRemoteConnector.cs @@ -117,6 +117,14 @@ namespace OpenSim.OfflineIM return true; } + public void DeleteMessages(UUID userID) + { + Dictionary sendData = new Dictionary(); + sendData["UserID"] = userID; + + MakeRequest("DELETE", sendData); + } + #endregion diff --git a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs index 6158abc5ba..13b0e7e6a8 100644 --- a/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs +++ b/OpenSim/Addons/OfflineIM/Remote/OfflineIMServiceRobustConnector.cs @@ -102,6 +102,8 @@ namespace OpenSim.OfflineIM return HandleGet(request); case "STORE": return HandleStore(request); + case "DELETE": + return HandleDelete(request); } m_log.DebugFormat("[OFFLINE IM HANDLER]: unknown method request: {0}", method); } @@ -158,6 +160,21 @@ namespace OpenSim.OfflineIM return Util.UTF8NoBomEncoding.GetBytes(xmlString); } + byte[] HandleDelete(Dictionary request) + { + if (!request.ContainsKey("UserID")) + { + return FailureResult(); + } + else + { + UUID userID = new UUID(request["UserID"].ToString()); + m_OfflineIMService.DeleteMessages(userID); + + return SuccessResult(); + } + } + #region Helpers private void NullResult(Dictionary result, string reason) diff --git a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs index df0c53f084..690c955660 100644 --- a/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs +++ b/OpenSim/Addons/OfflineIM/Service/OfflineIMService.cs @@ -124,5 +124,12 @@ namespace OpenSim.OfflineIM return m_Database.Store(data); } + + public void DeleteMessages(UUID userID) + { + m_Database.Delete("PrincipalID", userID.ToString()); + m_Database.Delete("FromID", userID.ToString()); + } + } } diff --git a/OpenSim/Services/Interfaces/IOfflineIMService.cs b/OpenSim/Services/Interfaces/IOfflineIMService.cs index 28489670fc..588aaafb6b 100644 --- a/OpenSim/Services/Interfaces/IOfflineIMService.cs +++ b/OpenSim/Services/Interfaces/IOfflineIMService.cs @@ -35,7 +35,14 @@ namespace OpenSim.Services.Interfaces public interface IOfflineIMService { List GetMessages(UUID principalID); + bool StoreMessage(GridInstantMessage im, out string reason); + + /// + /// Delete messages to or from this user (or group). + /// + /// A user or group ID + void DeleteMessages(UUID userID); } public class OfflineIMDataUtils From 3ffd90496a366f2b64eb8daadf63a2b6ee05ad7a Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 16 Jan 2014 20:23:31 +0000 Subject: [PATCH 05/49] Prevent duplicate invocations or race dontision in SP.CompleteMovement() This can happen under poor network conditions if a viewer repeats the message send If this happens, physics actors can get orphaned, which unecessarily raises physics frame times --- .../Region/Framework/Scenes/ScenePresence.cs | 29 +++++- .../Scenes/Tests/ScenePresenceAgentTests.cs | 92 ++++++++----------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 49f70c4f28..63cca5697e 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -108,6 +108,16 @@ namespace OpenSim.Region.Framework.Scenes } } + /// + /// This exists to prevent race conditions between two CompleteMovement threads if the simulator is slow and + /// the viewer fires these in quick succession. + /// + /// + /// TODO: The child -> agent transition should be folded into LifecycleState and the CompleteMovement + /// regulation done there. + /// + private object m_completeMovementLock = new object(); + // private static readonly byte[] DEFAULT_TEXTURE = AvatarAppearance.GetDefaultTexture().GetBytes(); private static readonly Array DIR_CONTROL_FLAGS = Enum.GetValues(typeof(Dir_ControlFlags)); private static readonly Vector3 HEAD_ADJUSTMENT = new Vector3(0f, 0f, 0.3f); @@ -905,6 +915,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// Turns a child agent into a root agent. /// + /// /// Child agents are logged into neighbouring sims largely to observe changes. Root agents exist when the /// avatar is actual in the sim. They can perform all actions. /// This change is made whenever an avatar enters a region, whether by crossing over from a neighbouring sim, @@ -912,8 +923,8 @@ namespace OpenSim.Region.Framework.Scenes /// /// This method is on the critical path for transferring an avatar from one region to another. Delay here /// delays that crossing. - /// - private void MakeRootAgent(Vector3 pos, bool isFlying) + /// + private bool MakeRootAgent(Vector3 pos, bool isFlying) { // m_log.InfoFormat( // "[SCENE]: Upgrading child to root agent for {0} in {1}", @@ -921,6 +932,10 @@ namespace OpenSim.Region.Framework.Scenes //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); + lock (m_completeMovementLock) + if (!IsChildAgent) + return false; + IsChildAgent = false; // Must reset this here so that a teleport to a region next to an existing region does not keep the flag @@ -1070,6 +1085,7 @@ namespace OpenSim.Region.Framework.Scenes m_scene.EventManager.TriggerOnMakeRootAgent(this); + return true; } public int GetStateSource() @@ -1443,7 +1459,14 @@ namespace OpenSim.Region.Framework.Scenes } bool flying = ((m_AgentControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) != 0); - MakeRootAgent(AbsolutePosition, flying); + if (!MakeRootAgent(AbsolutePosition, flying)) + { + m_log.DebugFormat( + "[SCENE PRESENCE]: Aborting CompleteMovement call for {0} in {1} as they are already root", + Name, Scene.Name); + + return; + } // Tell the client that we're totally ready ControllingClient.MoveAgentIntoRegion(m_scene.RegionInfo, AbsolutePosition, look); diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs index d1aeaeef16..1ff1329d71 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs @@ -111,6 +111,45 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); } + /// + /// Test that duplicate complete movement calls are ignored. + /// + /// + /// If duplicate calls are not ignored then there is a risk of race conditions or other unexpected effects. + /// + [Test] + public void TestDupeCompleteMovementCalls() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + TestScene scene = new SceneHelpers().SetupScene(); + + int makeRootAgentEvents = 0; + scene.EventManager.OnMakeRootAgent += spi => makeRootAgentEvents++; + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Normally these would be invoked by a CompleteMovement message coming in to the UDP stack. But for + // convenience, here we will invoke it manually. + sp.CompleteMovement(sp.ControllingClient, true); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Check rest of exepcted parameters. + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + + Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); + } + [Test] public void TestCreateDuplicateRootScenePresence() { @@ -249,58 +288,5 @@ namespace OpenSim.Region.Framework.Scenes.Tests // Assert.That(childPresence, Is.Not.Null); // Assert.That(childPresence.IsChildAgent, Is.True); } - -// /// -// /// Test adding a root agent to a scene. Doesn't yet actually complete crossing the agent into the scene. -// /// -// [Test] -// public void T010_TestAddRootAgent() -// { -// TestHelpers.InMethod(); -// -// string firstName = "testfirstname"; -// -// AgentCircuitData agent = new AgentCircuitData(); -// agent.AgentID = agent1; -// agent.firstname = firstName; -// agent.lastname = "testlastname"; -// agent.SessionID = UUID.Random(); -// agent.SecureSessionID = UUID.Random(); -// agent.circuitcode = 123; -// agent.BaseFolder = UUID.Zero; -// agent.InventoryFolder = UUID.Zero; -// agent.startpos = Vector3.Zero; -// agent.CapsPath = GetRandomCapsObjectPath(); -// agent.ChildrenCapSeeds = new Dictionary(); -// agent.child = true; -// -// scene.PresenceService.LoginAgent(agent.AgentID.ToString(), agent.SessionID, agent.SecureSessionID); -// -// string reason; -// scene.NewUserConnection(agent, (uint)TeleportFlags.ViaLogin, out reason); -// testclient = new TestClient(agent, scene); -// scene.AddNewAgent(testclient); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Not.Null, "presence is null"); -// Assert.That(presence.Firstname, Is.EqualTo(firstName), "First name not same"); -// acd1 = agent; -// } -// -// /// -// /// Test removing an uncrossed root agent from a scene. -// /// -// [Test] -// public void T011_TestRemoveRootAgent() -// { -// TestHelpers.InMethod(); -// -// scene.RemoveClient(agent1); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Null, "presence is not null"); -// } } } \ No newline at end of file From 3bc669ffc7638b56d5ab5aac038c33106ba9a95b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 16 Jan 2014 23:31:50 +0000 Subject: [PATCH 06/49] Actually put IsChildAgent = true inside the lock, otherwise there is still a small window for race conditions on duplicate CompleteMovement calls --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 63cca5697e..3290da1ba3 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -933,10 +933,12 @@ namespace OpenSim.Region.Framework.Scenes //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); lock (m_completeMovementLock) + { if (!IsChildAgent) return false; - IsChildAgent = false; + IsChildAgent = false; + } // Must reset this here so that a teleport to a region next to an existing region does not keep the flag // set and prevent the close of the connection on a subsequent re-teleport. From 4fa843ff19441c9daa4e7dae0a4d705f912fca54 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 16 Jan 2014 23:44:17 +0000 Subject: [PATCH 07/49] Reorder checks in SP.CompleteMovement() to fix test failures --- .../Region/Framework/Scenes/ScenePresence.cs | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 18d84a2db2..85a20e9653 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -1003,49 +1003,46 @@ namespace OpenSim.Region.Framework.Scenes /// private bool MakeRootAgent(Vector3 pos, bool isFlying) { -// m_log.InfoFormat( -// "[SCENE]: Upgrading child to root agent for {0} in {1}", -// Name, m_scene.RegionInfo.RegionName); - - if (ParentUUID != UUID.Zero) - { - m_log.DebugFormat("[SCENE PRESENCE]: Sitting avatar back on prim {0}", ParentUUID); - SceneObjectPart part = m_scene.GetSceneObjectPart(ParentUUID); - if (part == null) - { - m_log.ErrorFormat("[SCENE PRESENCE]: Can't find prim {0} to sit on", ParentUUID); - } - else - { - part.ParentGroup.AddAvatar(UUID); - if (part.SitTargetPosition != Vector3.Zero) - part.SitTargetAvatar = UUID; -// ParentPosition = part.GetWorldPosition(); - ParentID = part.LocalId; - ParentPart = part; - m_pos = PrevSitOffset; -// pos = ParentPosition; - pos = part.GetWorldPosition(); - } - ParentUUID = UUID.Zero; - - IsChildAgent = false; - -// Animator.TrySetMovementAnimation("SIT"); - } - else - { - IsChildAgent = false; - IsLoggingIn = false; - } - - //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); - lock (m_completeMovementLock) { if (!IsChildAgent) return false; + //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); + + // m_log.InfoFormat( + // "[SCENE]: Upgrading child to root agent for {0} in {1}", + // Name, m_scene.RegionInfo.RegionName); + + if (ParentUUID != UUID.Zero) + { + m_log.DebugFormat("[SCENE PRESENCE]: Sitting avatar back on prim {0}", ParentUUID); + SceneObjectPart part = m_scene.GetSceneObjectPart(ParentUUID); + if (part == null) + { + m_log.ErrorFormat("[SCENE PRESENCE]: Can't find prim {0} to sit on", ParentUUID); + } + else + { + part.ParentGroup.AddAvatar(UUID); + if (part.SitTargetPosition != Vector3.Zero) + part.SitTargetAvatar = UUID; + // ParentPosition = part.GetWorldPosition(); + ParentID = part.LocalId; + ParentPart = part; + m_pos = PrevSitOffset; + // pos = ParentPosition; + pos = part.GetWorldPosition(); + } + ParentUUID = UUID.Zero; + + // Animator.TrySetMovementAnimation("SIT"); + } + else + { + IsLoggingIn = false; + } + IsChildAgent = false; } From 39e5785c0f5bfa9fce2549757ac895920c88825a Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Thu, 31 Oct 2013 12:50:17 +0200 Subject: [PATCH 08/49] Fixed: Windlight functions caused an error if called when the script's owner isn't in the scene --- .../ScriptEngine/Shared/Api/Implementation/LS_Api.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs index 1d6cb6d4dc..30eed4060a 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs @@ -446,7 +446,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return 0; @@ -474,7 +476,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return; @@ -497,7 +501,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightSceneTargeted can only be used by estate managers or owners."); return 0; From 18e0d2f343ea83d1dee0ac1a71e0465aca2b6758 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 17 Jan 2014 01:55:40 +0000 Subject: [PATCH 09/49] minor: Add Ai Austin to additional OpenSim contributors by request as he has added config corrections and does extensive liaison with other projects --- CONTRIBUTORS.txt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9114fc578a..6fa116d27d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,5 +1,4 @@ - <<<>>>>The following people have contributed to OpenSim (Thank you -for your effort!) +The following people have contributed to OpenSim (Thank you for your effort!) = Current OpenSim Developers (in very rough order of appearance) = These folks represent the current core team for OpenSim, and are the @@ -61,8 +60,9 @@ where we are today. These folks have contributed code patches or content to OpenSimulator to help make it what it is today. -* aduffy70 * A_Biondi +* aduffy70 +* Ai Austin * alex_carnell * Alan Webb (IBM) * Aleric @@ -173,23 +173,18 @@ what it is today. * Zha Ewry * ziah - = LSL Devs = - * Alondria * CharlieO * Tedd * Melanie Thielker - = Testers = - * Ai Austin * CharlieO (LSL) * Ckrinke * openlifegrid.com - This software uses components from the following developers: * Sleepycat Software (Berkeley DB) * Aurora-Sim (http://aurora-sim.org) From 14c72d4a5bba73c10bd4a5d93d53cd4fc0398570 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 17 Jan 2014 01:58:10 +0000 Subject: [PATCH 10/49] Revert "Fixed: Windlight functions caused an error if called when the script's owner isn't in the scene" This reverts commit 39e5785c0f5bfa9fce2549757ac895920c88825a. Did not mean to apply this yet. --- .../ScriptEngine/Shared/Api/Implementation/LS_Api.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs index 30eed4060a..1d6cb6d4dc 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs @@ -446,9 +446,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return 0; @@ -476,9 +474,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return; } - - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return; @@ -501,9 +497,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) { LSShoutError("lsSetWindlightSceneTargeted can only be used by estate managers or owners."); return 0; From 9fefbcf7fc5e8767030757da7ab077fa96cc0904 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 17 Jan 2014 23:32:37 +0000 Subject: [PATCH 11/49] minor: since structs are values, assigning them to another variable copies it. Instantiation is unnecessary. --- .../Framework/EntityTransfer/EntityTransferModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 246b2530ac..5fea0cfd49 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -1385,7 +1385,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer public GridRegion GetDestination(Scene scene, UUID agentID, Vector3 pos, out uint xDest, out uint yDest, out string version, out Vector3 newpos) { version = String.Empty; - newpos = new Vector3(pos.X, pos.Y, pos.Z); + newpos = pos; // m_log.DebugFormat( // "[ENTITY TRANSFER MODULE]: Crossing agent {0} at pos {1} in {2}", agent.Name, pos, scene.Name); From b52b50ee56f3461d7f2dc47a905a0b10d07d3346 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 17 Jan 2014 23:36:23 +0000 Subject: [PATCH 12/49] minor: reinsert some method doc back into IEntityTransferModule --- .../Interfaces/IEntityTransferModule.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/OpenSim/Region/Framework/Interfaces/IEntityTransferModule.cs b/OpenSim/Region/Framework/Interfaces/IEntityTransferModule.cs index 1949a9007a..214b07a9d9 100644 --- a/OpenSim/Region/Framework/Interfaces/IEntityTransferModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IEntityTransferModule.cs @@ -47,13 +47,33 @@ namespace OpenSim.Region.Framework.Interfaces /// The handle of the destination region. If it's the same as the region currently /// occupied by the agent then the teleport will be within that region. /// + /// + /// /// /// /// void Teleport(ScenePresence agent, ulong regionHandle, Vector3 position, Vector3 lookAt, uint teleportFlags); + /// + /// Teleports the agent for the given client to their home destination. + /// + /// + /// bool TeleportHome(UUID id, IClientAPI client); + /// + /// Teleport an agent directly to a given region without checking whether the region should be substituted. + /// + /// + /// Please use Teleport() instead unless you know exactly what you're doing. + /// Do not use for same region teleports. + /// + /// + /// + /// /param> + /// + /// + /// void DoTeleport(ScenePresence sp, GridRegion reg, GridRegion finalDestination, Vector3 position, Vector3 lookAt, uint teleportFlags); From 12bfce7b9f1356893e351d13ddd947810c352727 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:06:12 +0000 Subject: [PATCH 13/49] elminate unnecessary asset != null check in FlotsamAssetCache.UpdateFileCache() Passed in asset is always not null --- .../CoreModules/Asset/FlotsamAssetCache.cs | 105 +++++++++--------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index f1fee6322c..e1aa4605d6 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -248,70 +248,67 @@ namespace OpenSim.Region.CoreModules.Asset private void UpdateFileCache(string key, AssetBase asset) { - // TODO: Spawn this off to some seperate thread to do the actual writing - if (asset != null) + string filename = GetFileName(key); + + try { - string filename = GetFileName(key); - - try + // If the file is already cached, don't cache it, just touch it so access time is updated + if (File.Exists(filename)) { - // If the file is already cached, don't cache it, just touch it so access time is updated - if (File.Exists(filename)) + // We don't really want to know about sharing + // violations here. If the file is locked, then + // the other thread has updated the time for us. + try { - // We don't really want to know about sharing - // violations here. If the file is locked, then - // the other thread has updated the time for us. - try - { - lock (m_CurrentlyWriting) - { - if (!m_CurrentlyWriting.Contains(filename)) - File.SetLastAccessTime(filename, DateTime.Now); - } - } - catch - { - } - } else { - - // Once we start writing, make sure we flag that we're writing - // that object to the cache so that we don't try to write the - // same file multiple times. lock (m_CurrentlyWriting) { + if (!m_CurrentlyWriting.Contains(filename)) + File.SetLastAccessTime(filename, DateTime.Now); + } + } + catch + { + } + } + else + { + // Once we start writing, make sure we flag that we're writing + // that object to the cache so that we don't try to write the + // same file multiple times. + lock (m_CurrentlyWriting) + { #if WAIT_ON_INPROGRESS_REQUESTS - if (m_CurrentlyWriting.ContainsKey(filename)) - { - return; - } - else - { - m_CurrentlyWriting.Add(filename, new ManualResetEvent(false)); - } - -#else - if (m_CurrentlyWriting.Contains(filename)) - { - return; - } - else - { - m_CurrentlyWriting.Add(filename); - } -#endif - + if (m_CurrentlyWriting.ContainsKey(filename)) + { + return; + } + else + { + m_CurrentlyWriting.Add(filename, new ManualResetEvent(false)); } - Util.FireAndForget( - delegate { WriteFileCache(filename, asset); }); +#else + if (m_CurrentlyWriting.Contains(filename)) + { + return; + } + else + { + m_CurrentlyWriting.Add(filename); + } +#endif + } + + Util.FireAndForget( + delegate { WriteFileCache(filename, asset); }); } - catch (Exception e) - { - m_log.ErrorFormat( - "[FLOTSAM ASSET CACHE]: Failed to update cache for asset {0}. Exception {1} {2}", - asset.ID, e.Message, e.StackTrace); - } + } + catch (Exception e) + { + m_log.ErrorFormat( + "[FLOTSAM ASSET CACHE]: Failed to update cache for asset {0}. Exception {1} {2}", + asset.ID, e.Message, e.StackTrace); } } From 97fbb8ed45e4827ea7473bac2b792499a9284c3e Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:12:12 +0000 Subject: [PATCH 14/49] Elminate some copy/paste in FlotsamAssetCache.CheckFromFileCache() and use using() construct to ensure filestream is always closed --- .../CoreModules/Asset/FlotsamAssetCache.cs | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index e1aa4605d6..169412e542 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -352,7 +352,6 @@ namespace OpenSim.Region.CoreModules.Asset return false; } - /// /// Try to get an asset from the file cache. /// @@ -390,15 +389,16 @@ namespace OpenSim.Region.CoreModules.Asset if (File.Exists(filename)) { - FileStream stream = null; try { - stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); - BinaryFormatter bformatter = new BinaryFormatter(); + using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryFormatter bformatter = new BinaryFormatter(); - asset = (AssetBase)bformatter.Deserialize(stream); + asset = (AssetBase)bformatter.Deserialize(stream); - m_DiskHits++; + m_DiskHits++; + } } catch (System.Runtime.Serialization.SerializationException e) { @@ -417,12 +417,6 @@ namespace OpenSim.Region.CoreModules.Asset m_log.WarnFormat( "[FLOTSAM ASSET CACHE]: Failed to get file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); - - } - finally - { - if (stream != null) - stream.Close(); } } @@ -434,36 +428,19 @@ namespace OpenSim.Region.CoreModules.Asset bool found = false; string filename = GetFileName(id); + if (File.Exists(filename)) { - // actually check if we can open it, and so update expire - FileStream stream = null; try { - stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); - if (stream != null) + using (FileStream stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { - found = true; - stream.Close(); + if (stream != null) + found = true; } - - } - catch (System.Runtime.Serialization.SerializationException e) - { - found = false; - m_log.ErrorFormat( - "[FLOTSAM ASSET CACHE]: Failed to check file {0} for asset {1}. Exception {2} {3}", - filename, id, e.Message, e.StackTrace); - - // If there was a problem deserializing the asset, the asset may - // either be corrupted OR was serialized under an old format - // {different version of AssetBase} -- we should attempt to - // delete it and re-cache - File.Delete(filename); } catch (Exception e) { - found = false; m_log.ErrorFormat( "[FLOTSAM ASSET CACHE]: Failed to check file {0} for asset {1}. Exception {2} {3}", filename, id, e.Message, e.StackTrace); From d381da81d6bf57f05dd3f3430de6c8c936932292 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:15:38 +0000 Subject: [PATCH 15/49] minor: Add method doc to IImproveAssetCache --- OpenSim/Framework/IImprovedAssetCache.cs | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/OpenSim/Framework/IImprovedAssetCache.cs b/OpenSim/Framework/IImprovedAssetCache.cs index a0b8b55aaa..a853e9015d 100644 --- a/OpenSim/Framework/IImprovedAssetCache.cs +++ b/OpenSim/Framework/IImprovedAssetCache.cs @@ -31,10 +31,34 @@ namespace OpenSim.Framework { public interface IImprovedAssetCache { + /// + /// Cache the specified asset. + /// + /// void Cache(AssetBase asset); + + /// + /// Get an asset by its id. + /// + /// + /// null if the asset does not exist. AssetBase Get(string id); + + /// + /// Check whether an asset with the specified id exists in the cache. + /// + /// bool Check(string id); + + /// + /// Expire an asset from the cache. + /// + /// void Expire(string id); + + /// + /// Clear the cache. + /// void Clear(); } -} +} \ No newline at end of file From ee8ba1ab9a30cf397c390b161a5d03e8c7b86290 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:21:53 +0000 Subject: [PATCH 16/49] Simplify FlotsamAssetCache.CheckFromMemoryCache() --- OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index 169412e542..a1803c148b 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -344,12 +344,7 @@ namespace OpenSim.Region.CoreModules.Asset private bool CheckFromMemoryCache(string id) { - AssetBase asset = null; - - if (m_MemoryCache.TryGetValue(id, out asset)) - return true; - - return false; + return m_MemoryCache.Contains(id); } /// From fc7ccfdafae7b8e9acd130b9f70e99241e0d33fa Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:30:23 +0000 Subject: [PATCH 17/49] Properly implement CenomeAssetCache.Check() --- OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs index f43305ff40..9b0e1f4804 100644 --- a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs @@ -194,10 +194,12 @@ namespace OpenSim.Region.CoreModules.Asset #region IImprovedAssetCache Members - public bool Check(string id) { - return false; + AssetBase asset; + + // XXX:This is probably not an efficient implementation. + return m_cache.TryGetValue(id, out asset); } /// From 427ffd3387c13daf7babc08c13a9acac1fe405c5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:48:20 +0000 Subject: [PATCH 18/49] implement GlynnTuckerAssetCache.Check() --- OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs index ce9b5467f6..5f76ac2c9e 100644 --- a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs @@ -117,7 +117,7 @@ namespace OpenSim.Region.CoreModules.Asset public bool Check(string id) { - return false; + return m_Cache.Contains(id); } public void Cache(AssetBase asset) From b9453a8f6f68cff86c261d245c41955ec8f8af84 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:52:36 +0000 Subject: [PATCH 19/49] implement CoreAssetCache.Check() --- OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs index 58ce61ac4e..f72074851e 100644 --- a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs @@ -114,7 +114,8 @@ namespace OpenSim.Region.CoreModules.Asset // public bool Check(string id) { - return false; + // XXX This is probably not an efficient implementation. + return Get(id) != null; } public void Cache(AssetBase asset) From 0cbe5f842419040dcfee688664d605a03d2f420b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 00:55:49 +0000 Subject: [PATCH 20/49] Remove redundant methods in FlotsamAssetCache --- OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index a1803c148b..6a5f8f397e 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -487,11 +487,6 @@ namespace OpenSim.Region.CoreModules.Asset return Get(id); } - public AssetBase CheckCached(string id) - { - return Get(id); - } - public void Expire(string id) { if (m_LogLevel >= 2) @@ -1036,11 +1031,6 @@ namespace OpenSim.Region.CoreModules.Asset return asset.Data; } - public bool CheckData(string id) - { - return Check(id); ; - } - public bool Get(string id, object sender, AssetRetrieved handler) { AssetBase asset = Get(id); From 50ea2e0d67a9edee86d76b2fa8d4561710326461 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Thu, 31 Oct 2013 12:50:17 +0200 Subject: [PATCH 21/49] Fixed: Windlight functions caused an error if called when the script's owner isn't in the scene --- .../ScriptEngine/Shared/Api/Implementation/LS_Api.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs index 1d6cb6d4dc..30eed4060a 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs @@ -446,7 +446,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return 0; @@ -474,7 +476,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); return; @@ -497,7 +501,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && World.GetScenePresence(m_host.OwnerID).GodLevel < 200) + + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) { LSShoutError("lsSetWindlightSceneTargeted can only be used by estate managers or owners."); return 0; From e9454d4672782647247db36e0d5e18b82cad58f0 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Sat, 18 Jan 2014 01:36:40 +0000 Subject: [PATCH 22/49] Following on from 50ea2e0, only fetch scene presence for check if lightscript function has failed initial IsEstateManagerOrOwner() check --- .../Shared/Api/Implementation/LS_Api.cs | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs index 30eed4060a..b13a5ae57e 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LS_Api.cs @@ -434,6 +434,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api } return wl; } + /// /// Set the current Windlight scene /// @@ -447,14 +448,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return 0; } - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID)) { - LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); - return 0; + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + + if (sp == null || sp.GodLevel < 200) + { + LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); + return 0; + } } + int success = 0; m_host.AddScriptLPS(1); + if (LightShareModule.EnableWindlight) { RegionLightShareData wl = getWindlightProfileFromRules(rules); @@ -467,8 +474,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("Windlight module is disabled"); return 0; } + return success; } + public void lsClearWindlightScene() { if (!m_LSFunctionsEnabled) @@ -476,19 +485,25 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return; } - - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID)) { - LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); - return; + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + + if (sp == null || sp.GodLevel < 200) + { + LSShoutError("lsSetWindlightScene can only be used by estate managers or owners."); + return; + } } m_host.ParentGroup.Scene.RegionInfo.WindlightSettings.valid = false; if (m_host.ParentGroup.Scene.SimulationDataService != null) m_host.ParentGroup.Scene.SimulationDataService.RemoveRegionWindlightSettings(m_host.ParentGroup.Scene.RegionInfo.RegionID); + m_host.ParentGroup.Scene.EventManager.TriggerOnSaveNewWindlightProfile(); } + /// /// Set the current Windlight scene to a target avatar /// @@ -501,15 +516,21 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("LightShare functions are not enabled."); return 0; } - - ScenePresence sp = World.GetScenePresence(m_host.OwnerID); - if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID) && (sp == null || sp.GodLevel < 200)) + + if (!World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_host.OwnerID)) { - LSShoutError("lsSetWindlightSceneTargeted can only be used by estate managers or owners."); - return 0; + ScenePresence sp = World.GetScenePresence(m_host.OwnerID); + + if (sp == null || sp.GodLevel < 200) + { + LSShoutError("lsSetWindlightSceneTargeted can only be used by estate managers or owners."); + return 0; + } } + int success = 0; m_host.AddScriptLPS(1); + if (LightShareModule.EnableWindlight) { RegionLightShareData wl = getWindlightProfileFromRules(rules); @@ -521,8 +542,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api LSShoutError("Windlight module is disabled"); return 0; } + return success; - } - + } } -} +} \ No newline at end of file From 4fb3d314b85019af0e30cf0c2e7c24e9bf32ab66 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sun, 19 Jan 2014 07:26:55 -0800 Subject: [PATCH 23/49] Fix casting error for float type INI file parameter parsing. --- OpenSim/Framework/Util.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index cebba46fac..7bc8176a18 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs @@ -1031,7 +1031,7 @@ namespace OpenSim.Framework else if (typeof(T) == typeof(Int32)) val = cnf.GetInt(varname, (int)val); else if (typeof(T) == typeof(float)) - val = cnf.GetFloat(varname, (int)val); + val = cnf.GetFloat(varname, (float)val); else m_log.ErrorFormat("[UTIL]: Unhandled type {0}", typeof(T)); } From e8273fa8ad85323f18fb67ecf6d5f07eced87178 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 26 Nov 2013 10:37:32 +0200 Subject: [PATCH 24/49] - Materials: support the viewer removing the material (in which case matsMap["Material"] is missing) - Reduced logging --- .../Materials/MaterialsDemoModule.cs | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs index d8f5563624..44b1a4aab3 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs @@ -104,7 +104,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule if (!m_enabled) return; - m_log.DebugFormat("[MaterialsDemoModule]: INITIALIZED MODULE"); + m_log.DebugFormat("[MaterialsDemoModule]: Initialized"); } public void Close() @@ -112,7 +112,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule if (!m_enabled) return; - m_log.DebugFormat("[MaterialsDemoModule]: CLOSED MODULE"); + //m_log.DebugFormat("[MaterialsDemoModule]: CLOSED MODULE"); } public void AddRegion(Scene scene) @@ -120,7 +120,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule if (!m_enabled) return; - m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName); + //m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName); m_scene = scene; m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; @@ -166,7 +166,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene; // m_scene.EventManager.OnGatherUuids -= GatherMaterialsUuids; - m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + //m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName); } public void RegionLoaded(Scene scene) @@ -195,7 +195,8 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule if (part.DynAttrs == null) { - m_log.Warn("[MaterialsDemoModule]: NULL DYNATTRS :( "); + //m_log.Warn("[MaterialsDemoModule]: NULL DYNATTRS :( "); + return; } lock (part.DynAttrs) @@ -216,11 +217,11 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule return; } - m_log.Info("[MaterialsDemoModule]: OSMaterials: " + OSDParser.SerializeJsonString(OSMaterials)); + //m_log.Info("[MaterialsDemoModule]: OSMaterials: " + OSDParser.SerializeJsonString(OSMaterials)); if (matsArr == null) { - m_log.Info("[MaterialsDemoModule]: matsArr is null :( "); + //m_log.Info("[MaterialsDemoModule]: matsArr is null :( "); return; } @@ -238,7 +239,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception decoding persisted material: " + e.ToString()); + m_log.Warn("[MaterialsDemoModule]: exception decoding persisted material ", e); } } } @@ -299,7 +300,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception in StoreMaterialsForPart(): " + e.ToString()); + m_log.Warn("[MaterialsDemoModule]: exception in StoreMaterialsForPart() ", e); } } @@ -307,7 +308,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { - m_log.Debug("[MaterialsDemoModule]: POST cap handler"); + //m_log.Debug("[MaterialsDemoModule]: POST cap handler"); OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); OSDMap resp = new OSDMap(); @@ -341,7 +342,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule { if (m_knownMaterials.ContainsKey(id)) { - m_log.Info("[MaterialsDemoModule]: request for known material ID: " + id.ToString()); + //m_log.Info("[MaterialsDemoModule]: request for known material ID: " + id.ToString()); OSDMap matMap = new OSDMap(); matMap["ID"] = OSD.FromBinary(id.GetBytes()); @@ -374,34 +375,40 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule { foreach (OSDMap matsMap in matsArr) { - m_log.Debug("[MaterialsDemoModule]: processing matsMap: " + OSDParser.SerializeJsonString(matsMap)); + //m_log.Debug("[MaterialsDemoModule]: processing matsMap: " + OSDParser.SerializeJsonString(matsMap)); - uint matLocalID = 0; - try { matLocalID = matsMap["ID"].AsUInteger(); } + uint primLocalID = 0; + try { primLocalID = matsMap["ID"].AsUInteger(); } catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"ID\" from matsMap: " + e.Message); } - m_log.Debug("[MaterialsDemoModule]: matLocalId: " + matLocalID.ToString()); - + //m_log.Debug("[MaterialsDemoModule]: primLocalID: " + primLocalID.ToString()); OSDMap mat = null; try { mat = matsMap["Material"] as OSDMap; } catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"Material\" from matsMap: " + e.Message); } - m_log.Debug("[MaterialsDemoModule]: mat: " + OSDParser.SerializeJsonString(mat)); - - UUID id = HashOsd(mat); - lock (m_knownMaterials) - m_knownMaterials[id] = mat; - + //m_log.Debug("[MaterialsDemoModule]: mat: " + OSDParser.SerializeJsonString(mat)); - var sop = m_scene.GetSceneObjectPart(matLocalID); + UUID id; + if (mat == null) + { + id = UUID.Zero; + } + else + { + id = HashOsd(mat); + lock (m_knownMaterials) + m_knownMaterials[id] = mat; + } + + var sop = m_scene.GetSceneObjectPart(primLocalID); if (sop == null) - m_log.Debug("[MaterialsDemoModule]: null SOP for localId: " + matLocalID.ToString()); + m_log.Debug("[MaterialsDemoModule]: null SOP for localId: " + primLocalID.ToString()); else { var te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length); if (te == null) { - m_log.Debug("[MaterialsDemoModule]: null TextureEntry for localId: " + matLocalID.ToString()); + m_log.Debug("[MaterialsDemoModule]: null TextureEntry for localId: " + primLocalID.ToString()); } else { @@ -434,7 +441,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule te.DefaultTexture.MaterialID = id; } - m_log.Debug("[MaterialsDemoModule]: setting material ID for face " + face.ToString() + " to " + id.ToString()); + //m_log.DebugFormat("[MaterialsDemoModule]: in \"{0}\", setting material ID for face {1} to {2}", sop.Name, face, id); //we cant use sop.UpdateTextureEntry(te); because it filters so do it manually @@ -455,7 +462,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception processing received material: " + e.Message); + m_log.Warn("[MaterialsDemoModule]: exception processing received material ", e); } } } @@ -465,10 +472,10 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception decoding zipped CAP payload: " + e.Message); + m_log.Warn("[MaterialsDemoModule]: exception decoding zipped CAP payload ", e); //return ""; } - m_log.Debug("[MaterialsDemoModule]: knownMaterials.Count: " + m_knownMaterials.Count.ToString()); + //m_log.Debug("[MaterialsDemoModule]: knownMaterials.Count: " + m_knownMaterials.Count.ToString()); } @@ -476,8 +483,8 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule string response = OSDParser.SerializeLLSDXmlString(resp); //m_log.Debug("[MaterialsDemoModule]: cap request: " + request); - m_log.Debug("[MaterialsDemoModule]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary())); - m_log.Debug("[MaterialsDemoModule]: cap response: " + response); + //m_log.Debug("[MaterialsDemoModule]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary())); + //m_log.Debug("[MaterialsDemoModule]: cap response: " + response); return response; } @@ -486,7 +493,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { - m_log.Debug("[MaterialsDemoModule]: GET cap handler"); + //m_log.Debug("[MaterialsDemoModule]: GET cap handler"); OSDMap resp = new OSDMap(); int matsCount = 0; @@ -506,7 +513,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } resp["Zipped"] = ZCompressOSD(allOsd, false); - m_log.Debug("[MaterialsDemoModule]: matsCount: " + matsCount.ToString()); + //m_log.Debug("[MaterialsDemoModule]: matsCount: " + matsCount.ToString()); return OSDParser.SerializeLLSDXmlString(resp); } @@ -654,4 +661,4 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule // } // } } -} \ No newline at end of file +} From ca0336d8349382ddb46df4c7e7f6377c64151f25 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Thu, 5 Dec 2013 14:18:59 +0200 Subject: [PATCH 25/49] Renamed MaterialsDemoModule to MaterialsModule --- .../Materials/{MaterialsDemoModule.cs => MaterialsModule.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename OpenSim/Region/OptionalModules/Materials/{MaterialsDemoModule.cs => MaterialsModule.cs} (99%) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs similarity index 99% rename from OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs rename to OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index 44b1a4aab3..e70715485e 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsDemoModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -88,7 +88,7 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule public class MaterialsDemoModule : INonSharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + public string Name { get { return "MaterialsDemoModule"; } } public Type ReplaceableInterface { get { return null; } } From 3018b2c5d7c9de0e8da6d158f0848c840b7864ab Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Fri, 6 Dec 2013 16:21:11 +0200 Subject: [PATCH 26/49] Materials module: a) Store materials as assets; b) Finalized it (removed the "Demo" label; removed most of the logging); c) Enabled by default Changed UuidGatherer to use 'sbyte' to identify assets instead of 'AssetType'. This lets UuidGatherer handle Materials, which are defined in a different enum from 'AssetType'. --- OpenSim/Framework/SLUtil.cs | 40 +- .../Serialization/ArchiveConstants.cs | 3 + .../CoreModules/Asset/FlotsamAssetCache.cs | 4 +- .../Archiver/InventoryArchiveWriteRequest.cs | 4 +- .../EntityTransfer/HGEntityTransferModule.cs | 4 +- .../InventoryAccess/HGAssetMapper.cs | 8 +- .../World/Archiver/ArchiveWriteRequest.cs | 12 +- .../World/Archiver/AssetsRequest.cs | 11 +- .../Scenes/Tests/UuidGathererTests.cs | 12 +- .../Region/Framework/Scenes/UuidGatherer.cs | 138 ++--- .../Materials/MaterialsModule.cs | 528 +++++++----------- bin/OpenSim.ini.example | 6 + 12 files changed, 312 insertions(+), 458 deletions(-) diff --git a/OpenSim/Framework/SLUtil.cs b/OpenSim/Framework/SLUtil.cs index cb73e8f762..92491050b3 100644 --- a/OpenSim/Framework/SLUtil.cs +++ b/OpenSim/Framework/SLUtil.cs @@ -39,8 +39,32 @@ namespace OpenSim.Framework { // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// + /// Asset types used only in OpenSim. + /// To avoid clashing with the code numbers used in Second Life, use only negative numbers here. + /// + public enum OpenSimAssetType : sbyte + { + Material = -2 + } + + #region SL / file extension / content-type conversions + /// + /// Returns the Enum entry corresponding to the given code, regardless of whether it belongs + /// to the AssetType or OpenSimAssetType enums. + /// + public static object AssetTypeFromCode(sbyte assetType) + { + if (Enum.IsDefined(typeof(OpenMetaverse.AssetType), assetType)) + return (OpenMetaverse.AssetType)assetType; + else if (Enum.IsDefined(typeof(OpenSimAssetType), assetType)) + return (OpenSimAssetType)assetType; + else + return OpenMetaverse.AssetType.Unknown; + } + private class TypeMapping { private sbyte assetType; @@ -56,12 +80,7 @@ namespace OpenSim.Framework public object AssetType { - get { - if (Enum.IsDefined(typeof(OpenMetaverse.AssetType), assetType)) - return (OpenMetaverse.AssetType)assetType; - else - return OpenMetaverse.AssetType.Unknown; - } + get { return AssetTypeFromCode(assetType); } } public InventoryType InventoryType @@ -102,6 +121,11 @@ namespace OpenSim.Framework : this((sbyte)assetType, inventoryType, contentType, null, extension) { } + + public TypeMapping(OpenSimAssetType assetType, InventoryType inventoryType, string contentType, string extension) + : this((sbyte)assetType, inventoryType, contentType, null, extension) + { + } } /// @@ -142,7 +166,9 @@ namespace OpenSim.Framework new TypeMapping(AssetType.CurrentOutfitFolder, InventoryType.Unknown, "application/vnd.ll.currentoutfitfolder", "currentoutfitfolder"), new TypeMapping(AssetType.OutfitFolder, InventoryType.Unknown, "application/vnd.ll.outfitfolder", "outfitfolder"), new TypeMapping(AssetType.MyOutfitsFolder, InventoryType.Unknown, "application/vnd.ll.myoutfitsfolder", "myoutfitsfolder"), - new TypeMapping(AssetType.Mesh, InventoryType.Mesh, "application/vnd.ll.mesh", "llm") + new TypeMapping(AssetType.Mesh, InventoryType.Mesh, "application/vnd.ll.mesh", "llm"), + + new TypeMapping(OpenSimAssetType.Material, InventoryType.Unknown, "application/llsd+xml", "material") }; private static Dictionary asset2Content; diff --git a/OpenSim/Framework/Serialization/ArchiveConstants.cs b/OpenSim/Framework/Serialization/ArchiveConstants.cs index 0c12787882..73ebfae234 100644 --- a/OpenSim/Framework/Serialization/ArchiveConstants.cs +++ b/OpenSim/Framework/Serialization/ArchiveConstants.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Text; using OpenMetaverse; +using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType; namespace OpenSim.Framework.Serialization { @@ -128,6 +129,7 @@ namespace OpenSim.Framework.Serialization ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Texture] = ASSET_EXTENSION_SEPARATOR + "texture.jp2"; ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.TextureTGA] = ASSET_EXTENSION_SEPARATOR + "texture.tga"; ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.TrashFolder] = ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)OpenSimAssetType.Material] = ASSET_EXTENSION_SEPARATOR + "material.xml"; // Not sure if we'll ever see this EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "animation.bvh"] = (sbyte)AssetType.Animation; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bodypart.txt"] = (sbyte)AssetType.Bodypart; @@ -152,6 +154,7 @@ namespace OpenSim.Framework.Serialization EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.jp2"] = (sbyte)AssetType.Texture; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.tga"] = (sbyte)AssetType.TextureTGA; EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"] = (sbyte)AssetType.TrashFolder; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "material.xml"] = (sbyte)OpenSimAssetType.Material; } public static string CreateOarLandDataPath(LandData ld) diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs index 6a5f8f397e..b270de9c89 100644 --- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs +++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs @@ -771,7 +771,7 @@ namespace OpenSim.Region.CoreModules.Asset UuidGatherer gatherer = new UuidGatherer(m_AssetService); HashSet uniqueUuids = new HashSet(); - Dictionary assets = new Dictionary(); + Dictionary assets = new Dictionary(); foreach (Scene s in m_Scenes) { @@ -794,7 +794,7 @@ namespace OpenSim.Region.CoreModules.Asset else if (storeUncached) { AssetBase cachedAsset = m_AssetService.Get(assetID.ToString()); - if (cachedAsset == null && assets[assetID] != AssetType.Unknown) + if (cachedAsset == null && assets[assetID] != (sbyte)AssetType.Unknown) m_log.DebugFormat( "[FLOTSAM ASSET CACHE]: Could not find asset {0}, type {1} referenced by object {2} at {3} in scene {4} when pre-caching all scene assets", assetID, assets[assetID], e.Name, e.AbsolutePosition, s.Name); diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs index 4ec8ae7aa7..429271916f 100644 --- a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs @@ -78,7 +78,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver /// /// Used to collect the uuids of the assets that we need to save into the archive /// - protected Dictionary m_assetUuids = new Dictionary(); + protected Dictionary m_assetUuids = new Dictionary(); /// /// Used to collect the uuids of the users that we need to save into the archive @@ -187,7 +187,7 @@ namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver // Don't chase down link asset items as they actually point to their target item IDs rather than an asset if (SaveAssets && itemAssetType != AssetType.Link && itemAssetType != AssetType.LinkFolder) - m_assetGatherer.GatherAssetUuids(inventoryItem.AssetID, (AssetType)inventoryItem.AssetType, m_assetUuids); + m_assetGatherer.GatherAssetUuids(inventoryItem.AssetID, (sbyte)inventoryItem.AssetType, m_assetUuids); } /// diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs index 04a0db6b47..09b19757ee 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs @@ -182,11 +182,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { string url = aCircuit.ServiceURLs["AssetServerURI"].ToString(); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: Incoming attachment {0} for HG user {1} with asset server {2}", so.Name, so.AttachedAvatar, url); - Dictionary ids = new Dictionary(); + Dictionary ids = new Dictionary(); HGUuidGatherer uuidGatherer = new HGUuidGatherer(Scene.AssetService, url); uuidGatherer.GatherAssetUuids(so, ids); - foreach (KeyValuePair kvp in ids) + foreach (KeyValuePair kvp in ids) uuidGatherer.FetchAsset(kvp.Key); } } diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs index b7a4d1ac1c..d4fb1baf48 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/HGAssetMapper.cs @@ -260,9 +260,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess // The act of gathering UUIDs downloads some assets from the remote server // but not all... - Dictionary ids = new Dictionary(); + Dictionary ids = new Dictionary(); HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, userAssetURL); - uuidGatherer.GatherAssetUuids(assetID, (AssetType)meta.Type, ids); + uuidGatherer.GatherAssetUuids(assetID, meta.Type, ids); m_log.DebugFormat("[HG ASSET MAPPER]: Preparing to get {0} assets", ids.Count); bool success = true; foreach (UUID uuid in ids.Keys) @@ -286,9 +286,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess AssetBase asset = m_scene.AssetService.Get(assetID.ToString()); if (asset != null) { - Dictionary ids = new Dictionary(); + Dictionary ids = new Dictionary(); HGUuidGatherer uuidGatherer = new HGUuidGatherer(m_scene.AssetService, string.Empty); - uuidGatherer.GatherAssetUuids(asset.FullID, (AssetType)asset.Type, ids); + uuidGatherer.GatherAssetUuids(asset.FullID, asset.Type, ids); bool success = false; foreach (UUID uuid in ids.Keys) { diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs index a990898570..7a844f49f4 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequest.cs @@ -178,7 +178,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Archive the regions - Dictionary assetUuids = new Dictionary(); + Dictionary assetUuids = new Dictionary(); scenesGroup.ForEachScene(delegate(Scene scene) { @@ -216,7 +216,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver } } - private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary assetUuids) + private void ArchiveOneRegion(Scene scene, string regionDir, Dictionary assetUuids) { m_log.InfoFormat("[ARCHIVER]: Writing region {0}", scene.RegionInfo.RegionName); @@ -276,16 +276,16 @@ namespace OpenSim.Region.CoreModules.World.Archiver RegionSettings regionSettings = scene.RegionInfo.RegionSettings; if (regionSettings.TerrainTexture1 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_1) - assetUuids[regionSettings.TerrainTexture1] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture1] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture2 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_2) - assetUuids[regionSettings.TerrainTexture2] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture2] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture3 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_3) - assetUuids[regionSettings.TerrainTexture3] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture3] = (sbyte)AssetType.Texture; if (regionSettings.TerrainTexture4 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_4) - assetUuids[regionSettings.TerrainTexture4] = AssetType.Texture; + assetUuids[regionSettings.TerrainTexture4] = (sbyte)AssetType.Texture; Save(scene, sceneObjects, regionDir); } diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs index 96000234ff..2d0da61145 100644 --- a/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs @@ -81,7 +81,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver /// /// uuids to request /// - protected IDictionary m_uuids; + protected IDictionary m_uuids; /// /// Callback used when all the assets requested have been received. @@ -115,7 +115,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver protected Dictionary m_options; protected internal AssetsRequest( - AssetsArchiver assetsArchiver, IDictionary uuids, + AssetsArchiver assetsArchiver, IDictionary uuids, IAssetService assetService, IUserAccountService userService, UUID scope, Dictionary options, AssetsRequestCallback assetsRequestCallback) @@ -154,7 +154,7 @@ namespace OpenSim.Region.CoreModules.World.Archiver m_requestCallbackTimer.Enabled = true; - foreach (KeyValuePair kvp in m_uuids) + foreach (KeyValuePair kvp in m_uuids) { // m_log.DebugFormat("[ARCHIVER]: Requesting asset {0}", kvp.Key); @@ -235,9 +235,8 @@ namespace OpenSim.Region.CoreModules.World.Archiver // Check for broken asset types and fix them with the AssetType gleaned by UuidGatherer if (fetchedAsset != null && fetchedAsset.Type == (sbyte)AssetType.Unknown) { - AssetType type = (AssetType)assetType; - m_log.InfoFormat("[ARCHIVER]: Rewriting broken asset type for {0} to {1}", fetchedAsset.ID, type); - fetchedAsset.Type = (sbyte)type; + m_log.InfoFormat("[ARCHIVER]: Rewriting broken asset type for {0} to {1}", fetchedAsset.ID, SLUtil.AssetTypeFromCode((sbyte)assetType)); + fetchedAsset.Type = (sbyte)assetType; } AssetRequestCallback(fetchedAssetID, this, fetchedAsset); diff --git a/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs b/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs index dd27294372..1e59e3f764 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/UuidGathererTests.cs @@ -62,8 +62,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests = AssetHelpers.CreateAsset(corruptAssetUuid, AssetType.Notecard, "CORRUPT ASSET", UUID.Zero); m_assetService.Store(corruptAsset); - IDictionary foundAssetUuids = new Dictionary(); - m_uuidGatherer.GatherAssetUuids(corruptAssetUuid, AssetType.Object, foundAssetUuids); + IDictionary foundAssetUuids = new Dictionary(); + m_uuidGatherer.GatherAssetUuids(corruptAssetUuid, (sbyte)AssetType.Object, foundAssetUuids); // We count the uuid as gathered even if the asset itself is corrupt. Assert.That(foundAssetUuids.Count, Is.EqualTo(1)); @@ -78,9 +78,9 @@ namespace OpenSim.Region.Framework.Scenes.Tests TestHelpers.InMethod(); UUID missingAssetUuid = UUID.Parse("00000000-0000-0000-0000-000000000666"); - IDictionary foundAssetUuids = new Dictionary(); + IDictionary foundAssetUuids = new Dictionary(); - m_uuidGatherer.GatherAssetUuids(missingAssetUuid, AssetType.Object, foundAssetUuids); + m_uuidGatherer.GatherAssetUuids(missingAssetUuid, (sbyte)AssetType.Object, foundAssetUuids); // We count the uuid as gathered even if the asset itself is missing. Assert.That(foundAssetUuids.Count, Is.EqualTo(1)); @@ -103,8 +103,8 @@ namespace OpenSim.Region.Framework.Scenes.Tests AssetBase ncAsset = AssetHelpers.CreateNotecardAsset(ncAssetId, soAssetId.ToString()); m_assetService.Store(ncAsset); - IDictionary foundAssetUuids = new Dictionary(); - m_uuidGatherer.GatherAssetUuids(ncAssetId, AssetType.Notecard, foundAssetUuids); + IDictionary foundAssetUuids = new Dictionary(); + m_uuidGatherer.GatherAssetUuids(ncAssetId, (sbyte)AssetType.Notecard, foundAssetUuids); // We count the uuid as gathered even if the asset itself is corrupt. Assert.That(foundAssetUuids.Count, Is.EqualTo(2)); diff --git a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs index 3e074b9cfc..42a19775ba 100644 --- a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs +++ b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs @@ -38,6 +38,7 @@ using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Services.Interfaces; +using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType; namespace OpenSim.Region.Framework.Scenes { @@ -83,7 +84,7 @@ namespace OpenSim.Region.Framework.Scenes /// The uuid of the asset for which to gather referenced assets /// The type of the asset for the uuid given /// The assets gathered - public void GatherAssetUuids(UUID assetUuid, AssetType assetType, IDictionary assetUuids) + public void GatherAssetUuids(UUID assetUuid, sbyte assetType, IDictionary assetUuids) { // avoid infinite loops if (assetUuids.ContainsKey(assetUuid)) @@ -93,23 +94,27 @@ namespace OpenSim.Region.Framework.Scenes { assetUuids[assetUuid] = assetType; - if (AssetType.Bodypart == assetType || AssetType.Clothing == assetType) + if ((sbyte)AssetType.Bodypart == assetType || (sbyte)AssetType.Clothing == assetType) { GetWearableAssetUuids(assetUuid, assetUuids); } - else if (AssetType.Gesture == assetType) + else if ((sbyte)AssetType.Gesture == assetType) { GetGestureAssetUuids(assetUuid, assetUuids); } - else if (AssetType.Notecard == assetType) + else if ((sbyte)AssetType.Notecard == assetType) { GetTextEmbeddedAssetUuids(assetUuid, assetUuids); } - else if (AssetType.LSLText == assetType) + else if ((sbyte)AssetType.LSLText == assetType) { GetTextEmbeddedAssetUuids(assetUuid, assetUuids); } - else if (AssetType.Object == assetType) + else if ((sbyte)OpenSimAssetType.Material == assetType) + { + GetMaterialAssetUuids(assetUuid, assetUuids); + } + else if ((sbyte)AssetType.Object == assetType) { GetSceneObjectAssetUuids(assetUuid, assetUuids); } @@ -136,7 +141,7 @@ namespace OpenSim.Region.Framework.Scenes /// A dictionary which is populated with the asset UUIDs gathered and the type of that asset. /// For assets where the type is not clear (e.g. UUIDs extracted from LSL and notecards), the type is Unknown. /// - public void GatherAssetUuids(SceneObjectGroup sceneObject, IDictionary assetUuids) + public void GatherAssetUuids(SceneObjectGroup sceneObject, IDictionary assetUuids) { // m_log.DebugFormat( // "[ASSET GATHERER]: Getting assets for object {0}, {1}", sceneObject.Name, sceneObject.UUID); @@ -156,7 +161,7 @@ namespace OpenSim.Region.Framework.Scenes { // Get the prim's default texture. This will be used for faces which don't have their own texture if (textureEntry.DefaultTexture != null) - assetUuids[textureEntry.DefaultTexture.TextureID] = AssetType.Texture; + assetUuids[textureEntry.DefaultTexture.TextureID] = (sbyte)AssetType.Texture; if (textureEntry.FaceTextures != null) { @@ -164,20 +169,20 @@ namespace OpenSim.Region.Framework.Scenes foreach (Primitive.TextureEntryFace texture in textureEntry.FaceTextures) { if (texture != null) - assetUuids[texture.TextureID] = AssetType.Texture; + assetUuids[texture.TextureID] = (sbyte)AssetType.Texture; } } } // If the prim is a sculpt then preserve this information too if (part.Shape.SculptTexture != UUID.Zero) - assetUuids[part.Shape.SculptTexture] = AssetType.Texture; + assetUuids[part.Shape.SculptTexture] = (sbyte)AssetType.Texture; if (part.Shape.ProjectionTextureUUID != UUID.Zero) - assetUuids[part.Shape.ProjectionTextureUUID] = AssetType.Texture; + assetUuids[part.Shape.ProjectionTextureUUID] = (sbyte)AssetType.Texture; if (part.CollisionSound != UUID.Zero) - assetUuids[part.CollisionSound] = AssetType.Sound; + assetUuids[part.CollisionSound] = (sbyte)AssetType.Sound; if (part.ParticleSystem.Length > 0) { @@ -185,7 +190,7 @@ namespace OpenSim.Region.Framework.Scenes { Primitive.ParticleSystem ps = new Primitive.ParticleSystem(part.ParticleSystem, 0); if (ps.Texture != UUID.Zero) - assetUuids[ps.Texture] = AssetType.Texture; + assetUuids[ps.Texture] = (sbyte)AssetType.Texture; } catch (Exception e) { @@ -205,7 +210,7 @@ namespace OpenSim.Region.Framework.Scenes // tii.Name, tii.Type, part.Name, part.UUID); if (!assetUuids.ContainsKey(tii.AssetID)) - GatherAssetUuids(tii.AssetID, (AssetType)tii.Type, assetUuids); + GatherAssetUuids(tii.AssetID, (sbyte)tii.Type, assetUuids); } // FIXME: We need to make gathering modular but we cannot yet, since gatherers are not guaranteed @@ -213,8 +218,6 @@ namespace OpenSim.Region.Framework.Scenes // inventory transfer. There needs to be a way for a module to register a method without assuming a // Scene.EventManager is present. // part.ParentGroup.Scene.EventManager.TriggerGatherUuids(part, assetUuids); - - GatherMaterialsUuids(part, assetUuids); } catch (Exception e) { @@ -225,7 +228,7 @@ namespace OpenSim.Region.Framework.Scenes } } } - + // /// // /// The callback made when we request the asset for an object from the asset service. // /// @@ -238,73 +241,6 @@ namespace OpenSim.Region.Framework.Scenes // Monitor.Pulse(this); // } // } - - /// - /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps - /// - /// - /// - public void GatherMaterialsUuids(SceneObjectPart part, IDictionary assetUuids) - { - // scan thru the dynAttrs map of this part for any textures used as materials - OSD osdMaterials = null; - - lock (part.DynAttrs) - { - if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) - { - OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); - - if (materialsStore == null) - return; - - materialsStore.TryGetValue("Materials", out osdMaterials); - } - - if (osdMaterials != null) - { - //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd)); - - if (osdMaterials is OSDArray) - { - OSDArray matsArr = osdMaterials as OSDArray; - foreach (OSDMap matMap in matsArr) - { - try - { - if (matMap.ContainsKey("Material")) - { - OSDMap mat = matMap["Material"] as OSDMap; - if (mat.ContainsKey("NormMap")) - { - UUID normalMapId = mat["NormMap"].AsUUID(); - if (normalMapId != UUID.Zero) - { - assetUuids[normalMapId] = AssetType.Texture; - //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString()); - } - } - if (mat.ContainsKey("SpecMap")) - { - UUID specularMapId = mat["SpecMap"].AsUUID(); - if (specularMapId != UUID.Zero) - { - assetUuids[specularMapId] = AssetType.Texture; - //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString()); - } - } - } - - } - catch (Exception e) - { - m_log.Warn("[UUID Gatherer]: exception getting materials: " + e.Message); - } - } - } - } - } - } /// /// Get an asset synchronously, potentially using an asynchronous callback. If the @@ -344,7 +280,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// Dictionary in which to record the references - private void GetTextEmbeddedAssetUuids(UUID embeddingAssetId, IDictionary assetUuids) + private void GetTextEmbeddedAssetUuids(UUID embeddingAssetId, IDictionary assetUuids) { // m_log.DebugFormat("[ASSET GATHERER]: Getting assets for uuid references in asset {0}", embeddingAssetId); @@ -364,7 +300,7 @@ namespace OpenSim.Region.Framework.Scenes // Embedded asset references (if not false positives) could be for many types of asset, so we will // label these as unknown. - assetUuids[uuid] = AssetType.Unknown; + assetUuids[uuid] = (sbyte)AssetType.Unknown; } } } @@ -374,7 +310,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// Dictionary in which to record the references - private void GetWearableAssetUuids(UUID wearableAssetUuid, IDictionary assetUuids) + private void GetWearableAssetUuids(UUID wearableAssetUuid, IDictionary assetUuids) { AssetBase assetBase = GetAsset(wearableAssetUuid); @@ -389,7 +325,7 @@ namespace OpenSim.Region.Framework.Scenes foreach (UUID uuid in wearableAsset.Textures.Values) { - assetUuids[uuid] = AssetType.Texture; + assetUuids[uuid] = (sbyte)AssetType.Texture; } } } @@ -401,7 +337,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - private void GetSceneObjectAssetUuids(UUID sceneObjectUuid, IDictionary assetUuids) + private void GetSceneObjectAssetUuids(UUID sceneObjectUuid, IDictionary assetUuids) { AssetBase objectAsset = GetAsset(sceneObjectUuid); @@ -430,7 +366,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// /// - private void GetGestureAssetUuids(UUID gestureUuid, IDictionary assetUuids) + private void GetGestureAssetUuids(UUID gestureUuid, IDictionary assetUuids) { AssetBase assetBase = GetAsset(gestureUuid); if (null == assetBase) @@ -464,9 +400,29 @@ namespace OpenSim.Region.Framework.Scenes // If it can be parsed as a UUID, it is an asset ID UUID uuid; if (UUID.TryParse(id, out uuid)) - assetUuids[uuid] = AssetType.Animation; + assetUuids[uuid] = (sbyte)AssetType.Animation; } } + + /// + /// Get the asset uuid's referenced in a material. + /// + private void GetMaterialAssetUuids(UUID materialUuid, IDictionary assetUuids) + { + AssetBase assetBase = GetAsset(materialUuid); + if (null == assetBase) + return; + + OSDMap mat = (OSDMap)OSDParser.DeserializeLLSDXml(assetBase.Data); + + UUID normMap = mat["NormMap"].AsUUID(); + if (normMap != UUID.Zero) + assetUuids[normMap] = (sbyte)AssetType.Texture; + + UUID specMap = mat["SpecMap"].AsUUID(); + if (specMap != UUID.Zero) + assetUuids[specMap] = (sbyte)AssetType.Texture; + } } public class HGUuidGatherer : UuidGatherer diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index e70715485e..09041e8085 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -42,77 +42,49 @@ using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; +using OpenSimAssetType = OpenSim.Framework.SLUtil.OpenSimAssetType; using Ionic.Zlib; // You will need to uncomment these lines if you are adding a region module to some other assembly which does not already // specify its assembly. Otherwise, the region modules in the assembly will not be picked up when OpenSimulator scans // the available DLLs -//[assembly: Addin("MaterialsDemoModule", "1.0")] +//[assembly: Addin("MaterialsModule", "1.0")] //[assembly: AddinDependency("OpenSim", "0.5")] -namespace OpenSim.Region.OptionalModules.MaterialsDemoModule +namespace OpenSim.Region.OptionalModules.Materials { - /// - /// - // # # ## ##### # # # # # #### - // # # # # # # ## # # ## # # # - // # # # # # # # # # # # # # # - // # ## # ###### ##### # # # # # # # # ### - // ## ## # # # # # ## # # ## # # - // # # # # # # # # # # # #### - // - // THIS MODULE IS FOR EXPERIMENTAL USE ONLY AND MAY CAUSE REGION OR ASSET CORRUPTION! - // - ////////////// WARNING ////////////////////////////////////////////////////////////////// - /// This is an *Experimental* module for developing support for materials-capable viewers - /// This module should NOT be used in a production environment! It may cause data corruption and - /// viewer crashes. It should be only used to evaluate implementations of materials. - /// - /// Materials are persisted via SceneObjectPart.dynattrs. This is a relatively new feature - /// of OpenSimulator and is not field proven at the time this module was written. Persistence - /// may fail or become corrupt and this could cause viewer crashes due to erroneous materials - /// data being sent to viewers. Materials descriptions might survive IAR, OAR, or other means - /// of archiving however the texture resources used by these materials probably will not as they - /// may not be adequately referenced to ensure proper archiving. - /// - /// - /// - /// To enable this module, add this string at the bottom of OpenSim.ini: - /// [MaterialsDemoModule] - /// - /// - /// - - [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsDemoModule")] - public class MaterialsDemoModule : INonSharedRegionModule + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MaterialsModule")] + public class MaterialsModule : INonSharedRegionModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - public string Name { get { return "MaterialsDemoModule"; } } + public string Name { get { return "MaterialsModule"; } } public Type ReplaceableInterface { get { return null; } } private Scene m_scene = null; private bool m_enabled = false; - public Dictionary m_knownMaterials = new Dictionary(); + public Dictionary m_regionMaterials = new Dictionary(); public void Initialise(IConfigSource source) { - m_enabled = (source.Configs["MaterialsDemoModule"] != null); + IConfig config = source.Configs["Materials"]; + if (config == null) + return; + + m_enabled = config.GetBoolean("enable_materials", true); if (!m_enabled) return; - m_log.DebugFormat("[MaterialsDemoModule]: Initialized"); + m_log.DebugFormat("[Materials]: Initialized"); } public void Close() { if (!m_enabled) return; - - //m_log.DebugFormat("[MaterialsDemoModule]: CLOSED MODULE"); } public void AddRegion(Scene scene) @@ -120,22 +92,19 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule if (!m_enabled) return; - //m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName); - m_scene = scene; m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; m_scene.EventManager.OnObjectAddedToScene += EventManager_OnObjectAddedToScene; -// m_scene.EventManager.OnGatherUuids += GatherMaterialsUuids; } - void EventManager_OnObjectAddedToScene(SceneObjectGroup obj) + private void EventManager_OnObjectAddedToScene(SceneObjectGroup obj) { foreach (var part in obj.Parts) if (part != null) - GetStoredMaterialsForPart(part); + GetStoredMaterialsInPart(part); } - void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps) + private void OnRegisterCaps(OpenMetaverse.UUID agentID, OpenSim.Framework.Capabilities.Caps caps) { string capsBase = "/CAPS/" + caps.CapsObjectPath; @@ -164,143 +133,65 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule m_scene.EventManager.OnRegisterCaps -= OnRegisterCaps; m_scene.EventManager.OnObjectAddedToScene -= EventManager_OnObjectAddedToScene; -// m_scene.EventManager.OnGatherUuids -= GatherMaterialsUuids; - - //m_log.DebugFormat("[MaterialsDemoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName); } public void RegionLoaded(Scene scene) { } - OSDMap GetMaterial(UUID id) - { - OSDMap map = null; - lock (m_knownMaterials) - { - if (m_knownMaterials.ContainsKey(id)) - { - map = new OSDMap(); - map["ID"] = OSD.FromBinary(id.GetBytes()); - map["Material"] = m_knownMaterials[id]; - } - } - return map; - } - - void GetStoredMaterialsForPart(SceneObjectPart part) + /// + /// Find the materials used in the SOP, and add them to 'm_regionMaterials'. + /// + private void GetStoredMaterialsInPart(SceneObjectPart part) { - OSD OSMaterials = null; - OSDArray matsArr = null; - - if (part.DynAttrs == null) - { - //m_log.Warn("[MaterialsDemoModule]: NULL DYNATTRS :( "); + if (part.Shape == null) return; - } - - lock (part.DynAttrs) - { - if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) - { - OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); - - if (materialsStore == null) - return; - - materialsStore.TryGetValue("Materials", out OSMaterials); - } - - if (OSMaterials != null && OSMaterials is OSDArray) - matsArr = OSMaterials as OSDArray; - else - return; - } - - //m_log.Info("[MaterialsDemoModule]: OSMaterials: " + OSDParser.SerializeJsonString(OSMaterials)); - - if (matsArr == null) - { - //m_log.Info("[MaterialsDemoModule]: matsArr is null :( "); + var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); + if (te == null) return; - } - foreach (OSD elemOsd in matsArr) + GetStoredMaterialInFace(part, te.DefaultTexture); + + foreach (Primitive.TextureEntryFace face in te.FaceTextures) { - if (elemOsd != null && elemOsd is OSDMap) - { - OSDMap matMap = elemOsd as OSDMap; - if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material")) - { - try - { - lock (m_knownMaterials) - m_knownMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"]; - } - catch (Exception e) - { - m_log.Warn("[MaterialsDemoModule]: exception decoding persisted material ", e); - } - } - } + if (face != null) + GetStoredMaterialInFace(part, face); } } - void StoreMaterialsForPart(SceneObjectPart part) + /// + /// Find the materials used in one Face, and add them to 'm_regionMaterials'. + /// + private void GetStoredMaterialInFace(SceneObjectPart part, Primitive.TextureEntryFace face) { - try + UUID id = face.MaterialID; + if (id == UUID.Zero) + return; + + lock (m_regionMaterials) { - if (part == null || part.Shape == null) + if (m_regionMaterials.ContainsKey(id)) return; - - Dictionary mats = new Dictionary(); - - Primitive.TextureEntry te = part.Shape.Textures; - - if (te.DefaultTexture != null) + + byte[] data = m_scene.AssetService.GetData(id.ToString()); + if (data == null) { - lock (m_knownMaterials) - { - if (m_knownMaterials.ContainsKey(te.DefaultTexture.MaterialID)) - mats[te.DefaultTexture.MaterialID] = m_knownMaterials[te.DefaultTexture.MaterialID]; - } - } - - if (te.FaceTextures != null) - { - foreach (var face in te.FaceTextures) - { - if (face != null) - { - lock (m_knownMaterials) - { - if (m_knownMaterials.ContainsKey(face.MaterialID)) - mats[face.MaterialID] = m_knownMaterials[face.MaterialID]; - } - } - } - } - if (mats.Count == 0) + m_log.WarnFormat("[Materials]: Prim \"{0}\" ({1}) contains unknown material ID {2}", part.Name, part.UUID, id); return; - - OSDArray matsArr = new OSDArray(); - foreach (KeyValuePair kvp in mats) - { - OSDMap matOsd = new OSDMap(); - matOsd["ID"] = OSD.FromUUID(kvp.Key); - matOsd["Material"] = kvp.Value; - matsArr.Add(matOsd); } - OSDMap OSMaterials = new OSDMap(); - OSMaterials["Materials"] = matsArr; + OSDMap mat; + try + { + mat = (OSDMap)OSDParser.DeserializeLLSDXml(data); + } + catch (Exception e) + { + m_log.WarnFormat("[Materials]: cannot decode material asset {0}: {1}", id, e.Message); + return; + } - lock (part.DynAttrs) - part.DynAttrs.SetStore("OpenSim", "Materials", OSMaterials); - } - catch (Exception e) - { - m_log.Warn("[MaterialsDemoModule]: exception in StoreMaterialsForPart() ", e); + m_regionMaterials[id] = mat; } } @@ -308,8 +199,6 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { - //m_log.Debug("[MaterialsDemoModule]: POST cap handler"); - OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); OSDMap resp = new OSDMap(); @@ -333,34 +222,38 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule { foreach (OSD elem in (OSDArray)osd) { - try { UUID id = new UUID(elem.AsBinary(), 0); - lock (m_knownMaterials) + lock (m_regionMaterials) { - if (m_knownMaterials.ContainsKey(id)) + if (m_regionMaterials.ContainsKey(id)) { - //m_log.Info("[MaterialsDemoModule]: request for known material ID: " + id.ToString()); OSDMap matMap = new OSDMap(); matMap["ID"] = OSD.FromBinary(id.GetBytes()); - - matMap["Material"] = m_knownMaterials[id]; + matMap["Material"] = m_regionMaterials[id]; respArr.Add(matMap); } else - m_log.Info("[MaterialsDemoModule]: request for UNKNOWN material ID: " + id.ToString()); + { + m_log.Warn("[Materials]: request for unknown material ID: " + id.ToString()); + + // Theoretically we could try to load the material from the assets service, + // but that shouldn't be necessary because the viewer should only request + // materials that exist in a prim on the region, and all of these materials + // are already stored in m_regionMaterials. + } } } catch (Exception e) { - // report something here? + m_log.Error("Error getting materials in response to viewer request", e); continue; } } } - else if (osd is OSDMap) // reqest to assign a material + else if (osd is OSDMap) // request to assign a material { materialsFromViewer = osd as OSDMap; @@ -375,94 +268,118 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule { foreach (OSDMap matsMap in matsArr) { - //m_log.Debug("[MaterialsDemoModule]: processing matsMap: " + OSDParser.SerializeJsonString(matsMap)); - uint primLocalID = 0; - try { primLocalID = matsMap["ID"].AsUInteger(); } - catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"ID\" from matsMap: " + e.Message); } - //m_log.Debug("[MaterialsDemoModule]: primLocalID: " + primLocalID.ToString()); + try { + primLocalID = matsMap["ID"].AsUInteger(); + } + catch (Exception e) { + m_log.Warn("[Materials]: cannot decode \"ID\" from matsMap: " + e.Message); + continue; + } OSDMap mat = null; - try { mat = matsMap["Material"] as OSDMap; } - catch (Exception e) { m_log.Warn("[MaterialsDemoModule]: cannot decode \"Material\" from matsMap: " + e.Message); } - //m_log.Debug("[MaterialsDemoModule]: mat: " + OSDParser.SerializeJsonString(mat)); + try + { + mat = matsMap["Material"] as OSDMap; + } + catch (Exception e) + { + m_log.Warn("[Materials]: cannot decode \"Material\" from matsMap: " + e.Message); + continue; + } + + SceneObjectPart sop = m_scene.GetSceneObjectPart(primLocalID); + if (sop == null) + { + m_log.WarnFormat("[Materials]: SOP not found for localId: {0}", primLocalID.ToString()); + continue; + } + + Primitive.TextureEntry te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length); + if (te == null) + { + m_log.WarnFormat("[Materials]: Error in TextureEntry for SOP {0} {1}", sop.Name, sop.UUID); + continue; + } + UUID id; if (mat == null) { + // This happens then the user removes a material from a prim id = UUID.Zero; } else { - id = HashOsd(mat); - lock (m_knownMaterials) - m_knownMaterials[id] = mat; + // Material UUID = hash of the material's data. + // This makes materials deduplicate across the entire grid (but isn't otherwise required). + byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat)); + using (var md5 = MD5.Create()) + id = new UUID(md5.ComputeHash(data), 0); + + lock (m_regionMaterials) + { + if (!m_regionMaterials.ContainsKey(id)) + { + m_regionMaterials[id] = mat; + + // This asset might exist already, but it's ok to try to store it again + string name = "Material " + ChooseMaterialName(mat, sop); + name = name.Substring(0, Math.Min(64, name.Length)).Trim(); + AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, sop.OwnerID.ToString()); + asset.Data = data; + m_scene.AssetService.Store(asset); + } + } } - var sop = m_scene.GetSceneObjectPart(primLocalID); - if (sop == null) - m_log.Debug("[MaterialsDemoModule]: null SOP for localId: " + primLocalID.ToString()); - else - { - var te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length); - if (te == null) + int face = -1; + + if (matsMap.ContainsKey("Face")) + { + face = matsMap["Face"].AsInteger(); + if (te.FaceTextures == null) // && face == 0) { - m_log.Debug("[MaterialsDemoModule]: null TextureEntry for localId: " + primLocalID.ToString()); + if (te.DefaultTexture == null) + m_log.WarnFormat("[Materials]: te.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID); + else + te.DefaultTexture.MaterialID = id; } else { - int face = -1; - - if (matsMap.ContainsKey("Face")) + if (te.FaceTextures.Length >= face - 1) { - face = matsMap["Face"].AsInteger(); - if (te.FaceTextures == null) // && face == 0) - { - if (te.DefaultTexture == null) - m_log.Debug("[MaterialsDemoModule]: te.DefaultTexture is null"); - else - te.DefaultTexture.MaterialID = id; - } - else - { - if (te.FaceTextures.Length >= face - 1) - { - if (te.FaceTextures[face] == null) - te.DefaultTexture.MaterialID = id; - else - te.FaceTextures[face].MaterialID = id; - } - } - } - else - { - if (te.DefaultTexture != null) + if (te.FaceTextures[face] == null) te.DefaultTexture.MaterialID = id; - } - - //m_log.DebugFormat("[MaterialsDemoModule]: in \"{0}\", setting material ID for face {1} to {2}", sop.Name, face, id); - - //we cant use sop.UpdateTextureEntry(te); because it filters so do it manually - - if (sop.ParentGroup != null) - { - sop.Shape.TextureEntry = te.GetBytes(); - sop.TriggerScriptChangedEvent(Changed.TEXTURE); - sop.UpdateFlag = UpdateRequired.FULL; - sop.ParentGroup.HasGroupChanged = true; - - sop.ScheduleFullUpdate(); - - StoreMaterialsForPart(sop); + else + te.FaceTextures[face].MaterialID = id; } } } + else + { + if (te.DefaultTexture != null) + te.DefaultTexture.MaterialID = id; + } + + //m_log.DebugFormat("[Materials]: in \"{0}\" {1}, setting material ID for face {2} to {3}", sop.Name, sop.UUID, face, id); + + // We can't use sop.UpdateTextureEntry(te) because it filters, so do it manually + sop.Shape.TextureEntry = te.GetBytes(); + + if (sop.ParentGroup != null) + { + sop.TriggerScriptChangedEvent(Changed.TEXTURE); + sop.UpdateFlag = UpdateRequired.FULL; + sop.ParentGroup.HasGroupChanged = true; + sop.ScheduleFullUpdate(); + } } } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception processing received material ", e); + m_log.Warn("[Materials]: exception processing received material ", e); } } } @@ -472,36 +389,63 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } catch (Exception e) { - m_log.Warn("[MaterialsDemoModule]: exception decoding zipped CAP payload ", e); + m_log.Warn("[Materials]: exception decoding zipped CAP payload ", e); //return ""; } - //m_log.Debug("[MaterialsDemoModule]: knownMaterials.Count: " + m_knownMaterials.Count.ToString()); } resp["Zipped"] = ZCompressOSD(respArr, false); string response = OSDParser.SerializeLLSDXmlString(resp); - //m_log.Debug("[MaterialsDemoModule]: cap request: " + request); - //m_log.Debug("[MaterialsDemoModule]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary())); - //m_log.Debug("[MaterialsDemoModule]: cap response: " + response); + //m_log.Debug("[Materials]: cap request: " + request); + //m_log.Debug("[Materials]: cap request (zipped portion): " + ZippedOsdBytesToString(req["Zipped"].AsBinary())); + //m_log.Debug("[Materials]: cap response: " + response); return response; } + /// + /// Use heuristics to choose a good name for the material. + /// + private string ChooseMaterialName(OSDMap mat, SceneObjectPart sop) + { + UUID normMap = mat["NormMap"].AsUUID(); + if (normMap != UUID.Zero) + { + AssetBase asset = m_scene.AssetService.GetCached(normMap.ToString()); + if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR")) + return asset.Name; + } + + UUID specMap = mat["SpecMap"].AsUUID(); + if (specMap != UUID.Zero) + { + AssetBase asset = m_scene.AssetService.GetCached(specMap.ToString()); + if ((asset != null) && (asset.Name.Length > 0) && !asset.Name.Equals("From IAR")) + return asset.Name; + } + + if (sop.Name != "Primitive") + return sop.Name; + + if ((sop.ParentGroup != null) && (sop.ParentGroup.Name != "Primitive")) + return sop.ParentGroup.Name; + + return ""; + } + public string RenderMaterialsGetCap(string request, string path, string param, IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { - //m_log.Debug("[MaterialsDemoModule]: GET cap handler"); - OSDMap resp = new OSDMap(); int matsCount = 0; OSDArray allOsd = new OSDArray(); - lock (m_knownMaterials) + lock (m_regionMaterials) { - foreach (KeyValuePair kvp in m_knownMaterials) + foreach (KeyValuePair kvp in m_regionMaterials) { OSDMap matMap = new OSDMap(); @@ -513,12 +457,11 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule } resp["Zipped"] = ZCompressOSD(allOsd, false); - //m_log.Debug("[MaterialsDemoModule]: matsCount: " + matsCount.ToString()); return OSDParser.SerializeLLSDXmlString(resp); } - static string ZippedOsdBytesToString(byte[] bytes) + private static string ZippedOsdBytesToString(byte[] bytes) { try { @@ -537,26 +480,27 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule /// private static UUID HashOsd(OSD osd) { + byte[] data = OSDParser.SerializeLLSDBinary(osd, false); using (var md5 = MD5.Create()) - using (MemoryStream ms = new MemoryStream(OSDParser.SerializeLLSDBinary(osd, false))) - return new UUID(md5.ComputeHash(ms), 0); + return new UUID(md5.ComputeHash(data), 0); } public static OSD ZCompressOSD(OSD inOsd, bool useHeader) { OSD osd = null; + byte[] data = OSDParser.SerializeLLSDBinary(inOsd, useHeader); + using (MemoryStream msSinkCompressed = new MemoryStream()) { using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkCompressed, Ionic.Zlib.CompressionMode.Compress, CompressionLevel.BestCompression, true)) { - CopyStream(new MemoryStream(OSDParser.SerializeLLSDBinary(inOsd, useHeader)), zOut); - zOut.Close(); + zOut.Write(data, 0, data.Length); } msSinkCompressed.Seek(0L, SeekOrigin.Begin); - osd = OSD.FromBinary( msSinkCompressed.ToArray()); + osd = OSD.FromBinary(msSinkCompressed.ToArray()); } return osd; @@ -571,94 +515,14 @@ namespace OpenSim.Region.OptionalModules.MaterialsDemoModule { using (Ionic.Zlib.ZlibStream zOut = new Ionic.Zlib.ZlibStream(msSinkUnCompressed, CompressionMode.Decompress, true)) { - CopyStream(new MemoryStream(input), zOut); - zOut.Close(); + zOut.Write(input, 0, input.Length); } + msSinkUnCompressed.Seek(0L, SeekOrigin.Begin); osd = OSDParser.DeserializeLLSDBinary(msSinkUnCompressed.ToArray()); } return osd; } - - static void CopyStream(System.IO.Stream input, System.IO.Stream output) - { - byte[] buffer = new byte[2048]; - int len; - while ((len = input.Read(buffer, 0, 2048)) > 0) - { - output.Write(buffer, 0, len); - } - - output.Flush(); - } - - // FIXME: This code is currently still in UuidGatherer since we cannot use Scene.EventManager as some - // calls to the gatherer are done for objects with no scene. -// /// -// /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps -// /// -// /// -// /// -// private void GatherMaterialsUuids(SceneObjectPart part, IDictionary assetUuids) -// { -// // scan thru the dynAttrs map of this part for any textures used as materials -// OSD osdMaterials = null; -// -// lock (part.DynAttrs) -// { -// if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) -// { -// OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); -// if (materialsStore == null) -// return; -// -// materialsStore.TryGetValue("Materials", out osdMaterials); -// } -// -// if (osdMaterials != null) -// { -// //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd)); -// -// if (osdMaterials is OSDArray) -// { -// OSDArray matsArr = osdMaterials as OSDArray; -// foreach (OSDMap matMap in matsArr) -// { -// try -// { -// if (matMap.ContainsKey("Material")) -// { -// OSDMap mat = matMap["Material"] as OSDMap; -// if (mat.ContainsKey("NormMap")) -// { -// UUID normalMapId = mat["NormMap"].AsUUID(); -// if (normalMapId != UUID.Zero) -// { -// assetUuids[normalMapId] = AssetType.Texture; -// //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString()); -// } -// } -// if (mat.ContainsKey("SpecMap")) -// { -// UUID specularMapId = mat["SpecMap"].AsUUID(); -// if (specularMapId != UUID.Zero) -// { -// assetUuids[specularMapId] = AssetType.Texture; -// //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString()); -// } -// } -// } -// -// } -// catch (Exception e) -// { -// m_log.Warn("[MaterialsDemoModule]: exception getting materials: " + e.Message); -// } -// } -// } -// } -// } -// } } } diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 28c16cf5dc..28369a3ae1 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -691,6 +691,12 @@ ; enable_windlight = false +[Materials] + ;# {enable_materials} {} {Enable Materials support?} {true false} true + ;; This enables the use of Materials. + ; enable_materials = true + + [DataSnapshot] ;# {index_sims} {} {Enable data snapshotting (search)?} {true false} false ;; The following set of configs pertains to search. From 68d83425c6b39614210b28e97d5006a882ea3097 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Thu, 12 Dec 2013 15:14:24 +0200 Subject: [PATCH 27/49] When asked to change the Material for one face, change only that face; not the default material --- .../Materials/MaterialsModule.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index 09041e8085..97795941aa 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -339,27 +339,14 @@ namespace OpenSim.Region.OptionalModules.Materials if (matsMap.ContainsKey("Face")) { face = matsMap["Face"].AsInteger(); - if (te.FaceTextures == null) // && face == 0) - { - if (te.DefaultTexture == null) - m_log.WarnFormat("[Materials]: te.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID); - else - te.DefaultTexture.MaterialID = id; - } - else - { - if (te.FaceTextures.Length >= face - 1) - { - if (te.FaceTextures[face] == null) - te.DefaultTexture.MaterialID = id; - else - te.FaceTextures[face].MaterialID = id; - } - } + Primitive.TextureEntryFace faceEntry = te.CreateFace((uint)face); + faceEntry.MaterialID = id; } else { - if (te.DefaultTexture != null) + if (te.DefaultTexture == null) + m_log.WarnFormat("[Materials]: TextureEntry.DefaultTexture is null in {0} {1}", sop.Name, sop.UUID); + else te.DefaultTexture.MaterialID = id; } From d1f16c4b4b3f5c0938f3f0572c70e92cb90b6a0b Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Sun, 5 Jan 2014 14:03:10 +0200 Subject: [PATCH 28/49] Check agent permissions before modifying an object's materials. Also, when creating a Material asset, set the current agent as the Creator. --- .../Materials/MaterialsModule.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index 97795941aa..4b635d8c72 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -109,7 +109,10 @@ namespace OpenSim.Region.OptionalModules.Materials string capsBase = "/CAPS/" + caps.CapsObjectPath; IRequestHandler renderMaterialsPostHandler - = new RestStreamHandler("POST", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null); + = new RestStreamHandler("POST", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsPostCap(request, agentID), + "RenderMaterials", null); caps.RegisterHandler("RenderMaterials", renderMaterialsPostHandler); // OpenSimulator CAPs infrastructure seems to be somewhat hostile towards any CAP that requires both GET @@ -117,12 +120,18 @@ namespace OpenSim.Region.OptionalModules.Materials // handler normally and then add a GET handler via MainServer IRequestHandler renderMaterialsGetHandler - = new RestStreamHandler("GET", capsBase + "/", RenderMaterialsGetCap, "RenderMaterials", null); + = new RestStreamHandler("GET", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsGetCap(request), + "RenderMaterials", null); MainServer.Instance.AddStreamHandler(renderMaterialsGetHandler); // materials viewer seems to use either POST or PUT, so assign POST handler for PUT as well IRequestHandler renderMaterialsPutHandler - = new RestStreamHandler("PUT", capsBase + "/", RenderMaterialsPostCap, "RenderMaterials", null); + = new RestStreamHandler("PUT", capsBase + "/", + (request, path, param, httpRequest, httpResponse) + => RenderMaterialsPostCap(request, agentID), + "RenderMaterials", null); MainServer.Instance.AddStreamHandler(renderMaterialsPutHandler); } @@ -195,9 +204,7 @@ namespace OpenSim.Region.OptionalModules.Materials } } - public string RenderMaterialsPostCap(string request, string path, - string param, IOSHttpRequest httpRequest, - IOSHttpResponse httpResponse) + public string RenderMaterialsPostCap(string request, UUID agentID) { OSDMap req = (OSDMap)OSDParser.DeserializeLLSDXml(request); OSDMap resp = new OSDMap(); @@ -295,6 +302,12 @@ namespace OpenSim.Region.OptionalModules.Materials continue; } + if (!m_scene.Permissions.CanEditObject(sop.UUID, agentID)) + { + m_log.WarnFormat("User {0} can't edit object {1} {2}", agentID, sop.Name, sop.UUID); + continue; + } + Primitive.TextureEntry te = new Primitive.TextureEntry(sop.Shape.TextureEntry, 0, sop.Shape.TextureEntry.Length); if (te == null) { @@ -326,7 +339,7 @@ namespace OpenSim.Region.OptionalModules.Materials // This asset might exist already, but it's ok to try to store it again string name = "Material " + ChooseMaterialName(mat, sop); name = name.Substring(0, Math.Min(64, name.Length)).Trim(); - AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, sop.OwnerID.ToString()); + AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString()); asset.Data = data; m_scene.AssetService.Store(asset); } @@ -422,9 +435,7 @@ namespace OpenSim.Region.OptionalModules.Materials } - public string RenderMaterialsGetCap(string request, string path, - string param, IOSHttpRequest httpRequest, - IOSHttpResponse httpResponse) + public string RenderMaterialsGetCap(string request) { OSDMap resp = new OSDMap(); int matsCount = 0; From 28723beb0ccec654ac24ee1632b137b424fd3360 Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 02:57:08 -0800 Subject: [PATCH 29/49] Add code to convert legacy materials stored in DynAttrs to new asset format and store them as assets --- .../Materials/MaterialsModule.cs | 122 +++++++++++++++--- 1 file changed, 102 insertions(+), 20 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index 4b635d8c72..1b5a7a37d1 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -148,6 +148,78 @@ namespace OpenSim.Region.OptionalModules.Materials { } + /// + /// Searches the part for any legacy materials stored in DynAttrs and converts them to assets, replacing + /// the MaterialIDs in the TextureEntries for the part. + /// Deletes the legacy materials from the part as they are no longer needed. + /// + /// + private void ConvertLegacyMaterialsInPart(SceneObjectPart part) + { + if (part.DynAttrs == null) + return; + + var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); + if (te == null) + return; + + OSD OSMaterials = null; + OSDArray matsArr = null; + + lock (part.DynAttrs) + { + if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) + { + OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); + + if (materialsStore == null) + return; + + materialsStore.TryGetValue("Materials", out OSMaterials); + } + + if (OSMaterials != null && OSMaterials is OSDArray) + matsArr = OSMaterials as OSDArray; + else + return; + } + + if (matsArr == null) + return; + + foreach (OSD elemOsd in matsArr) + { + if (elemOsd != null && elemOsd is OSDMap) + { + OSDMap matMap = elemOsd as OSDMap; + if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material")) + { + UUID id = matMap["ID"].AsUUID(); + OSDMap material = (OSDMap)matMap["Material"]; + bool used = false; + + foreach (var face in te.FaceTextures) + if (face.MaterialID == id) + used = true; + + if (used) + { // store legacy material in new asset format, and update the part texture entry with the new hashed UUID + + var newId = StoreMaterialAsAsset(part.CreatorID, material, part); + foreach (var face in te.FaceTextures) + if (face.MaterialID == id) + face.MaterialID = newId; + } + } + } + } + + part.Shape.TextureEntry = te.GetBytes(); + + lock (part.DynAttrs) + part.DynAttrs.RemoveStore("OpenSim", "Materials"); + } + /// /// Find the materials used in the SOP, and add them to 'm_regionMaterials'. /// @@ -155,6 +227,9 @@ namespace OpenSim.Region.OptionalModules.Materials { if (part.Shape == null) return; + + ConvertLegacyMaterialsInPart(part); + var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); if (te == null) return; @@ -324,26 +399,7 @@ namespace OpenSim.Region.OptionalModules.Materials } else { - // Material UUID = hash of the material's data. - // This makes materials deduplicate across the entire grid (but isn't otherwise required). - byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat)); - using (var md5 = MD5.Create()) - id = new UUID(md5.ComputeHash(data), 0); - - lock (m_regionMaterials) - { - if (!m_regionMaterials.ContainsKey(id)) - { - m_regionMaterials[id] = mat; - - // This asset might exist already, but it's ok to try to store it again - string name = "Material " + ChooseMaterialName(mat, sop); - name = name.Substring(0, Math.Min(64, name.Length)).Trim(); - AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString()); - asset.Data = data; - m_scene.AssetService.Store(asset); - } - } + id = StoreMaterialAsAsset(agentID, mat, sop); } @@ -404,6 +460,32 @@ namespace OpenSim.Region.OptionalModules.Materials return response; } + private UUID StoreMaterialAsAsset(UUID agentID, OSDMap mat, SceneObjectPart sop) + { + UUID id; + // Material UUID = hash of the material's data. + // This makes materials deduplicate across the entire grid (but isn't otherwise required). + byte[] data = System.Text.Encoding.ASCII.GetBytes(OSDParser.SerializeLLSDXmlString(mat)); + using (var md5 = MD5.Create()) + id = new UUID(md5.ComputeHash(data), 0); + + lock (m_regionMaterials) + { + if (!m_regionMaterials.ContainsKey(id)) + { + m_regionMaterials[id] = mat; + + // This asset might exist already, but it's ok to try to store it again + string name = "Material " + ChooseMaterialName(mat, sop); + name = name.Substring(0, Math.Min(64, name.Length)).Trim(); + AssetBase asset = new AssetBase(id, name, (sbyte)OpenSimAssetType.Material, agentID.ToString()); + asset.Data = data; + m_scene.AssetService.Store(asset); + } + } + return id; + } + /// /// Use heuristics to choose a good name for the material. /// From 95c926b2cd8585dd5b84ad7827d21b6122ea1001 Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 03:02:30 -0800 Subject: [PATCH 30/49] delay texture entry parsing until absolutely necessary while converting legacy materials --- .../Region/OptionalModules/Materials/MaterialsModule.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index 1b5a7a37d1..d8ec9795dd 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -159,10 +159,6 @@ namespace OpenSim.Region.OptionalModules.Materials if (part.DynAttrs == null) return; - var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); - if (te == null) - return; - OSD OSMaterials = null; OSDArray matsArr = null; @@ -187,6 +183,10 @@ namespace OpenSim.Region.OptionalModules.Materials if (matsArr == null) return; + var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); + if (te == null) + return; + foreach (OSD elemOsd in matsArr) { if (elemOsd != null && elemOsd is OSDMap) From 36d8a24a867fbbc95214653fec463aced8ba7c5f Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 03:11:01 -0800 Subject: [PATCH 31/49] force SOG update when converting legacy materials to ensure changes are persisted --- OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index d8ec9795dd..ce2a56abda 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -215,6 +215,8 @@ namespace OpenSim.Region.OptionalModules.Materials } part.Shape.TextureEntry = te.GetBytes(); + part.ParentGroup.HasGroupChanged = true; + part.ScheduleFullUpdate(); lock (part.DynAttrs) part.DynAttrs.RemoveStore("OpenSim", "Materials"); From 4800303abdaff19471988097c89193ff6cc4b24e Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 03:18:36 -0800 Subject: [PATCH 32/49] update OpenMetaverse.StructuredData to git master (bf4e9f654ff99c85e20b53e56faac38e307dd8c2) which fixes JSON OSD serialization to a standards compliant means of encoding floating point NaN and Infinity --- bin/OpenMetaverse.StructuredData.XML | 234 +++++++++++++-------------- bin/OpenMetaverse.StructuredData.dll | Bin 106496 -> 106496 bytes 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/bin/OpenMetaverse.StructuredData.XML b/bin/OpenMetaverse.StructuredData.XML index 789ad5b811..3999d9977d 100644 --- a/bin/OpenMetaverse.StructuredData.XML +++ b/bin/OpenMetaverse.StructuredData.XML @@ -4,123 +4,6 @@ OpenMetaverse.StructuredData - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Uses reflection to create an SDMap from all of the SD - serializable types in an object - - Class or struct containing serializable types - An SDMap holding the serialized values from the - container object - - - - Uses reflection to deserialize member variables in an object from - an SDMap - - Reference to an object to fill with deserialized - values - Serialized values to put in the target - object - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -345,5 +228,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uses reflection to create an SDMap from all of the SD + serializable types in an object + + Class or struct containing serializable types + An SDMap holding the serialized values from the + container object + + + + Uses reflection to deserialize member variables in an object from + an SDMap + + Reference to an object to fill with deserialized + values + Serialized values to put in the target + object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/OpenMetaverse.StructuredData.dll b/bin/OpenMetaverse.StructuredData.dll index d980531e18d08cdd5c971bd110479c3e4705cd94..ed613e710f0803df6ea03f2679ed6258d1caeb16 100755 GIT binary patch literal 106496 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 Oz>EZDB=G;H1pWt+xKmI7 From 8e72b53edc435c2c2fbec0b8c91304e7f7a6a4f2 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 20 Jan 2014 19:16:19 +0000 Subject: [PATCH 33/49] Stop exceptions being generated on agent connection if a telehub object has been deleted or has no spawn points. --- OpenSim/Framework/RegionSettings.cs | 21 ++-- .../World/Estate/EstateManagementCommands.cs | 2 +- .../World/Estate/EstateManagementModule.cs | 8 +- OpenSim/Region/Framework/Scenes/Scene.cs | 50 +++++--- .../Scenes/Tests/SceneTelehubTests.cs | 119 ++++++++++++++++++ OpenSim/Tests/Common/Mock/TestClient.cs | 5 - OpenSim/Tests/Common/TestHelpers.cs | 21 +++- 7 files changed, 186 insertions(+), 40 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs diff --git a/OpenSim/Framework/RegionSettings.cs b/OpenSim/Framework/RegionSettings.cs index db8c53ef95..a895c40cc1 100644 --- a/OpenSim/Framework/RegionSettings.cs +++ b/OpenSim/Framework/RegionSettings.cs @@ -482,21 +482,14 @@ namespace OpenSim.Framework set { m_LoadedCreationID = value; } } - // Connected Telehub object - private UUID m_TelehubObject = UUID.Zero; - public UUID TelehubObject - { - get - { - return m_TelehubObject; - } - set - { - m_TelehubObject = value; - } - } + ///

+ /// Connected Telehub object + /// + public UUID TelehubObject { get; set; } - // Our Connected Telehub's SpawnPoints + /// + /// Our connected Telehub's SpawnPoints + /// public List l_SpawnPoints = new List(); // Add a SpawnPoint diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs index 173b603fe5..1659493981 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs @@ -60,7 +60,7 @@ namespace OpenSim.Region.CoreModules.World.Estate public void Initialise() { - m_log.DebugFormat("[ESTATE MODULE]: Setting up estate commands for region {0}", m_module.Scene.RegionInfo.RegionName); +// m_log.DebugFormat("[ESTATE MODULE]: Setting up estate commands for region {0}", m_module.Scene.RegionInfo.RegionName); m_module.Scene.AddCommand("Regions", m_module, "set terrain texture", "set terrain texture [] []", diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs index 17387da751..3bd7b4a218 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs @@ -702,7 +702,7 @@ namespace OpenSim.Region.CoreModules.World.Estate } } - public void handleOnEstateManageTelehub(IClientAPI client, UUID invoice, UUID senderID, string cmd, uint param1) + public void HandleOnEstateManageTelehub(IClientAPI client, UUID invoice, UUID senderID, string cmd, uint param1) { SceneObjectPart part; @@ -742,7 +742,9 @@ namespace OpenSim.Region.CoreModules.World.Estate default: break; } - SendTelehubInfo(client); + + if (client != null) + SendTelehubInfo(client); } private void SendSimulatorBlueBoxMessage( @@ -1214,7 +1216,7 @@ namespace OpenSim.Region.CoreModules.World.Estate client.OnEstateRestartSimRequest += handleEstateRestartSimRequest; client.OnEstateChangeCovenantRequest += handleChangeEstateCovenantRequest; client.OnEstateChangeInfo += handleEstateChangeInfo; - client.OnEstateManageTelehub += handleOnEstateManageTelehub; + client.OnEstateManageTelehub += HandleOnEstateManageTelehub; client.OnUpdateEstateAccessDeltaRequest += handleEstateAccessDeltaRequest; client.OnSimulatorBlueBoxMessageRequest += SendSimulatorBlueBoxMessage; client.OnEstateBlueBoxMessageRequest += SendEstateBlueBoxMessage; diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 567ce2ad03..59c5b0922e 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -3946,32 +3946,52 @@ namespace OpenSim.Region.Framework.Scenes } } +// m_log.DebugFormat( +// "[SCENE]: Found telehub object {0} for new user connection {1} to {2}", +// RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + // Honor Estate teleport routing via Telehubs excluding ViaHome and GodLike TeleportFlags if (RegionInfo.RegionSettings.TelehubObject != UUID.Zero && RegionInfo.EstateSettings.AllowDirectTeleport == false && !viahome && !godlike) { SceneObjectGroup telehub = GetSceneObjectGroup(RegionInfo.RegionSettings.TelehubObject); - // Can have multiple SpawnPoints - List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); - if (spawnpoints.Count > 1) + + if (telehub != null) { - // We have multiple SpawnPoints, Route the agent to a random or sequential one - if (SpawnPointRouting == "random") - acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + // Can have multiple SpawnPoints + List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); + if (spawnpoints.Count > 1) + { + // We have multiple SpawnPoints, Route the agent to a random or sequential one + if (SpawnPointRouting == "random") + acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + else + acd.startpos = spawnpoints[SpawnPoint()].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + } + else if (spawnpoints.Count == 1) + { + // We have a single SpawnPoint and will route the agent to it + acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + } else - acd.startpos = spawnpoints[SpawnPoint()].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + { + m_log.DebugFormat( + "[SCENE]: No spawnpoints defined for telehub {0} for {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + } } else { - // We have a single SpawnPoint and will route the agent to it - acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + m_log.DebugFormat( + "[SCENE]: No telehub {0} found to direct {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); } return true; diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs new file mode 100644 index 0000000000..9a97acc176 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs @@ -0,0 +1,119 @@ +/* + * 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 Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Estate; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene telehub tests + /// + /// + /// TODO: Tests which run through normal functionality. Currently, the only test is one that checks behaviour + /// in the case of an error condition + /// + [TestFixture] + public class SceneTelehubTests : OpenSimTestCase + { + /// + /// Test for desired behaviour when a telehub has no spawn points + /// + [Test] + public void TestNoTelehubSpawnPoints() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + + /// + /// Test for desired behaviour when the scene object nominated as a telehub object does not exist. + /// + [Test] + public void TestNoTelehubSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + SceneObjectGroup spawnPointSo = SceneHelpers.AddSceneObject(scene, "spawnpointObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "spawnpoint add", spawnPointSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + scene.DeleteSceneObject(telehubSo, false); + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/OpenSim/Tests/Common/Mock/TestClient.cs b/OpenSim/Tests/Common/Mock/TestClient.cs index 937010285f..a4247e303a 100644 --- a/OpenSim/Tests/Common/Mock/TestClient.cs +++ b/OpenSim/Tests/Common/Mock/TestClient.cs @@ -788,11 +788,6 @@ namespace OpenSim.Tests.Common.Mock { OnRegionHandShakeReply(this); } - - if (OnCompleteMovementToRegion != null) - { - OnCompleteMovementToRegion(this, true); - } } public void SendAssetUploadCompleteMessage(sbyte AssetType, bool Success, UUID AssetFullID) diff --git a/OpenSim/Tests/Common/TestHelpers.cs b/OpenSim/Tests/Common/TestHelpers.cs index a684d72e7f..6bf23f8e67 100644 --- a/OpenSim/Tests/Common/TestHelpers.cs +++ b/OpenSim/Tests/Common/TestHelpers.cs @@ -117,8 +117,6 @@ namespace OpenSim.Tests.Common /// Parse a UUID stem into a full UUID. ///
/// - /// Yes, this is completely inconsistent with ParseTail but this is probably a better way to do it, - /// UUIDs are conceptually not hexadecmial numbers. /// The fragment will come at the start of the UUID. The rest will be 0s /// /// @@ -143,5 +141,24 @@ namespace OpenSim.Tests.Common { return new UUID(string.Format("00000000-0000-0000-0000-{0:X12}", tail)); } + + /// + /// Parse a UUID tail section into a full UUID. + /// + /// + /// The fragment will come at the end of the UUID. The rest will be 0s + /// + /// + /// + /// A UUID fragment that will be parsed into a full UUID. Therefore, it can only contain + /// cahracters which are valid in a UUID, except for "-" which is currently only allowed if a full UUID is + /// given as the 'fragment'. + /// + public static UUID ParseTail(string stem) + { + string rawUuid = stem.PadLeft(32, '0'); + + return UUID.Parse(rawUuid); + } } } From 2e78e89c36e661f72773e54f97bec3f04af67b79 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Mon, 20 Jan 2014 11:33:49 -0800 Subject: [PATCH 34/49] Clean up orphaned json stores. This can happen when an object is removed, when a script is removed, or when a script is reset. Also added a stats command to track the number of json stores used by a region. Will probably add some more commands later. --- .../Framework/Interfaces/IJsonStoreModule.cs | 7 + .../Scripting/JsonStore/JsonStoreCommands.cs | 195 ++++++++++++++++++ .../Scripting/JsonStore/JsonStoreModule.cs | 41 +++- .../JsonStore/JsonStoreScriptModule.cs | 41 +++- 4 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs diff --git a/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs b/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs index b67312e0b4..1a897219df 100644 --- a/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs +++ b/OpenSim/Region/Framework/Interfaces/IJsonStoreModule.cs @@ -51,10 +51,17 @@ namespace OpenSim.Region.Framework.Interfaces UUID = 5 } + public struct JsonStoreStats + { + public int StoreCount; + } + public delegate void TakeValueCallback(string s); public interface IJsonStoreModule { + JsonStoreStats GetStoreStats(); + bool AttachObjectStore(UUID objectID); bool CreateStore(string value, ref UUID result); bool DestroyStore(UUID storeID); diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs new file mode 100644 index 0000000000..d4b19ddd91 --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreCommands.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) Contributors + * 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 OpenSim 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 Mono.Addins; + +using System; +using System.Reflection; +using System.Threading; +using System.Text; +using System.Net; +using System.Net.Sockets; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreCommandsModule")] + + public class JsonStoreCommandsModule : INonSharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private IConfig m_config = null; + private bool m_enabled = false; + + private Scene m_scene = null; + //private IJsonStoreModule m_store; + private JsonStoreModule m_store; + +#region Region Module interface + + // ----------------------------------------------------------------- + /// + /// Name of this shared module is it's class name + /// + // ----------------------------------------------------------------- + public string Name + { + get { return this.GetType().Name; } + } + + // ----------------------------------------------------------------- + /// + /// Initialise this shared module + /// + /// this region is getting initialised + /// nini config, we are not using this + // ----------------------------------------------------------------- + public void Initialise(IConfigSource config) + { + try + { + if ((m_config = config.Configs["JsonStore"]) == null) + { + // There is no configuration, the module is disabled + // m_log.InfoFormat("[JsonStore] no configuration info"); + return; + } + + m_enabled = m_config.GetBoolean("Enabled", m_enabled); + } + catch (Exception e) + { + m_log.Error("[JsonStore]: initialization error: {0}", e); + return; + } + + if (m_enabled) + m_log.DebugFormat("[JsonStore]: module is enabled"); + } + + // ----------------------------------------------------------------- + /// + /// everything is loaded, perform post load configuration + /// + // ----------------------------------------------------------------- + public void PostInitialise() + { + } + + // ----------------------------------------------------------------- + /// + /// Nothing to do on close + /// + // ----------------------------------------------------------------- + public void Close() + { + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void AddRegion(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + + } + } + + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public void RemoveRegion(Scene scene) + { + // need to remove all references to the scene in the subscription + // list to enable full garbage collection of the scene object + } + + // ----------------------------------------------------------------- + /// + /// Called when all modules have been added for a region. This is + /// where we hook up events + /// + // ----------------------------------------------------------------- + public void RegionLoaded(Scene scene) + { + if (m_enabled) + { + m_scene = scene; + + m_store = (JsonStoreModule) m_scene.RequestModuleInterface(); + if (m_store == null) + { + m_log.ErrorFormat("[JsonStoreCommands]: JsonModule interface not defined"); + m_enabled = false; + return; + } + + scene.AddCommand("JsonStore", this, "jsonstore stats", "jsonstore stats", + "Display statistics about the state of the JsonStore module", "", + CmdStats); + } + } + + /// ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + public Type ReplaceableInterface + { + get { return null; } + } + +#endregion + +#region Commands + + private void CmdStats(string module, string[] cmd) + { + if (MainConsole.Instance.ConsoleScene != m_scene && MainConsole.Instance.ConsoleScene != null) + return; + + JsonStoreStats stats = m_store.GetStoreStats(); + MainConsole.Instance.OutputFormat("{0}\t{1}",m_scene.RegionInfo.RegionName,stats.StoreCount); + } + +#endregion + + } +} diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs index 5fbfcc583a..b502a55508 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreModule.cs @@ -42,7 +42,6 @@ using OpenSim.Region.Framework.Scenes; using System.Collections.Generic; using System.Text.RegularExpressions; - namespace OpenSim.Region.OptionalModules.Scripting.JsonStore { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "JsonStoreModule")] @@ -60,6 +59,7 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore private Scene m_scene = null; private Dictionary m_JsonValueStore; + private UUID m_sharedStore; #region Region Module interface @@ -140,6 +140,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore m_sharedStore = UUID.Zero; m_JsonValueStore = new Dictionary(); m_JsonValueStore.Add(m_sharedStore,new JsonStore("")); + + scene.EventManager.OnObjectBeingRemovedFromScene += EventManagerOnObjectBeingRemovedFromScene; } } @@ -149,6 +151,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RemoveRegion(Scene scene) { + scene.EventManager.OnObjectBeingRemovedFromScene -= EventManagerOnObjectBeingRemovedFromScene; + // need to remove all references to the scene in the subscription // list to enable full garbage collection of the scene object } @@ -161,7 +165,9 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RegionLoaded(Scene scene) { - if (m_enabled) {} + if (m_enabled) + { + } } /// ----------------------------------------------------------------- @@ -175,8 +181,39 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore #endregion +#region SceneEvents + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public void EventManagerOnObjectBeingRemovedFromScene(SceneObjectGroup obj) + { + obj.ForEachPart(delegate(SceneObjectPart sop) { DestroyStore(sop.UUID); } ); + } + +#endregion + #region ScriptInvocationInteface + + // ----------------------------------------------------------------- + /// + /// + /// + // ----------------------------------------------------------------- + public JsonStoreStats GetStoreStats() + { + JsonStoreStats stats; + + lock (m_JsonValueStore) + { + stats.StoreCount = m_JsonValueStore.Count; + } + + return stats; + } + // ----------------------------------------------------------------- /// /// diff --git a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs index 1bb5aee796..9fbfb66b8e 100644 --- a/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs +++ b/OpenSim/Region/OptionalModules/Scripting/JsonStore/JsonStoreScriptModule.cs @@ -59,7 +59,9 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore private IScriptModuleComms m_comms; private IJsonStoreModule m_store; - + + private Dictionary> m_scriptStores = new Dictionary>(); + #region Region Module interface // ----------------------------------------------------------------- @@ -126,6 +128,8 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void AddRegion(Scene scene) { + scene.EventManager.OnScriptReset += HandleScriptReset; + scene.EventManager.OnRemoveScript += HandleScriptReset; } // ----------------------------------------------------------------- @@ -134,10 +138,32 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore // ----------------------------------------------------------------- public void RemoveRegion(Scene scene) { + scene.EventManager.OnScriptReset -= HandleScriptReset; + scene.EventManager.OnRemoveScript -= HandleScriptReset; + // need to remove all references to the scene in the subscription // list to enable full garbage collection of the scene object } + // ----------------------------------------------------------------- + /// + /// + // ----------------------------------------------------------------- + private void HandleScriptReset(uint localID, UUID itemID) + { + HashSet stores; + + lock (m_scriptStores) + { + if (! m_scriptStores.TryGetValue(itemID, out stores)) + return; + m_scriptStores.Remove(itemID); + } + + foreach (UUID id in stores) + m_store.DestroyStore(id); + } + // ----------------------------------------------------------------- /// /// Called when all modules have been added for a region. This is @@ -250,6 +276,13 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore if (! m_store.CreateStore(value, ref uuid)) GenerateRuntimeError("Failed to create Json store"); + lock (m_scriptStores) + { + if (! m_scriptStores.ContainsKey(scriptID)) + m_scriptStores[scriptID] = new HashSet(); + + m_scriptStores[scriptID].Add(uuid); + } return uuid; } @@ -261,6 +294,12 @@ namespace OpenSim.Region.OptionalModules.Scripting.JsonStore [ScriptInvocation] public int JsonDestroyStore(UUID hostID, UUID scriptID, UUID storeID) { + lock(m_scriptStores) + { + if (m_scriptStores.ContainsKey(scriptID)) + m_scriptStores[scriptID].Remove(storeID); + } + return m_store.DestroyStore(storeID) ? 1 : 0; } From 1cae3664a52fe48965954afc19804b11720c4add Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 11:53:33 -0800 Subject: [PATCH 35/49] add null texture entry face check before converting legacy materials --- OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index ce2a56abda..c4bc8a025e 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -199,7 +199,7 @@ namespace OpenSim.Region.OptionalModules.Materials bool used = false; foreach (var face in te.FaceTextures) - if (face.MaterialID == id) + if (face != null && face.MaterialID == id) used = true; if (used) @@ -207,7 +207,7 @@ namespace OpenSim.Region.OptionalModules.Materials var newId = StoreMaterialAsAsset(part.CreatorID, material, part); foreach (var face in te.FaceTextures) - if (face.MaterialID == id) + if (face != null && face.MaterialID == id) face.MaterialID = newId; } } From af58631f00b95081dc99f4f75e8ec6b031b8cf2a Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 13:57:14 -0800 Subject: [PATCH 36/49] rather than converting existing materials to assets, just retrieve them and make them available for viewing. Any new materials added to the scene will become assets. --- .../Materials/MaterialsModule.cs | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs index c4bc8a025e..afb788b8ae 100644 --- a/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs +++ b/OpenSim/Region/OptionalModules/Materials/MaterialsModule.cs @@ -149,12 +149,10 @@ namespace OpenSim.Region.OptionalModules.Materials } /// - /// Searches the part for any legacy materials stored in DynAttrs and converts them to assets, replacing - /// the MaterialIDs in the TextureEntries for the part. - /// Deletes the legacy materials from the part as they are no longer needed. + /// Finds any legacy materials stored in DynAttrs that may exist for this part and add them to 'm_regionMaterials'. /// /// - private void ConvertLegacyMaterialsInPart(SceneObjectPart part) + private void GetLegacyStoredMaterialsInPart(SceneObjectPart part) { if (part.DynAttrs == null) return; @@ -183,10 +181,6 @@ namespace OpenSim.Region.OptionalModules.Materials if (matsArr == null) return; - var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); - if (te == null) - return; - foreach (OSD elemOsd in matsArr) { if (elemOsd != null && elemOsd is OSDMap) @@ -194,32 +188,18 @@ namespace OpenSim.Region.OptionalModules.Materials OSDMap matMap = elemOsd as OSDMap; if (matMap.ContainsKey("ID") && matMap.ContainsKey("Material")) { - UUID id = matMap["ID"].AsUUID(); - OSDMap material = (OSDMap)matMap["Material"]; - bool used = false; - - foreach (var face in te.FaceTextures) - if (face != null && face.MaterialID == id) - used = true; - - if (used) - { // store legacy material in new asset format, and update the part texture entry with the new hashed UUID - - var newId = StoreMaterialAsAsset(part.CreatorID, material, part); - foreach (var face in te.FaceTextures) - if (face != null && face.MaterialID == id) - face.MaterialID = newId; + try + { + lock (m_regionMaterials) + m_regionMaterials[matMap["ID"].AsUUID()] = (OSDMap)matMap["Material"]; + } + catch (Exception e) + { + m_log.Warn("[Materials]: exception decoding persisted legacy material: " + e.ToString()); } } } } - - part.Shape.TextureEntry = te.GetBytes(); - part.ParentGroup.HasGroupChanged = true; - part.ScheduleFullUpdate(); - - lock (part.DynAttrs) - part.DynAttrs.RemoveStore("OpenSim", "Materials"); } /// @@ -230,12 +210,12 @@ namespace OpenSim.Region.OptionalModules.Materials if (part.Shape == null) return; - ConvertLegacyMaterialsInPart(part); - var te = new Primitive.TextureEntry(part.Shape.TextureEntry, 0, part.Shape.TextureEntry.Length); if (te == null) return; + GetLegacyStoredMaterialsInPart(part); + GetStoredMaterialInFace(part, te.DefaultTexture); foreach (Primitive.TextureEntryFace face in te.FaceTextures) From 7bd42fc42f0d945fe96b058d06f14c091d96b2d2 Mon Sep 17 00:00:00 2001 From: dahlia Date: Mon, 20 Jan 2014 15:01:18 -0800 Subject: [PATCH 37/49] Add back code to UuidGatherer to retrieve UUIDs for materials stored in DynAttrs. This is unfortunately still necessary until a better solution for handling existing legacy materials can be implemented --- .../Region/Framework/Scenes/UuidGatherer.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs index 42a19775ba..75a51b5e68 100644 --- a/OpenSim/Region/Framework/Scenes/UuidGatherer.cs +++ b/OpenSim/Region/Framework/Scenes/UuidGatherer.cs @@ -218,6 +218,10 @@ namespace OpenSim.Region.Framework.Scenes // inventory transfer. There needs to be a way for a module to register a method without assuming a // Scene.EventManager is present. // part.ParentGroup.Scene.EventManager.TriggerGatherUuids(part, assetUuids); + + + // still needed to retrieve textures used as materials for any parts containing legacy materials stored in DynAttrs + GatherMaterialsUuids(part, assetUuids); } catch (Exception e) { @@ -241,6 +245,75 @@ namespace OpenSim.Region.Framework.Scenes // Monitor.Pulse(this); // } // } + + /// + /// Gather all of the texture asset UUIDs used to reference "Materials" such as normal and specular maps + /// stored in legacy format in part.DynAttrs + /// + /// + /// + //public void GatherMaterialsUuids(SceneObjectPart part, IDictionary assetUuids) + public void GatherMaterialsUuids(SceneObjectPart part, IDictionary assetUuids) + { + // scan thru the dynAttrs map of this part for any textures used as materials + OSD osdMaterials = null; + + lock (part.DynAttrs) + { + if (part.DynAttrs.ContainsStore("OpenSim", "Materials")) + { + OSDMap materialsStore = part.DynAttrs.GetStore("OpenSim", "Materials"); + + if (materialsStore == null) + return; + + materialsStore.TryGetValue("Materials", out osdMaterials); + } + + if (osdMaterials != null) + { + //m_log.Info("[UUID Gatherer]: found Materials: " + OSDParser.SerializeJsonString(osd)); + + if (osdMaterials is OSDArray) + { + OSDArray matsArr = osdMaterials as OSDArray; + foreach (OSDMap matMap in matsArr) + { + try + { + if (matMap.ContainsKey("Material")) + { + OSDMap mat = matMap["Material"] as OSDMap; + if (mat.ContainsKey("NormMap")) + { + UUID normalMapId = mat["NormMap"].AsUUID(); + if (normalMapId != UUID.Zero) + { + assetUuids[normalMapId] = (sbyte)AssetType.Texture; + //m_log.Info("[UUID Gatherer]: found normal map ID: " + normalMapId.ToString()); + } + } + if (mat.ContainsKey("SpecMap")) + { + UUID specularMapId = mat["SpecMap"].AsUUID(); + if (specularMapId != UUID.Zero) + { + assetUuids[specularMapId] = (sbyte)AssetType.Texture; + //m_log.Info("[UUID Gatherer]: found specular map ID: " + specularMapId.ToString()); + } + } + } + + } + catch (Exception e) + { + m_log.Warn("[UUID Gatherer]: exception getting materials: " + e.Message); + } + } + } + } + } + } /// /// Get an asset synchronously, potentially using an asynchronous callback. If the From 83626e60e69ac0534faffa40f9e79a5d3ae0d332 Mon Sep 17 00:00:00 2001 From: Mic Bowman Date: Mon, 20 Jan 2014 18:59:43 -0800 Subject: [PATCH 38/49] Adds a configuration option to cannibalize bandwidth from the udp texture throttle and move it to the task throttle. Since most viewers are using http textures, the udp texture throttle is holding onto bw that could be used for more responsive prims updates. See the documentation for CannibalizeTextureRate in OpenSimDefaults.ini. Option is disabled by default. --- .../Region/ClientStack/Linden/UDP/LLUDPClient.cs | 14 ++++++++++++++ .../Region/ClientStack/Linden/UDP/ThrottleRates.cs | 6 ++++++ bin/OpenSimDefaults.ini | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs index 202cc625a2..51433cbbc3 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs @@ -162,6 +162,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP private int m_defaultRTO = 1000; // 1sec is the recommendation in the RFC private int m_maxRTO = 60000; + /// + /// This is the percentage of the udp texture queue to add to the task queue since + /// textures are now generally handled through http. + /// + private double m_cannibalrate = 0.0; + private ClientInfo m_info = new ClientInfo(); /// @@ -201,6 +207,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Create an array of token buckets for this clients different throttle categories m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; + m_cannibalrate = rates.CannibalizeTextureRate; + for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) { ThrottleOutPacketType type = (ThrottleOutPacketType)i; @@ -349,6 +357,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP texture = Math.Max(texture, LLUDPServer.MTU); asset = Math.Max(asset, LLUDPServer.MTU); + // Since most textures are now delivered through http, make it possible + // to cannibalize some of the bw from the texture throttle to use for + // the task queue (e.g. object updates) + task = task + (int)(m_cannibalrate * texture); + texture = (int)((1 - m_cannibalrate) * texture); + //int total = resend + land + wind + cloud + task + texture + asset; //m_log.DebugFormat("[LLUDPCLIENT]: {0} is setting throttles. Resend={1}, Land={2}, Wind={3}, Cloud={4}, Task={5}, Texture={6}, Asset={7}, Total={8}", // AgentID, resend, land, wind, cloud, task, texture, asset, total); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs index c9aac0ba09..e5bae6ee6c 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs @@ -59,6 +59,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// Flag used to enable adaptive throttles public bool AdaptiveThrottlesEnabled; + /// Amount of the texture throttle to steal for the task throttle + public double CannibalizeTextureRate; + /// /// Default constructor /// @@ -80,6 +83,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP Total = throttleConfig.GetInt("client_throttle_max_bps", 0); AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false); + + CannibalizeTextureRate = (double)throttleConfig.GetFloat("CannibalizeTextureRate", 0.0f); + CannibalizeTextureRate = Util.Clamp(CannibalizeTextureRate,0.0, 0.9); } catch (Exception) { } } diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index ae7b7947d8..0da99ba5d0 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -539,6 +539,16 @@ ; ;TextureSendLimit = 20 + ; CannibalizeTextureRate allows bandwidth to be moved from the + ; UDP texture throttle to the task throttle. Since most viewers + ; use HTTP textures, this provides a means of using what is largely + ; unused bandwidth in the total throttle. The value is the proportion + ; of the texture rate to move to the task queue. It must be between + ; 0.0 (none of the bandwidth is cannibalized) and 0.9 (90% of the + ; bandwidth is grabbed) + ; + ; CannibalizeTextureRate = 0.5 + ; Quash and remove any light properties from attachments not on the ; hands. This allows flashlights and lanterns to function, but kills ; silly vanity "Facelights" dead. Sorry, head mounted miner's lamps From a859464e91f724816c2ba588a541981469d58d2b Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 23 Jan 2014 23:44:21 +0000 Subject: [PATCH 39/49] Add "generate map" console command to allow manual regeneration and storage of maptiles Primarily for test purposes though could be useful if one prefers to manually update the map tile --- .../CoreModules/World/LegacyMap/MapImageModule.cs | 5 ++++- .../CoreModules/World/WorldMap/WorldMapModule.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs index 40638f8dea..61ba5f3605 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs @@ -127,7 +127,10 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap try { using (Bitmap mapbmp = CreateMapTile()) - return OpenJPEG.EncodeFromImage(mapbmp, true); + { + if (mapbmp != null) + return OpenJPEG.EncodeFromImage(mapbmp, true); + } } catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke { diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index cdf14679b3..88761a2895 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -114,6 +114,11 @@ namespace OpenSim.Region.CoreModules.World.WorldMap "export-map []", "Save an image of the world map", HandleExportWorldMapConsoleCommand); + m_scene.AddCommand( + "Regions", this, "generate map", + "generate map", + "Generates and stores a new maptile.", HandleGenerateMapConsoleCommand); + AddHandlers(); } } @@ -1274,6 +1279,16 @@ namespace OpenSim.Region.CoreModules.World.WorldMap m_scene.RegionInfo.RegionName, exportPath); } + public void HandleGenerateMapConsoleCommand(string module, string[] cmdparams) + { + Scene consoleScene = m_scene.ConsoleScene(); + + if (consoleScene != null && consoleScene != m_scene) + return; + + GenerateMaptile(); + } + public OSD HandleRemoteMapItemRequest(string path, OSD request, string endpoint) { uint xstart = 0; From a2d5d810e0572e7f87808ba35c4b2a21e6f48c40 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 24 Jan 2014 00:14:58 +0000 Subject: [PATCH 40/49] Properly dispose of drawing objects to reduce/stop memory leakage on generating map tiles with the MapImageModule and TexturedMapTileRenderer (the current defaults) --- .../World/LegacyMap/MapImageModule.cs | 575 +++++++++--------- .../World/LegacyMap/ShadedMapTileRenderer.cs | 11 +- .../LegacyMap/TexturedMapTileRenderer.cs | 27 +- .../World/WorldMap/WorldMapModule.cs | 107 ++-- 4 files changed, 372 insertions(+), 348 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs index 61ba5f3605..bc52a4361e 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/MapImageModule.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public struct DrawStruct { public DrawRoutine dr; - public Rectangle rect; +// public Rectangle rect; public SolidBrush brush; public face[] trns; } @@ -119,6 +119,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { mapbmp = FetchTexture(m_scene.RegionInfo.RegionSettings.TerrainImageID); } + return mapbmp; } @@ -280,321 +281,331 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap tc = Environment.TickCount; m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); EntityBase[] objs = whichScene.GetEntities(); - Dictionary z_sort = new Dictionary(); - //SortedList z_sort = new SortedList(); List z_sortheights = new List(); List z_localIDs = new List(); + Dictionary z_sort = new Dictionary(); - lock (objs) + try { - foreach (EntityBase obj in objs) + //SortedList z_sort = new SortedList(); + + lock (objs) { - // Only draw the contents of SceneObjectGroup - if (obj is SceneObjectGroup) + foreach (EntityBase obj in objs) { - SceneObjectGroup mapdot = (SceneObjectGroup)obj; - Color mapdotspot = Color.Gray; // Default color when prim color is white - - // Loop over prim in group - foreach (SceneObjectPart part in mapdot.Parts) + // Only draw the contents of SceneObjectGroup + if (obj is SceneObjectGroup) { - if (part == null) - continue; - - // Draw if the object is at least 1 meter wide in any direction - if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) + SceneObjectGroup mapdot = (SceneObjectGroup)obj; + Color mapdotspot = Color.Gray; // Default color when prim color is white + + // Loop over prim in group + foreach (SceneObjectPart part in mapdot.Parts) { - // Try to get the RGBA of the default texture entry.. - // - try - { - // get the null checks out of the way - // skip the ones that break - if (part == null) - continue; - - if (part.Shape == null) - continue; - - if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass) - continue; // eliminates trees from this since we don't really have a good tree representation - // if you want tree blocks on the map comment the above line and uncomment the below line - //mapdotspot = Color.PaleGreen; - - Primitive.TextureEntry textureEntry = part.Shape.Textures; - - if (textureEntry == null || textureEntry.DefaultTexture == null) - continue; - - Color4 texcolor = textureEntry.DefaultTexture.RGBA; - - // Not sure why some of these are null, oh well. - - int colorr = 255 - (int)(texcolor.R * 255f); - int colorg = 255 - (int)(texcolor.G * 255f); - int colorb = 255 - (int)(texcolor.B * 255f); - - if (!(colorr == 255 && colorg == 255 && colorb == 255)) - { - //Try to set the map spot color - try - { - // If the color gets goofy somehow, skip it *shakes fist at Color4 - mapdotspot = Color.FromArgb(colorr, colorg, colorb); - } - catch (ArgumentException) - { - } - } - } - catch (IndexOutOfRangeException) - { - // Windows Array - } - catch (ArgumentOutOfRangeException) - { - // Mono Array - } - - Vector3 pos = part.GetWorldPosition(); - - // skip prim outside of retion - if (pos.X < 0f || pos.X > 256f || pos.Y < 0f || pos.Y > 256f) + if (part == null) continue; - // skip prim in non-finite position - if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || - Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y)) - continue; - - // Figure out if object is under 256m above the height of the terrain - bool isBelow256AboveTerrain = false; - - try + // Draw if the object is at least 1 meter wide in any direction + if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) { - isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); - } - catch (Exception) - { - } - - if (isBelow256AboveTerrain) - { - // Translate scale by rotation so scale is represented properly when object is rotated - Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); - Vector3 scale = new Vector3(); - Vector3 tScale = new Vector3(); - Vector3 axPos = new Vector3(pos.X,pos.Y,pos.Z); - - Quaternion llrot = part.GetWorldRotation(); - Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); - scale = lscale * rot; - - // negative scales don't work in this situation - scale.X = Math.Abs(scale.X); - scale.Y = Math.Abs(scale.Y); - scale.Z = Math.Abs(scale.Z); - - // This scaling isn't very accurate and doesn't take into account the face rotation :P - int mapdrawstartX = (int)(pos.X - scale.X); - int mapdrawstartY = (int)(pos.Y - scale.Y); - int mapdrawendX = (int)(pos.X + scale.X); - int mapdrawendY = (int)(pos.Y + scale.Y); - - // If object is beyond the edge of the map, don't draw it to avoid errors - if (mapdrawstartX < 0 || mapdrawstartX > ((int)Constants.RegionSize - 1) || mapdrawendX < 0 || mapdrawendX > ((int)Constants.RegionSize - 1) - || mapdrawstartY < 0 || mapdrawstartY > ((int)Constants.RegionSize - 1) || mapdrawendY < 0 - || mapdrawendY > ((int)Constants.RegionSize - 1)) - continue; - -#region obb face reconstruction part duex - Vector3[] vertexes = new Vector3[8]; - - // float[] distance = new float[6]; - Vector3[] FaceA = new Vector3[6]; // vertex A for Facei - Vector3[] FaceB = new Vector3[6]; // vertex B for Facei - Vector3[] FaceC = new Vector3[6]; // vertex C for Facei - Vector3[] FaceD = new Vector3[6]; // vertex D for Facei - - tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - // vertexes[0].x = pos.X + vertexes[0].x; - //vertexes[0].y = pos.Y + vertexes[0].y; - //vertexes[0].z = pos.Z + vertexes[0].z; - - FaceA[0] = vertexes[0]; - FaceB[3] = vertexes[0]; - FaceA[4] = vertexes[0]; - - tScale = lscale; - scale = ((tScale * rot)); - vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[1].x = pos.X + vertexes[1].x; - // vertexes[1].y = pos.Y + vertexes[1].y; - //vertexes[1].z = pos.Z + vertexes[1].z; - - FaceB[0] = vertexes[1]; - FaceA[1] = vertexes[1]; - FaceC[4] = vertexes[1]; - - tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - - vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - //vertexes[2].x = pos.X + vertexes[2].x; - //vertexes[2].y = pos.Y + vertexes[2].y; - //vertexes[2].z = pos.Z + vertexes[2].z; - - FaceC[0] = vertexes[2]; - FaceD[3] = vertexes[2]; - FaceC[5] = vertexes[2]; - - tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - //vertexes[3].x = pos.X + vertexes[3].x; - // vertexes[3].y = pos.Y + vertexes[3].y; - // vertexes[3].z = pos.Z + vertexes[3].z; - - FaceD[0] = vertexes[3]; - FaceC[1] = vertexes[3]; - FaceA[5] = vertexes[3]; - - tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[4].x = pos.X + vertexes[4].x; - // vertexes[4].y = pos.Y + vertexes[4].y; - // vertexes[4].z = pos.Z + vertexes[4].z; - - FaceB[1] = vertexes[4]; - FaceA[2] = vertexes[4]; - FaceD[4] = vertexes[4]; - - tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[5].x = pos.X + vertexes[5].x; - // vertexes[5].y = pos.Y + vertexes[5].y; - // vertexes[5].z = pos.Z + vertexes[5].z; - - FaceD[1] = vertexes[5]; - FaceC[2] = vertexes[5]; - FaceB[5] = vertexes[5]; - - tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z); - scale = ((tScale * rot)); - vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[6].x = pos.X + vertexes[6].x; - // vertexes[6].y = pos.Y + vertexes[6].y; - // vertexes[6].z = pos.Z + vertexes[6].z; - - FaceB[2] = vertexes[6]; - FaceA[3] = vertexes[6]; - FaceB[4] = vertexes[6]; - - tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z); - scale = ((tScale * rot)); - vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); - - // vertexes[7].x = pos.X + vertexes[7].x; - // vertexes[7].y = pos.Y + vertexes[7].y; - // vertexes[7].z = pos.Z + vertexes[7].z; - - FaceD[2] = vertexes[7]; - FaceC[3] = vertexes[7]; - FaceD[5] = vertexes[7]; -#endregion - - //int wy = 0; - - //bool breakYN = false; // If we run into an error drawing, break out of the - // loop so we don't lag to death on error handling - DrawStruct ds = new DrawStruct(); - ds.brush = new SolidBrush(mapdotspot); - //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY); - - ds.trns = new face[FaceA.Length]; - - for (int i = 0; i < FaceA.Length; i++) + // Try to get the RGBA of the default texture entry.. + // + try { - Point[] working = new Point[5]; - working[0] = project(FaceA[i], axPos); - working[1] = project(FaceB[i], axPos); - working[2] = project(FaceD[i], axPos); - working[3] = project(FaceC[i], axPos); - working[4] = project(FaceA[i], axPos); + // get the null checks out of the way + // skip the ones that break + if (part == null) + continue; - face workingface = new face(); - workingface.pts = working; + if (part.Shape == null) + continue; - ds.trns[i] = workingface; + if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass) + continue; // eliminates trees from this since we don't really have a good tree representation + // if you want tree blocks on the map comment the above line and uncomment the below line + //mapdotspot = Color.PaleGreen; + + Primitive.TextureEntry textureEntry = part.Shape.Textures; + + if (textureEntry == null || textureEntry.DefaultTexture == null) + continue; + + Color4 texcolor = textureEntry.DefaultTexture.RGBA; + + // Not sure why some of these are null, oh well. + + int colorr = 255 - (int)(texcolor.R * 255f); + int colorg = 255 - (int)(texcolor.G * 255f); + int colorb = 255 - (int)(texcolor.B * 255f); + + if (!(colorr == 255 && colorg == 255 && colorb == 255)) + { + //Try to set the map spot color + try + { + // If the color gets goofy somehow, skip it *shakes fist at Color4 + mapdotspot = Color.FromArgb(colorr, colorg, colorb); + } + catch (ArgumentException) + { + } + } + } + catch (IndexOutOfRangeException) + { + // Windows Array + } + catch (ArgumentOutOfRangeException) + { + // Mono Array } - z_sort.Add(part.LocalId, ds); - z_localIDs.Add(part.LocalId); - z_sortheights.Add(pos.Z); + Vector3 pos = part.GetWorldPosition(); - //for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) - //{ - //for (wy = mapdrawstartY; wy < mapdrawendY; wy++) + // skip prim outside of retion + if (pos.X < 0f || pos.X > 256f || pos.Y < 0f || pos.Y > 256f) + continue; + + // skip prim in non-finite position + if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || + Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y)) + continue; + + // Figure out if object is under 256m above the height of the terrain + bool isBelow256AboveTerrain = false; + + try + { + isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); + } + catch (Exception) + { + } + + if (isBelow256AboveTerrain) + { + // Translate scale by rotation so scale is represented properly when object is rotated + Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); + Vector3 scale = new Vector3(); + Vector3 tScale = new Vector3(); + Vector3 axPos = new Vector3(pos.X,pos.Y,pos.Z); + + Quaternion llrot = part.GetWorldRotation(); + Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); + scale = lscale * rot; + + // negative scales don't work in this situation + scale.X = Math.Abs(scale.X); + scale.Y = Math.Abs(scale.Y); + scale.Z = Math.Abs(scale.Z); + + // This scaling isn't very accurate and doesn't take into account the face rotation :P + int mapdrawstartX = (int)(pos.X - scale.X); + int mapdrawstartY = (int)(pos.Y - scale.Y); + int mapdrawendX = (int)(pos.X + scale.X); + int mapdrawendY = (int)(pos.Y + scale.Y); + + // If object is beyond the edge of the map, don't draw it to avoid errors + if (mapdrawstartX < 0 || mapdrawstartX > ((int)Constants.RegionSize - 1) || mapdrawendX < 0 || mapdrawendX > ((int)Constants.RegionSize - 1) + || mapdrawstartY < 0 || mapdrawstartY > ((int)Constants.RegionSize - 1) || mapdrawendY < 0 + || mapdrawendY > ((int)Constants.RegionSize - 1)) + continue; + + #region obb face reconstruction part duex + Vector3[] vertexes = new Vector3[8]; + + // float[] distance = new float[6]; + Vector3[] FaceA = new Vector3[6]; // vertex A for Facei + Vector3[] FaceB = new Vector3[6]; // vertex B for Facei + Vector3[] FaceC = new Vector3[6]; // vertex C for Facei + Vector3[] FaceD = new Vector3[6]; // vertex D for Facei + + tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + // vertexes[0].x = pos.X + vertexes[0].x; + //vertexes[0].y = pos.Y + vertexes[0].y; + //vertexes[0].z = pos.Z + vertexes[0].z; + + FaceA[0] = vertexes[0]; + FaceB[3] = vertexes[0]; + FaceA[4] = vertexes[0]; + + tScale = lscale; + scale = ((tScale * rot)); + vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[1].x = pos.X + vertexes[1].x; + // vertexes[1].y = pos.Y + vertexes[1].y; + //vertexes[1].z = pos.Z + vertexes[1].z; + + FaceB[0] = vertexes[1]; + FaceA[1] = vertexes[1]; + FaceC[4] = vertexes[1]; + + tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + + vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[2].x = pos.X + vertexes[2].x; + //vertexes[2].y = pos.Y + vertexes[2].y; + //vertexes[2].z = pos.Z + vertexes[2].z; + + FaceC[0] = vertexes[2]; + FaceD[3] = vertexes[2]; + FaceC[5] = vertexes[2]; + + tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[3].x = pos.X + vertexes[3].x; + // vertexes[3].y = pos.Y + vertexes[3].y; + // vertexes[3].z = pos.Z + vertexes[3].z; + + FaceD[0] = vertexes[3]; + FaceC[1] = vertexes[3]; + FaceA[5] = vertexes[3]; + + tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[4].x = pos.X + vertexes[4].x; + // vertexes[4].y = pos.Y + vertexes[4].y; + // vertexes[4].z = pos.Z + vertexes[4].z; + + FaceB[1] = vertexes[4]; + FaceA[2] = vertexes[4]; + FaceD[4] = vertexes[4]; + + tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[5].x = pos.X + vertexes[5].x; + // vertexes[5].y = pos.Y + vertexes[5].y; + // vertexes[5].z = pos.Z + vertexes[5].z; + + FaceD[1] = vertexes[5]; + FaceC[2] = vertexes[5]; + FaceB[5] = vertexes[5]; + + tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[6].x = pos.X + vertexes[6].x; + // vertexes[6].y = pos.Y + vertexes[6].y; + // vertexes[6].z = pos.Z + vertexes[6].z; + + FaceB[2] = vertexes[6]; + FaceA[3] = vertexes[6]; + FaceB[4] = vertexes[6]; + + tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[7].x = pos.X + vertexes[7].x; + // vertexes[7].y = pos.Y + vertexes[7].y; + // vertexes[7].z = pos.Z + vertexes[7].z; + + FaceD[2] = vertexes[7]; + FaceC[3] = vertexes[7]; + FaceD[5] = vertexes[7]; + #endregion + + //int wy = 0; + + //bool breakYN = false; // If we run into an error drawing, break out of the + // loop so we don't lag to death on error handling + DrawStruct ds = new DrawStruct(); + ds.brush = new SolidBrush(mapdotspot); + //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY); + + ds.trns = new face[FaceA.Length]; + + for (int i = 0; i < FaceA.Length; i++) + { + Point[] working = new Point[5]; + working[0] = project(FaceA[i], axPos); + working[1] = project(FaceB[i], axPos); + working[2] = project(FaceD[i], axPos); + working[3] = project(FaceC[i], axPos); + working[4] = project(FaceA[i], axPos); + + face workingface = new face(); + workingface.pts = working; + + ds.trns[i] = workingface; + } + + z_sort.Add(part.LocalId, ds); + z_localIDs.Add(part.LocalId); + z_sortheights.Add(pos.Z); + + //for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) //{ - //m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); - //try + //for (wy = mapdrawstartY; wy < mapdrawendY; wy++) //{ - // Remember, flip the y! - // mapbmp.SetPixel(wx, (255 - wy), mapdotspot); - //} - //catch (ArgumentException) - //{ - // breakYN = true; + //m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); + //try + //{ + // Remember, flip the y! + // mapbmp.SetPixel(wx, (255 - wy), mapdotspot); + //} + //catch (ArgumentException) + //{ + // breakYN = true; + //} + + //if (breakYN) + // break; //} //if (breakYN) // break; //} + } // Object is within 256m Z of terrain + } // object is at least a meter wide + } // loop over group children + } // entitybase is sceneobject group + } // foreach loop over entities - //if (breakYN) - // break; - //} - } // Object is within 256m Z of terrain - } // object is at least a meter wide - } // loop over group children - } // entitybase is sceneobject group - } // foreach loop over entities + float[] sortedZHeights = z_sortheights.ToArray(); + uint[] sortedlocalIds = z_localIDs.ToArray(); - float[] sortedZHeights = z_sortheights.ToArray(); - uint[] sortedlocalIds = z_localIDs.ToArray(); + // Sort prim by Z position + Array.Sort(sortedZHeights, sortedlocalIds); - // Sort prim by Z position - Array.Sort(sortedZHeights, sortedlocalIds); - - Graphics g = Graphics.FromImage(mapbmp); - - for (int s = 0; s < sortedZHeights.Length; s++) - { - if (z_sort.ContainsKey(sortedlocalIds[s])) + using (Graphics g = Graphics.FromImage(mapbmp)) { - DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]]; - for (int r = 0; r < rectDrawStruct.trns.Length; r++) + for (int s = 0; s < sortedZHeights.Length; s++) { - g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts); + if (z_sort.ContainsKey(sortedlocalIds[s])) + { + DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]]; + for (int r = 0; r < rectDrawStruct.trns.Length; r++) + { + g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts); + } + //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect); + } } - //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect); } - } + } // lock entities objs - g.Dispose(); - } // lock entities objs + } + finally + { + foreach (DrawStruct ds in z_sort.Values) + ds.brush.Dispose(); + } m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Done in " + (Environment.TickCount - tc) + " ms"); + return mapbmp; } diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs index 992bff3290..cb06fd4490 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/ShadedMapTileRenderer.cs @@ -54,7 +54,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { int tc = Environment.TickCount; - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); + m_log.Debug("[SHADED MAP TILE RENDERER]: Generating Maptile Step 1: Terrain"); double[,] hm = m_scene.Heightmap.GetDoubles(); bool ShadowDebugContinue = true; @@ -199,7 +199,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { if (!terraincorruptedwarningsaid) { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + m_log.WarnFormat("[SHADED MAP TILE RENDERER]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); terraincorruptedwarningsaid = true; } color = Color.Black; @@ -229,7 +229,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap { if (!terraincorruptedwarningsaid) { - m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + m_log.WarnFormat("[SHADED MAP TILE RENDERER]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); terraincorruptedwarningsaid = true; } Color black = Color.Black; @@ -238,7 +238,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } } } - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + + m_log.Debug("[SHADED MAP TILE RENDERER]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs index d13c2ef91d..e895178791 100644 --- a/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs +++ b/OpenSim/Region/CoreModules/World/LegacyMap/TexturedMapTileRenderer.cs @@ -173,7 +173,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap private Bitmap fetchTexture(UUID id) { AssetBase asset = m_scene.AssetService.Get(id.ToString()); - m_log.DebugFormat("[TexturedMapTileRenderer]: Fetched texture {0}, found: {1}", id, asset != null); + m_log.DebugFormat("[TEXTURED MAP TILE RENDERER]: Fetched texture {0}, found: {1}", id, asset != null); if (asset == null) return null; ManagedImage managedImage; @@ -188,17 +188,17 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } catch (DllNotFoundException) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", id); + m_log.ErrorFormat("[TEXTURED MAP TILE RENDERER]: OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", id); } catch (IndexOutOfRangeException) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); + m_log.ErrorFormat("[TEXTURED MAP TILE RENDERER]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); } catch (Exception) { - m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); + m_log.ErrorFormat("[TEXTURED MAP TILE RENDERER]: OpenJpeg was unable to encode this. Asset Data is empty for {0}", id); } return null; @@ -233,10 +233,14 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap if (textureID == UUID.Zero) return defaultColor; // not set if (m_mapping.ContainsKey(textureID)) return m_mapping[textureID]; // one of the predefined textures - Bitmap bmp = fetchTexture(textureID); - Color color = bmp == null ? defaultColor : computeAverageColor(bmp); - // store it for future reference - m_mapping[textureID] = color; + Color color; + + using (Bitmap bmp = fetchTexture(textureID)) + { + color = bmp == null ? defaultColor : computeAverageColor(bmp); + // store it for future reference + m_mapping[textureID] = color; + } return color; } @@ -278,7 +282,7 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap public void TerrainToBitmap(Bitmap mapbmp) { int tc = Environment.TickCount; - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Terrain"); + m_log.Debug("[TEXTURED MAP TILE RENDERER]: Generating Maptile Step 1: Terrain"); // These textures should be in the AssetCache anyway, as every client conneting to this // region needs them. Except on start, when the map is recreated (before anyone connected), @@ -412,7 +416,8 @@ namespace OpenSim.Region.CoreModules.World.LegacyMap } } } - m_log.Debug("[MAPTILE]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); + + m_log.Debug("[TEXTURED MAP TILE RENDERER]: Generating Maptile Step 1: Done in " + (Environment.TickCount - tc) + " ms"); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index 88761a2895..cf2ef29090 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -1520,62 +1520,69 @@ namespace OpenSim.Region.CoreModules.World.WorldMap private Byte[] GenerateOverlay() { - Bitmap overlay = new Bitmap(256, 256); - - bool[,] saleBitmap = new bool[64, 64]; - for (int x = 0 ; x < 64 ; x++) + using (Bitmap overlay = new Bitmap(256, 256)) { - for (int y = 0 ; y < 64 ; y++) - saleBitmap[x, y] = false; - } - - bool landForSale = false; - - List parcels = m_scene.LandChannel.AllParcels(); - - Color background = Color.FromArgb(0, 0, 0, 0); - SolidBrush transparent = new SolidBrush(background); - Graphics g = Graphics.FromImage(overlay); - g.FillRectangle(transparent, 0, 0, 256, 256); - - SolidBrush yellow = new SolidBrush(Color.FromArgb(255, 249, 223, 9)); - - foreach (ILandObject land in parcels) - { - // m_log.DebugFormat("[WORLD MAP]: Parcel {0} flags {1}", land.LandData.Name, land.LandData.Flags); - if ((land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0) + bool[,] saleBitmap = new bool[64, 64]; + for (int x = 0 ; x < 64 ; x++) { - landForSale = true; + for (int y = 0 ; y < 64 ; y++) + saleBitmap[x, y] = false; + } - saleBitmap = land.MergeLandBitmaps(saleBitmap, land.GetLandBitmap()); + bool landForSale = false; + + List parcels = m_scene.LandChannel.AllParcels(); + + Color background = Color.FromArgb(0, 0, 0, 0); + + using (Graphics g = Graphics.FromImage(overlay)) + { + using (SolidBrush transparent = new SolidBrush(background)) + g.FillRectangle(transparent, 0, 0, 256, 256); + + + foreach (ILandObject land in parcels) + { + // m_log.DebugFormat("[WORLD MAP]: Parcel {0} flags {1}", land.LandData.Name, land.LandData.Flags); + if ((land.LandData.Flags & (uint)ParcelFlags.ForSale) != 0) + { + landForSale = true; + + saleBitmap = land.MergeLandBitmaps(saleBitmap, land.GetLandBitmap()); + } + } + + if (!landForSale) + { + m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName); + return null; + } + + m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName); + + using (SolidBrush yellow = new SolidBrush(Color.FromArgb(255, 249, 223, 9))) + { + for (int x = 0 ; x < 64 ; x++) + { + for (int y = 0 ; y < 64 ; y++) + { + if (saleBitmap[x, y]) + g.FillRectangle(yellow, x * 4, 252 - (y * 4), 4, 4); + } + } + } + } + + try + { + return OpenJPEG.EncodeFromImage(overlay, true); + } + catch (Exception e) + { + m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString()); } } - if (!landForSale) - { - m_log.DebugFormat("[WORLD MAP]: Region {0} has no parcels for sale, not generating overlay", m_scene.RegionInfo.RegionName); - return null; - } - - m_log.DebugFormat("[WORLD MAP]: Region {0} has parcels for sale, generating overlay", m_scene.RegionInfo.RegionName); - - for (int x = 0 ; x < 64 ; x++) - { - for (int y = 0 ; y < 64 ; y++) - { - if (saleBitmap[x, y]) - g.FillRectangle(yellow, x * 4, 252 - (y * 4), 4, 4); - } - } - - try - { - return OpenJPEG.EncodeFromImage(overlay, true); - } - catch (Exception e) - { - m_log.DebugFormat("[WORLD MAP]: Error creating parcel overlay: " + e.ToString()); - } return null; } } From 4a9796a50680ef7aeaa8c9c617b90205724879c8 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 24 Jan 2014 19:31:31 +0000 Subject: [PATCH 41/49] Skip IClientAPIs that don't implement IStatsCollector (such as NPCAvatar) from the "show queues" console report to stop screwing up formatting. "show pquques" already did this --- .../Agent/UDP/Linden/LindenUDPInfoModule.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 1eb0a6bc5f..3bf5585699 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -434,24 +434,24 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden scene.ForEachClient( delegate(IClientAPI client) { - bool isChild = client.SceneAgent.IsChildAgent; - if (isChild && !showChildren) - return; - - string name = client.Name; - if (pname != "" && name != pname) - return; - - string regionName = scene.RegionInfo.RegionName; - - report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); - report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding)); - report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); - if (client is IStatsCollector) { - IStatsCollector stats = (IStatsCollector)client; + + bool isChild = client.SceneAgent.IsChildAgent; + if (isChild && !showChildren) + return; + string name = client.Name; + if (pname != "" && name != pname) + return; + + string regionName = scene.RegionInfo.RegionName; + + report.Append(GetColumnEntry(name, maxNameLength, columnPadding)); + report.Append(GetColumnEntry(regionName, maxRegionNameLength, columnPadding)); + report.Append(GetColumnEntry(isChild ? "Cd" : "Rt", maxTypeLength, columnPadding)); + + IStatsCollector stats = (IStatsCollector)client; report.AppendLine(stats.Report()); } }); From c9b5ba78d959e6368a525630fecc6103f317f1da Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 24 Jan 2014 19:36:12 +0000 Subject: [PATCH 42/49] minor: correct the usage statement on the "show image queues" console command - should not have been "image queues show" --- .../OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 3bf5585699..034082e01f 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -304,7 +304,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden private string GetImageQueuesReport(string[] showParams) { if (showParams.Length < 5 || showParams.Length > 6) - return "Usage: image queues show [full]"; + return "Usage: show image queues [full]"; string firstName = showParams[3]; string lastName = showParams[4]; From fea8345f560370d20e13f8362fc8f63396c2247f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 24 Jan 2014 19:40:14 +0000 Subject: [PATCH 43/49] minor: remove long unused state queue from "show queues" console reports --- .../Agent/UDP/Linden/LindenUDPInfoModule.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index 034082e01f..ec18db0aa8 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -395,7 +395,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden report.Append(GetColumnEntry("Type", maxTypeLength, columnPadding)); report.AppendFormat( - "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7} {12,7}\n", + "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7}\n", "Since", "Pkts", "Pkts", @@ -407,12 +407,11 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden "Q Pkts", "Q Pkts", "Q Pkts", - "Q Pkts", "Q Pkts"); report.AppendFormat("{0,-" + totalInfoFieldsLength + "}", ""); report.AppendFormat( - "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7} {12,7}\n", + "{0,7} {1,7} {2,7} {3,7} {4,9} {5,7} {6,7} {7,7} {8,7} {9,7} {10,8} {11,7}\n", "Last In", "In", "Out", @@ -424,8 +423,7 @@ namespace OpenSim.Region.OptionalModules.UDP.Linden "Cloud", "Task", "Texture", - "Asset", - "State"); + "Asset"); lock (m_scenes) { From e2fbc88d98b8e23b26716b6b800ab540ac0ca821 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 27 Jan 2014 22:56:51 +0000 Subject: [PATCH 44/49] Re-enabled NPCModuleTests.TestCreate() --- .../Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs b/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs index 7f9e440732..c65794e297 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/Tests/NPCModuleTests.cs @@ -110,8 +110,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests // ScenePresence.SendInitialData() to reset our entire appearance. m_scene.AssetService.Store(AssetHelpers.CreateNotecardAsset(originalFace8TextureId)); -/* - m_afMod.SetAppearance(sp, originalTe, null); + m_afMod.SetAppearance(sp, originalTe, null, null); UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); @@ -126,7 +125,6 @@ namespace OpenSim.Region.OptionalModules.World.NPC.Tests // Have to account for both SP and NPC. Assert.That(m_scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(2)); -*/ } [Test] From 1b86239f791754e2c3ba7bf2641db5882efb0c80 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 27 Jan 2014 23:17:09 +0000 Subject: [PATCH 45/49] refactor: Remove identical part.ParentGroup.AddAvatar(UUID); calls which occur no matter which branch of the conditional is executed --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 85a20e9653..0cc00ed708 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -2909,7 +2909,6 @@ namespace OpenSim.Region.Framework.Scenes Rotation = newRot; // ParentPosition = part.AbsolutePosition; - part.ParentGroup.AddAvatar(UUID); } else { @@ -2918,13 +2917,13 @@ namespace OpenSim.Region.Framework.Scenes m_pos -= part.GroupPosition; // ParentPosition = part.AbsolutePosition; - part.ParentGroup.AddAvatar(UUID); // m_log.DebugFormat( // "[SCENE PRESENCE]: Sitting {0} at position {1} ({2} + {3}) on part {4} {5} without sit target", // Name, part.AbsolutePosition, m_pos, ParentPosition, part.Name, part.LocalId); } + part.ParentGroup.AddAvatar(UUID); ParentPart = m_scene.GetSceneObjectPart(m_requestedSitTargetID); ParentID = m_requestedSitTargetID; m_AngularVelocity = Vector3.Zero; From a4017ee1eb30af8af4ac08c8003a796fcdd6f4a8 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 27 Jan 2014 23:47:43 +0000 Subject: [PATCH 46/49] Reinsert attachments list taking code in SP.MakeRootAgent() Locking attachments then launching script instances on a separate thread will not work, attachments will simply be unlocked and vulnerable to race conditions. --- .../Region/Framework/Scenes/ScenePresence.cs | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 0cc00ed708..84201cca31 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -1190,22 +1190,36 @@ namespace OpenSim.Region.Framework.Scenes // and CHANGED_REGION) when the attachments have been rezzed in the new region. This cannot currently // be done in AttachmentsModule.CopyAttachments(AgentData ad, IScenePresence sp) itself since we are // not transporting the required data. - lock (m_attachments) - { - if (HasAttachments()) - { - m_log.DebugFormat( - "[SCENE PRESENCE]: Restarting scripts in attachments for {0} in {1}", Name, Scene.Name); + // + // We need to restart scripts here so that they receive the correct changed events (CHANGED_TELEPORT + // and CHANGED_REGION) when the attachments have been rezzed in the new region. This cannot currently + // be done in AttachmentsModule.CopyAttachments(AgentData ad, IScenePresence sp) itself since we are + // not transporting the required data. + // + // We must take a copy of the attachments list here (rather than locking) to avoid a deadlock where a script in one of + // the attachments may start processing an event (which locks ScriptInstance.m_Script) that then calls a method here + // which needs to lock m_attachments. ResumeScripts() needs to take a ScriptInstance.m_Script lock to try to unset the Suspend status. + // + // FIXME: In theory, this deadlock should not arise since scripts should not be processing events until ResumeScripts(). + // But XEngine starts all scripts unsuspended. Starting them suspended will not currently work because script rezzing + // is placed in an asynchronous queue in XEngine and so the ResumeScripts() call will almost certainly execute before the + // script is rezzed. This means the ResumeScripts() does absolutely nothing when using XEngine. + // + // One cannot simply iterate over attachments in a fire and forget thread because this would no longer + // be locked, allowing race conditions if other code changes the attachments list. + List attachments = GetAttachments(); - // Resume scripts - Util.FireAndForget(delegate(object x) { - foreach (SceneObjectGroup sog in m_attachments) - { - sog.ScheduleGroupForFullUpdate(); - sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource()); - sog.ResumeScripts(); - } - }); + if (attachments.Count > 0) + { + m_log.DebugFormat( + "[SCENE PRESENCE]: Restarting scripts in attachments for {0} in {1}", Name, Scene.Name); + + // Resume scripts + foreach (SceneObjectGroup sog in attachments) + { + sog.ScheduleGroupForFullUpdate(); + sog.RootPart.ParentGroup.CreateScriptInstances(0, false, m_scene.DefaultScriptEngine, GetStateSource()); + sog.ResumeScripts(); } } } @@ -3227,6 +3241,8 @@ namespace OpenSim.Region.Framework.Scenes // again here... this comes after the cached appearance check because the avatars // appearance goes into the avatar update packet SendAvatarDataToAllAgents(); + + // This invocation always shows up in the viewer logs as an error. Is it needed? SendAppearanceToAgent(this); // If we are using the the cached appearance then send it out to everyone From 8c2b41b01dac74ecd4275d558d1bf1462f691b0e Mon Sep 17 00:00:00 2001 From: Dev Random Date: Thu, 23 Jan 2014 23:05:00 -0500 Subject: [PATCH 47/49] Make inidirectory files supercede distro files --- .../Region/Application/ConfigurationLoader.cs | 76 ++++++++++++------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/OpenSim/Region/Application/ConfigurationLoader.cs b/OpenSim/Region/Application/ConfigurationLoader.cs index e3e0c0194f..9634dab4bb 100644 --- a/OpenSim/Region/Application/ConfigurationLoader.cs +++ b/OpenSim/Region/Application/ConfigurationLoader.cs @@ -139,12 +139,29 @@ namespace OpenSim } } + m_config = new OpenSimConfigSource(); + m_config.Source = new IniConfigSource(); + m_config.Source.Merge(DefaultConfig()); + + m_log.Info("[CONFIG]: Reading configuration settings"); + + for (int i = 0 ; i < sources.Count ; i++) + { + if (ReadConfig(m_config, sources[i])) + { + iniFileExists = true; + AddIncludes(m_config, sources); + } + } + + // Override distro settings with contents of inidirectory string iniDirName = startupConfig.GetString("inidirectory", "config"); string iniDirPath = Path.Combine(Util.configDir(), iniDirName); if (Directory.Exists(iniDirPath)) { - m_log.InfoFormat("Searching folder {0} for config ini files", iniDirPath); + m_log.InfoFormat("[CONFIG]: Searching folder {0} for config ini files", iniDirPath); + List overrideSources = new List(); string[] fileEntries = Directory.GetFiles(iniDirName); foreach (string filePath in fileEntries) @@ -152,40 +169,45 @@ namespace OpenSim if (Path.GetExtension(filePath).ToLower() == ".ini") { if (!sources.Contains(Path.GetFullPath(filePath))) - sources.Add(Path.GetFullPath(filePath)); + { + overrideSources.Add(Path.GetFullPath(filePath)); + // put it in sources too, to avoid circularity + sources.Add(Path.GetFullPath(filePath)); + } } } - } - m_config = new OpenSimConfigSource(); - m_config.Source = new IniConfigSource(); - m_config.Source.Merge(DefaultConfig()); - - m_log.Info("[CONFIG]: Reading configuration settings"); + if (overrideSources.Count > 0) + { + OpenSimConfigSource overrideConfig = new OpenSimConfigSource(); + overrideConfig.Source = new IniConfigSource(); + + for (int i = 0 ; i < overrideSources.Count ; i++) + { + if (ReadConfig(overrideConfig, overrideSources[i])) + { + iniFileExists = true; + AddIncludes(overrideConfig, overrideSources); + } + } + m_config.Source.Merge(overrideConfig.Source); + } + } + if (sources.Count == 0) { m_log.FatalFormat("[CONFIG]: Could not load any configuration"); Environment.Exit(1); - } - - for (int i = 0 ; i < sources.Count ; i++) - { - if (ReadConfig(sources[i])) - { - iniFileExists = true; - AddIncludes(sources); - } - } - - if (!iniFileExists) + } + else if (!iniFileExists) { m_log.FatalFormat("[CONFIG]: Could not load any configuration"); m_log.FatalFormat("[CONFIG]: Configuration exists, but there was an error loading it!"); Environment.Exit(1); } - - // Make sure command line options take precedence + + // Make sure command line options take precedence m_config.Source.Merge(argvSource); IConfig enVars = m_config.Source.Configs["Environment"]; @@ -214,10 +236,10 @@ namespace OpenSim /// Adds the included files as ini configuration files /// /// List of URL strings or filename strings - private void AddIncludes(List sources) + private void AddIncludes(OpenSimConfigSource configSource, List sources) { //loop over config sources - foreach (IConfig config in m_config.Source.Configs) + foreach (IConfig config in configSource.Source.Configs) { // Look for Include-* in the key name string[] keys = config.GetKeys(); @@ -284,7 +306,7 @@ namespace OpenSim /// /// Full path to the ini /// - private bool ReadConfig(string iniPath) + private bool ReadConfig(OpenSimConfigSource configSource, string iniPath) { bool success = false; @@ -292,7 +314,7 @@ namespace OpenSim { m_log.InfoFormat("[CONFIG]: Reading configuration file {0}", Path.GetFullPath(iniPath)); - m_config.Source.Merge(new IniConfigSource(iniPath)); + configSource.Source.Merge(new IniConfigSource(iniPath)); success = true; } else @@ -305,7 +327,7 @@ namespace OpenSim { XmlReader r = XmlReader.Create(iniPath); XmlConfigSource cs = new XmlConfigSource(r); - m_config.Source.Merge(cs); + configSource.Source.Merge(cs); success = true; } From f7172095e09bf76bd85e17f3be24280b49348168 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Tue, 28 Jan 2014 00:04:55 +0000 Subject: [PATCH 48/49] Convert tabs to spaces from previous commit 8c2b41b01 --- .../Region/Application/ConfigurationLoader.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/OpenSim/Region/Application/ConfigurationLoader.cs b/OpenSim/Region/Application/ConfigurationLoader.cs index 9634dab4bb..52e520c41e 100644 --- a/OpenSim/Region/Application/ConfigurationLoader.cs +++ b/OpenSim/Region/Application/ConfigurationLoader.cs @@ -124,7 +124,7 @@ namespace OpenSim else { Application.iniFilePath = Path.GetFullPath( - Path.Combine(Util.configDir(), iniFileName)); + Path.Combine(Util.configDir(), iniFileName)); if (!File.Exists(Application.iniFilePath)) { @@ -154,14 +154,14 @@ namespace OpenSim } } - // Override distro settings with contents of inidirectory + // Override distro settings with contents of inidirectory string iniDirName = startupConfig.GetString("inidirectory", "config"); string iniDirPath = Path.Combine(Util.configDir(), iniDirName); if (Directory.Exists(iniDirPath)) { m_log.InfoFormat("[CONFIG]: Searching folder {0} for config ini files", iniDirPath); - List overrideSources = new List(); + List overrideSources = new List(); string[] fileEntries = Directory.GetFiles(iniDirName); foreach (string filePath in fileEntries) @@ -169,45 +169,45 @@ namespace OpenSim if (Path.GetExtension(filePath).ToLower() == ".ini") { if (!sources.Contains(Path.GetFullPath(filePath))) - { + { overrideSources.Add(Path.GetFullPath(filePath)); - // put it in sources too, to avoid circularity - sources.Add(Path.GetFullPath(filePath)); - } + // put it in sources too, to avoid circularity + sources.Add(Path.GetFullPath(filePath)); + } } } - if (overrideSources.Count > 0) - { - OpenSimConfigSource overrideConfig = new OpenSimConfigSource(); - overrideConfig.Source = new IniConfigSource(); - - for (int i = 0 ; i < overrideSources.Count ; i++) - { - if (ReadConfig(overrideConfig, overrideSources[i])) - { - iniFileExists = true; - AddIncludes(overrideConfig, overrideSources); - } - } - m_config.Source.Merge(overrideConfig.Source); - } - } - + if (overrideSources.Count > 0) + { + OpenSimConfigSource overrideConfig = new OpenSimConfigSource(); + overrideConfig.Source = new IniConfigSource(); + + for (int i = 0 ; i < overrideSources.Count ; i++) + { + if (ReadConfig(overrideConfig, overrideSources[i])) + { + iniFileExists = true; + AddIncludes(overrideConfig, overrideSources); + } + } + m_config.Source.Merge(overrideConfig.Source); + } + } + if (sources.Count == 0) { m_log.FatalFormat("[CONFIG]: Could not load any configuration"); Environment.Exit(1); } - else if (!iniFileExists) + else if (!iniFileExists) { m_log.FatalFormat("[CONFIG]: Could not load any configuration"); m_log.FatalFormat("[CONFIG]: Configuration exists, but there was an error loading it!"); Environment.Exit(1); } - - // Make sure command line options take precedence + + // Make sure command line options take precedence m_config.Source.Merge(argvSource); IConfig enVars = m_config.Source.Configs["Environment"]; From 2cf224166f232076c6d51312aaae1a9a9c24bd1c Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Wed, 15 Jan 2014 17:38:09 +0200 Subject: [PATCH 49/49] Added osGetRegionSize(), which returns the region size in meters and recognizes megaregions --- .../Shared/Api/Implementation/OSSL_Api.cs | 23 +++++++++++++++++++ .../Shared/Api/Interface/IOSSL_Api.cs | 1 + .../Shared/Api/Runtime/OSSL_Stub.cs | 5 ++++ 3 files changed, 29 insertions(+) diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs index f4d5562364..15fbbfdb03 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs @@ -2926,6 +2926,29 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return ret; } + public LSL_Vector osGetRegionSize() + { + CheckThreatLevel(ThreatLevel.None, "osGetRegionSize"); + m_host.AddScriptLPS(1); + + bool isMegaregion; + IRegionCombinerModule rcMod = World.RequestModuleInterface(); + if (rcMod != null) + isMegaregion = rcMod.IsRootForMegaregion(World.RegionInfo.RegionID); + else + isMegaregion = false; + + if (isMegaregion) + { + Vector2 size = rcMod.GetSizeOfMegaregion(World.RegionInfo.RegionID); + return new LSL_Vector(size.X, size.Y, Constants.RegionHeight); + } + else + { + return new LSL_Vector((float)Constants.RegionSize, (float)Constants.RegionSize, Constants.RegionHeight); + } + } + public int osGetSimulatorMemory() { CheckThreatLevel(ThreatLevel.Moderate, "osGetSimulatorMemory"); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs index 51d0581711..519779eef9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Interface/IOSSL_Api.cs @@ -337,6 +337,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api.Interfaces key osGetMapTexture(); key osGetRegionMapTexture(string regionName); LSL_List osGetRegionStats(); + vector osGetRegionSize(); int osGetSimulatorMemory(); void osKickAvatar(string FirstName,string SurName,string alert); diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs index c9902e4339..02a35414ae 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/OSSL_Stub.cs @@ -863,6 +863,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.ScriptBase return m_OSSL_Functions.osGetRegionStats(); } + public vector osGetRegionSize() + { + return m_OSSL_Functions.osGetRegionSize(); + } + /// /// Returns the amount of memory in use by the Simulator Daemon. /// Amount in bytes - if >= 4GB, returns 4GB. (LSL is not 64-bit aware)