From 12beaccec79319186ae2a7bf3663510584117a63 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Wed, 3 Sep 2008 18:04:37 +0000 Subject: [PATCH] Merge branch 'cms' of http://pokgsa.ibm.com/~jbongio/public/opensim --- .../ContentManagementSystem/AuraMetaEntity.cs | 114 +++ .../ContentManagementSystem/BeamMetaEntity.cs | 98 +++ .../ContentManagementSystem/CMController.cs | 684 ++++++++++++++++++ .../CMEntityCollection.cs | 148 ++++ .../ContentManagementSystem/CMModel.cs | 309 ++++++++ .../Modules/ContentManagementSystem/CMView.cs | 156 ++++ .../ContentManagementEntity.cs | 326 +++++++++ .../ContentManagementModule.cs | 111 +++ .../FileSystemDatabase.cs | 260 +++++++ .../ContentManagementSystem/GitDatabase.cs | 130 ++++ .../IContentDatabase.cs | 57 ++ .../ContentManagementSystem/MetaEntity.cs | 219 ++++++ .../PointMetaEntity.cs | 81 +++ .../Modules/ContentManagementSystem/README | 52 ++ .../Requirements_documentation.odt | Bin 0 -> 38196 bytes .../SceneObjectGroupDiff.cs | 169 +++++ bin/opensim-ode.sh | 1 - 17 files changed, 2914 insertions(+), 1 deletion(-) create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/AuraMetaEntity.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/BeamMetaEntity.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/CMController.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/CMEntityCollection.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/CMModel.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/CMView.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementEntity.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementModule.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/FileSystemDatabase.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/GitDatabase.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/IContentDatabase.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/MetaEntity.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/PointMetaEntity.cs create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/README create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/Requirements_documentation.odt create mode 100644 OpenSim/Region/Environment/Modules/ContentManagementSystem/SceneObjectGroupDiff.cs diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/AuraMetaEntity.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/AuraMetaEntity.cs new file mode 100644 index 0000000000..2155b4c6f1 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/AuraMetaEntity.cs @@ -0,0 +1,114 @@ +// AuraMetaEntity.cs created with MonoDevelop +// User: bongiojp at 3:03 PM 8/6/2008 +// +// To change standard headers go to Edit->Preferences->Coding->Standard Headers +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + + public class AuraMetaEntity : PointMetaEntity + { + //transparency of root part, NOT particle system. Should probably add support for changing particle system transparency. + public AuraMetaEntity(Scene scene, uint LocalId, LLVector3 groupPos, float transparency, LLVector3 color, LLVector3 scale) : base(scene, LocalId, groupPos, transparency) + { + SetAura(color, scale); + } + + public AuraMetaEntity(Scene scene, LLUUID uuid, uint LocalId, LLVector3 groupPos, float transparency, LLVector3 color, LLVector3 scale) : base(scene, uuid, LocalId, groupPos, transparency) + { + SetAura(color, scale); + } + + private float Average(LLVector3 values) + { + return (values.X + values.Y + values.Z)/3f; + } + + public void SetAura(LLVector3 color, LLVector3 scale) + { + SetAura(color, Average(scale) * 2.0f); + } + + public void SetAura(LLVector3 color, float radius) + { + SceneObjectPart From = m_Entity.RootPart; + + //m_log.Debug("[META ENTITY] BEFORE: radius = " + radius); + float burstRadius = 0.1f; + Primitive.ParticleSystem.SourcePattern patternFlags = Primitive.ParticleSystem.SourcePattern.None; + float age = 1.5f; + float burstRate = 0.4f; + if (radius >= 8.0f) + { + //float sizeOfObject = radius / 2.0f; + burstRadius = (radius - 8.0f)/3f; + burstRate = 1.5f; + radius = 7.99f; + patternFlags = Primitive.ParticleSystem.SourcePattern.Explode; + age = 4.0f; + } + SetAura(From, color, radius, burstRadius, age, burstRate, patternFlags); + } + public void SetAura(SceneObjectPart From, LLVector3 color, float radius, float burstRadius, float age, float burstRate, libsecondlife.Primitive.ParticleSystem.SourcePattern patternFlags) + { + Primitive.ParticleSystem prules = new Primitive.ParticleSystem(); + //prules.PartDataFlags = Primitive.ParticleSystem.ParticleDataFlags.Emissive | + // Primitive.ParticleSystem.ParticleDataFlags.FollowSrc; //PSYS_PART_FLAGS + //prules.PartDataFlags = Primitive.ParticleSystem.ParticleDataFlags.Beam | + // Primitive.ParticleSystem.ParticleDataFlags.TargetPos; + prules.PartStartColor.R = color.X; //PSYS_PART_START_COLOR + prules.PartStartColor.G = color.Y; + prules.PartStartColor.B = color.Z; + prules.PartStartColor.A = 0.5f; //PSYS_PART_START_ALPHA, transparency + prules.PartEndColor.R = color.X; //PSYS_PART_END_COLOR + prules.PartEndColor.G = color.Y; + prules.PartEndColor.B = color.Z; + prules.PartEndColor.A = 0.5f; //PSYS_PART_END_ALPHA, transparency + /*prules.PartStartScaleX = 0.5f; //PSYS_PART_START_SCALE + prules.PartStartScaleY = 0.5f; + prules.PartEndScaleX = 0.5f; //PSYS_PART_END_SCALE + prules.PartEndScaleY = 0.5f; + */ + prules.PartStartScaleX = radius; //PSYS_PART_START_SCALE + prules.PartStartScaleY = radius; + prules.PartEndScaleX = radius; //PSYS_PART_END_SCALE + prules.PartEndScaleY = radius; + prules.PartMaxAge = age; //PSYS_PART_MAX_AGE + prules.PartAcceleration.X = 0.0f; //PSYS_SRC_ACCEL + prules.PartAcceleration.Y = 0.0f; + prules.PartAcceleration.Z = 0.0f; + prules.Pattern = patternFlags; //PSYS_SRC_PATTERN + //prules.Texture = LLUUID.Zero;//= LLUUID //PSYS_SRC_TEXTURE, default used if blank + prules.BurstRate = burstRate; //PSYS_SRC_BURST_RATE + prules.BurstPartCount = 2; //PSYS_SRC_BURST_PART_COUNT + //prules.BurstRadius = radius; //PSYS_SRC_BURST_RADIUS + prules.BurstRadius = burstRadius; //PSYS_SRC_BURST_RADIUS + prules.BurstSpeedMin = 0.001f; //PSYS_SRC_BURST_SPEED_MIN + prules.BurstSpeedMax = 0.001f; //PSYS_SRC_BURST_SPEED_MAX + prules.MaxAge = 0.0f; //PSYS_SRC_MAX_AGE + //prules.Target = To; //PSYS_SRC_TARGET_KEY + prules.AngularVelocity.X = 0.0f; //PSYS_SRC_OMEGA + prules.AngularVelocity.Y = 0.0f; + prules.AngularVelocity.Z = 0.0f; + prules.InnerAngle = 0.0f; //PSYS_SRC_ANGLE_BEGIN + prules.OuterAngle = 0.0f; //PSYS_SRC_ANGLE_END + + prules.CRC = 1; //activates the particle system?? + From.AddNewParticleSystem(prules); + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/BeamMetaEntity.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/BeamMetaEntity.cs new file mode 100644 index 0000000000..499d1bc020 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/BeamMetaEntity.cs @@ -0,0 +1,98 @@ +// BeamMetaEntity.cs created with MonoDevelop +// User: bongiojp at 3:03 PM 8/6/2008 +// +// To change standard headers go to Edit->Preferences->Coding->Standard Headers +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + + public class BeamMetaEntity : PointMetaEntity + { + + public BeamMetaEntity(Scene scene, uint LocalId, LLVector3 groupPos, float transparency, SceneObjectPart To, LLVector3 color) : base(scene, LocalId, groupPos, transparency) + { + SetBeamToUUID(To, color); + } + + public BeamMetaEntity(Scene scene, LLUUID uuid, uint LocalId, LLVector3 groupPos, float transparency, SceneObjectPart To, LLVector3 color) : base(scene, uuid, LocalId, groupPos, transparency) + { + SetBeamToUUID(To, color); + } + + public void SetBeamToUUID(SceneObjectPart To, LLVector3 color) + { + SceneObjectPart From = m_Entity.RootPart; + //Scale size of particles to distance objects are apart (for better visibility) + LLVector3 FromPos = From.GetWorldPosition(); + LLVector3 ToPos = From.GetWorldPosition(); + LLUUID toUUID = To.UUID; + float distance = (float) (Math.Sqrt(Math.Pow(FromPos.X-ToPos.X, 2) + + Math.Pow(FromPos.X-ToPos.Y, 2) + + Math.Pow(FromPos.X-ToPos.Z, 2) + ) + ); + //float rate = (float) (distance/4f); + float rate = 0.5f; + float scale = (float) (distance/128f); + float speed = (float) (2.0f - distance/128f); + + SetBeamToUUID(From, To, color, rate, scale, speed); + } + + public void SetBeamToUUID(SceneObjectPart From, SceneObjectPart To, LLVector3 color, float rate, float scale, float speed) + { + Primitive.ParticleSystem prules = new Primitive.ParticleSystem(); + //prules.PartDataFlags = Primitive.ParticleSystem.ParticleDataFlags.Emissive | + // Primitive.ParticleSystem.ParticleDataFlags.FollowSrc; //PSYS_PART_FLAGS + prules.PartDataFlags = Primitive.ParticleSystem.ParticleDataFlags.Beam | + Primitive.ParticleSystem.ParticleDataFlags.TargetPos; + prules.PartStartColor.R = color.X; //PSYS_PART_START_COLOR + prules.PartStartColor.G = color.Y; + prules.PartStartColor.B = color.Z; + prules.PartStartColor.A = 1.0f; //PSYS_PART_START_ALPHA, transparency + prules.PartEndColor.R = color.X; //PSYS_PART_END_COLOR + prules.PartEndColor.G = color.Y; + prules.PartEndColor.B = color.Z; + prules.PartEndColor.A = 1.0f; //PSYS_PART_END_ALPHA, transparency + prules.PartStartScaleX = scale; //PSYS_PART_START_SCALE + prules.PartStartScaleY = scale; + prules.PartEndScaleX = scale; //PSYS_PART_END_SCALE + prules.PartEndScaleY = scale; + prules.PartMaxAge = 1.0f; //PSYS_PART_MAX_AGE + prules.PartAcceleration.X = 0.0f; //PSYS_SRC_ACCEL + prules.PartAcceleration.Y = 0.0f; + prules.PartAcceleration.Z = 0.0f; + //prules.Pattern = Primitive.ParticleSystem.SourcePattern.Explode; //PSYS_SRC_PATTERN + //prules.Texture = LLUUID.Zero;//= LLUUID //PSYS_SRC_TEXTURE, default used if blank + prules.BurstRate = rate; //PSYS_SRC_BURST_RATE + prules.BurstPartCount = 1; //PSYS_SRC_BURST_PART_COUNT + prules.BurstRadius = 0.5f; //PSYS_SRC_BURST_RADIUS + prules.BurstSpeedMin = speed; //PSYS_SRC_BURST_SPEED_MIN + prules.BurstSpeedMax = speed; //PSYS_SRC_BURST_SPEED_MAX + prules.MaxAge = 0.0f; //PSYS_SRC_MAX_AGE + prules.Target = To.UUID; //PSYS_SRC_TARGET_KEY + prules.AngularVelocity.X = 0.0f; //PSYS_SRC_OMEGA + prules.AngularVelocity.Y = 0.0f; + prules.AngularVelocity.Z = 0.0f; + prules.InnerAngle = 0.0f; //PSYS_SRC_ANGLE_BEGIN + prules.OuterAngle = 0.0f; //PSYS_SRC_ANGLE_END + + prules.CRC = 1; //activates the particle system?? + From.AddNewParticleSystem(prules); + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMController.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMController.cs new file mode 100644 index 0000000000..465741f06a --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMController.cs @@ -0,0 +1,684 @@ +// CMController.cs +// User: bongiojp +// + +using System; +using System.Collections.Generic; +using System.Collections; +using libsecondlife; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using System.Threading; +using System.Diagnostics; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + /// + /// The controller in a Model-View-Controller framework. This controller catches actions by the avatars, creates work packets, loops through these work packets in a separate thread, + /// then dictates to the model how the data should change and dictates to the view which data should be displayed. The main mechanism for interaction is through the simchat system. + /// + public class CMController + { + + /// + /// The structure that defines the basic unit of work which is produced when a user sends commands to the ContentMangaementSystem. + /// + private struct Work + { + public WorkType Type; + public Object Data1; //Just space for holding data. + public Object Data2; //Just more space for holding data. + public uint LocalId; //Convenient + public LLUUID UUID; //Convenient + } + + /// + /// Identifies what the data in struct Work should be used for. + /// + private enum WorkType + { + NONE, + OBJECTATTRIBUTECHANGE, + PRIMITIVEADDED, + OBJECTDUPLICATED, + OBJECTKILLED, + UNDODID, + NEWCLIENT, + SIMCHAT + } + + /// + /// Used to keep track of whether a list has been produced yet and whether that list is up-to-date compard to latest revision on disk. + /// + [Flags] + private enum State + { + NONE = 0, + DIRTY = 1, // The meta entities may not correctly represent the last revision. + SHOWING_CHANGES = 1<<1 // The meta entities are being shown to user. + } + + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// The queue that keeps track of which actions have happened. The MainLoop thread eats through this queue. + /// + private static OpenSim.Framework.BlockingQueue m_WorkQueue = new OpenSim.Framework.BlockingQueue(); + + /// + /// A list of all the scenes that should be revisioned. Controller is the only class that keeps track of all scenes in the region. + /// + Hashtable m_sceneList = Hashtable.Synchronized(new Hashtable()); + + /// + /// The estate module is used to identify which clients are estateManagers. Presently, the controller only pays attention to estate managers. + /// + IEstateModule m_estateModule = null; + + Thread m_thread = null; + State m_state = State.NONE; + bool init = false; + + //These have to be global variables, threading doesn't allow for passing parameters. (Used in MainLoop) + CMModel m_model = null; + CMView m_view = null; + int m_channel = -1; + + /// + /// Initializes a work thread with an initial scene. Additional scenes should be added through the RegisterNewRegion method. + /// + /// + /// + /// + /// + /// + /// + /// + /// The first scene to keep track of. + /// + /// + /// The simchat channel number to listen to for instructions + /// + public CMController(CMModel model, CMView view, Scene scene, int channel) + { + m_model = model; m_view = view; m_channel = channel; + RegisterNewRegion(scene); + Initialize(model, view, scene, channel); + } + + private void Initialize(CMModel model, CMView view, Scene scene, int channel) + { + lock(this) + { + m_estateModule = scene.RequestModuleInterface(); + m_thread = new Thread( MainLoop ); + m_thread.Name = "Content Management"; + m_thread.IsBackground = true; + m_thread.Start(); + ThreadTracker.Add(m_thread); + m_state = State.NONE; + } + } + + /// + /// Register a new scene object to keep track of for revisioning. Starts the controller monitoring actions of clients within the given scene. + /// + /// + /// A + /// + public void RegisterNewRegion(Scene scene) + { + m_sceneList.Add(scene.RegionInfo.RegionID, scene); + + m_log.Debug("[CONTENT MANAGEMENT] Registering new region: " + scene.RegionInfo.RegionID); + m_log.Debug("[CONTENT MANAGEMENT] Initializing Content Management System."); + + scene.EventManager.OnNewClient += StartManaging; + scene.EventManager.OnRemovePresence += StopManaging; + // scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnObjectBeingRemovedFromScene += GroupBeingDeleted; + } + + /// + /// Run in a thread of its own. A endless loop that consumes (or blocks on) and work queue. Thw work queue is filled through client actions. + /// + private void MainLoop() + { + CMModel model = m_model; CMView view = m_view; int channel = m_channel; + Work currentJob = new Work(); + while(true) + { + currentJob = m_WorkQueue.Dequeue(); + m_log.Debug("[CONTENT MANAGEMENT] MAIN LOOP -- DeQueued a request"); + m_log.Debug("[CONTENT MANAGEMENT] MAIN LOOP -- Work type: " + currentJob.Type); + switch(currentJob.Type) + { + case WorkType.NONE: + break; + case WorkType.OBJECTATTRIBUTECHANGE: + ObjectAttributeChanged(model, view, currentJob.LocalId); + break; + case WorkType.PRIMITIVEADDED: + PrimitiveAdded(model, view, currentJob); + break; + case WorkType.OBJECTDUPLICATED: + ObjectDuplicated(model, view, currentJob.LocalId); + break; + case WorkType.OBJECTKILLED: + ObjectKilled(model, view, (SceneObjectGroup) currentJob.Data1); + break; + case WorkType.UNDODID: + UndoDid(model, view, currentJob.UUID); + break; + case WorkType.NEWCLIENT: + NewClient(view, (IClientAPI) currentJob.Data1); + break; + case WorkType.SIMCHAT: + m_log.Debug("[CONTENT MANAGEMENT] MAIN LOOP -- Message received: " + ((ChatFromViewerArgs) currentJob.Data1).Message); + SimChat(model, view, (ChatFromViewerArgs) currentJob.Data1, channel); + break; + default: + m_log.Debug("[CONTENT MANAGEMENT] MAIN LOOP -- uuuuuuuuuh, what?"); + break; + } + } + } + + /// + /// Only called by the MainLoop. Updates the view of a new client with metaentities if diff-mode is currently enabled. + /// + private void NewClient(CMView view, IClientAPI client) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + view.SendMetaEntitiesToNewClient(client); + } + + /// + /// Only called by the MainLoop. Displays new green auras over the newly created part when a part is shift copied. + /// + private void ObjectDuplicated(CMModel model, CMView view, uint localId) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + view.DisplayAuras(model.CheckForNewEntitiesMissingAuras( GetGroupByPrim(localId).Scene )); + } + + /// + /// Only called by the MainLoop. + /// + private void ObjectKilled(CMModel model, CMView view, SceneObjectGroup group) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + { + view.RemoveOrUpdateDeletedEntity(group); + model.RemoveOrUpdateDeletedEntity(group); + } + } + + /// + /// Only called by the MainLoop. + /// + private void UndoDid(CMModel model, CMView view, LLUUID uuid) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + { + ContentManagementEntity ent = model.FindMetaEntityAffectedByUndo(uuid); + if (ent != null) + view.DisplayEntity(ent); + } + } + + /// + /// Only called by the MainLoop. + /// + private void ObjectAttributeChanged(CMModel model, CMView view, uint LocalId) + { + SceneObjectGroup group = null; + if ((m_state & State.SHOWING_CHANGES) > 0) + { + group = GetGroupByPrim(LocalId); + if (group != null) + { + view.DisplayAuras( model.UpdateNormalEntityEffects(group) ); //Might be a normal entity (green aura) + m_view.DisplayMetaEntity(group.UUID); //Might be a meta entity (blue aura) + } + } + } + + /// + /// Only called by the MainLoop. + /// + private void PrimitiveAdded(CMModel model, CMView view, Work currentJob) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + { + foreach(Object scene in m_sceneList.Values) + m_view.DisplayAuras(model.CheckForNewEntitiesMissingAuras((Scene) scene)); + } + } + + /// + /// Only called by the MainLoop. Takes the message from a user sent to the channel and executes the proper command. + /// + public void SimChat(CMModel model, CMView view, ChatFromViewerArgs e, int channel) + { + if (e.Channel != channel) + return; + if (e.Sender == null) + return; + + m_log.Debug("[CONTENT MANAGEMENT] Message received: " + e.Message); + + IClientAPI client = e.Sender; + Scene scene = (Scene) e.Scene; + string message = e.Message; + string[] args = e.Message.Split(new char[] {' '}); + + ScenePresence avatar = scene.GetScenePresence(client.AgentId); + + if (!(m_estateModule.IsManager(avatar.UUID))) + { + m_log.Debug("[CONTENT MANAGEMENT] Message sent from non Estate Manager ... ignoring."); + view.SendSimChatMessage(scene, "You must be an estate manager to perform that action."); + return; + } + + switch(args[0]) + { + case "ci": + case "commit": + commit(message, scene, model, view); + break; + case "dm": + case "diff-mode": + diffmode(scene, model, view); + break; + case "rb": + case "rollback": + rollback(scene, model, view); + break; + case "help": + m_view.DisplayHelpMenu(scene); + break; + default: + view.SendSimChatMessage(scene, "Command not found: " + args[0]); + break; + } + } + + /// + /// Only called from within the SimChat method. Hides all auras and meta entities, + /// retrieves the current scene object list with the most recent revision retrieved from the model for each scene, + /// then lets the view update the clients of the new objects. + /// + protected void rollback(Scene scene, CMModel model, CMView view) + { + if ((m_state & State.SHOWING_CHANGES) > 0) + { + view.HideAllAuras(); + view.HideAllMetaEntities(); + } + + System.Collections.Generic.List proximitySceneList = ScenesInOrderOfProximity( m_sceneList, scene); + foreach(Scene currScene in proximitySceneList) + model.RollbackRegion(currScene); + + if ((m_state & State.DIRTY) != 0 ) + { + model.DeleteAllMetaObjects(); + foreach(Scene currScene in proximitySceneList) + model.UpdateCMEntities(currScene); + } + + if ((m_state & State.SHOWING_CHANGES) > 0) + view.DisplayRecentChanges(); + + } + + /// + /// Only called from within the SimChat method. + /// + protected void diffmode(Scene scene, CMModel model, CMView view) + { + System.Collections.Generic.List proximitySceneList = ScenesInOrderOfProximity( m_sceneList, scene); + + if ((m_state & State.SHOWING_CHANGES) > 0) // TURN OFF + { + view.SendSimChatMessage(scene, "Hiding all meta objects."); + view.HideAllMetaEntities(); + view.HideAllAuras(); + view.SendSimChatMessage(scene, "Diff-mode = OFF"); + + m_state &= ~State.SHOWING_CHANGES; + return; + } + else // TURN ON + { + if ((m_state & State.DIRTY) != 0 || m_state == State.NONE) + { + view.SendSimChatMessage(scene, "Hiding meta objects and replacing with latest revision"); + //Hide objects from users and Forget about them + view.HideAllMetaEntities(); + view.HideAllAuras(); + model.DeleteAllMetaObjects(); + //Recreate them from backend files + foreach(Object currScene in m_sceneList.Values) + model.UpdateCMEntities((Scene) currScene); + } + else if ((m_state & State.DIRTY) != 0) { + view.SendSimChatMessage(scene, "Forming list of meta entities with latest revision"); + foreach(Scene currScene in proximitySceneList) + model.UpdateCMEntities(currScene); + } + + view.SendSimChatMessage(scene, "Displaying differences between last revision and current environment"); + foreach(Scene currScene in proximitySceneList) + model.CheckForNewEntitiesMissingAuras(currScene); + view.DisplayRecentChanges(); + + view.SendSimChatMessage(scene, "Diff-mode = ON"); + m_state |= State.SHOWING_CHANGES; + m_state &= ~State.DIRTY; + } + } + + /// + /// Only called from within the SimChat method. + /// + protected void commit(string message, Scene scene, CMModel model, CMView view) + { + System.Collections.Generic.List proximitySceneList = ScenesInOrderOfProximity( m_sceneList, scene); + + string[] args = message.Split(new char[] {' '}); + + char[] logMessage = {' '}; + if (args.Length > 1) + { + logMessage = new char[message.Length - (args[0].Length)]; + message.CopyTo(args[0].Length, logMessage, 0, message.Length - (args[0].Length)); + } + + m_log.Debug("[CONTENT MANAGEMENT] Saving terrain and objects of region."); + foreach(Scene currScene in proximitySceneList) + { + model.CommitRegion(currScene, new String(logMessage)); + view.SendSimChatMessage(scene, "Region Saved Successfully: " + currScene.RegionInfo.RegionName); + } + + view.SendSimChatMessage(scene, "Successfully saved all regions."); + m_state |= State.DIRTY; + + if ((m_state & State.SHOWING_CHANGES) > 0) //DISPLAY NEW CHANGES INSTEAD OF OLD CHANGES + { + view.SendSimChatMessage(scene, "Updating differences between new revision and current environment."); + //Hide objects from users and Forget about them + view.HideAllMetaEntities(); + view.HideAllAuras(); + model.DeleteAllMetaObjects(); + + //Recreate them from backend files + foreach(Scene currScene in proximitySceneList) + { + model.UpdateCMEntities(currScene); + view.SendSimChatMessage(scene, "Finished updating differences between current scene and last revision: " + currScene.RegionInfo.RegionName); + } + + //Display new objects to users1 + view.DisplayRecentChanges(); + view.SendSimChatMessage(scene, "Finished updating for DIFF-MODE."); + m_state &= ~(State.DIRTY); + m_state |= State.SHOWING_CHANGES; + } + } + + /// + /// Takes a list of scenes and forms a new orderd list according to the proximity of scenes to the second argument. + /// + protected static System.Collections.Generic.List ScenesInOrderOfProximity( Hashtable sceneList, Scene scene) + { + int somethingAddedToList = 1; + System.Collections.Generic.List newList = new List(); + newList.Add(scene); + + if (! sceneList.ContainsValue(scene)) + { + foreach(Object sceneObj in sceneList) + newList.Add((Scene) sceneObj); + return newList; + } + + while(somethingAddedToList > 0) + { + somethingAddedToList = 0; + for(int i = 0; i < newList.Count; i++) + { + foreach(Object sceneObj in sceneList.Values) + { + if (newList[i].CheckNeighborRegion(((Scene)sceneObj).RegionInfo) && (! newList.Contains((Scene)sceneObj)) ) + { + newList.Add((Scene)sceneObj); + somethingAddedToList++; + } + } + } + } + + foreach(Object sceneObj in sceneList.Values) + if (! newList.Contains((Scene)sceneObj)) + newList.Add((Scene)sceneObj); + + return newList; + } + + /// + /// Searches in all scenes for a SceneObjectGroup that contains a part with a specific localID. If found, the object is returned. Else null is returned. + /// + private SceneObjectGroup GetGroupByPrim(uint localID) + { + foreach(Object currScene in m_sceneList.Values) + { + foreach (EntityBase ent in ((Scene)currScene).GetEntities()) + { + if (ent is SceneObjectGroup) + { + if (((SceneObjectGroup)ent).HasChildPrim(localID)) + return (SceneObjectGroup)ent; + } + } + } + return null; + } + //------------------------------------------------ EVENTS ----------------------------------------------------// + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, LLUUID regionID) + { + } + + /// + /// Adds extra handlers to a number of events so that the controller can produce work based on the client's actions. + /// + protected void StartManaging(IClientAPI client) + { + m_log.Debug("[CONTENT MANAGEMENT] Registering channel with chat services."); + client.OnChatFromViewer += SimChatSent; + init = true; + + OnNewClient(client); + + m_log.Debug("[CONTENT MANAGEMENT] Adding handlers to client."); + client.OnUpdatePrimScale += UpdateSingleScale; + client.OnUpdatePrimGroupScale += UpdateMultipleScale; + client.OnUpdatePrimGroupPosition += UpdateMultiplePosition; + client.OnUpdatePrimSinglePosition += UpdateSinglePosition; + client.OnUpdatePrimGroupRotation += UpdateMultipleRotation; + client.OnUpdatePrimSingleRotation += UpdateSingleRotation; + client.OnAddPrim += UpdateNewParts; + client.OnObjectDuplicate += ObjectDuplicated; + client.OnObjectDuplicateOnRay += ObjectDuplicatedOnRay; + client.OnUndo += OnUnDid; + //client.OnUpdatePrimGroupMouseRotation += m_innerScene.UpdatePrimRotation; + } + + /// + /// + /// + protected void StopManaging(LLUUID clientUUID) + { + foreach(Object sceneobj in m_sceneList.Values) + { + ScenePresence presence = ((Scene)sceneobj).GetScenePresence(clientUUID); + if (presence != null) + { + IClientAPI client = presence.ControllingClient; + m_log.Debug("[CONTENT MANAGEMENT] Unregistering channel with chat services."); + client.OnChatFromViewer -= SimChatSent; + + m_log.Debug("[CONTENT MANAGEMENT] Removing handlers to client"); + client.OnUpdatePrimScale -= UpdateSingleScale; + client.OnUpdatePrimGroupScale -= UpdateMultipleScale; + client.OnUpdatePrimGroupPosition -= UpdateMultiplePosition; + client.OnUpdatePrimSinglePosition -= UpdateSinglePosition; + client.OnUpdatePrimGroupRotation -= UpdateMultipleRotation; + client.OnUpdatePrimSingleRotation -= UpdateSingleRotation; + client.OnAddPrim -= UpdateNewParts; + client.OnObjectDuplicate -= ObjectDuplicated; + client.OnObjectDuplicateOnRay -= ObjectDuplicatedOnRay; + client.OnUndo -= OnUnDid; + //client.OnUpdatePrimGroupMouseRotation += m_innerScene.UpdatePrimRotation; + return; + } + } + } + + protected void GroupBeingDeleted(SceneObjectGroup group) + { + m_log.Debug("[CONTENT MANAGEMENT] Something was deleted!!!"); + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTKILLED; + moreWork.Data1 = group.Copy(); + m_WorkQueue.Enqueue(moreWork); + } + + //This is stupid, the same information is contained in the first and second argument + protected void SimChatSent(Object x, ChatFromViewerArgs e) + { + m_log.Debug("[CONTENT MANAGEMENT] SIMCHAT SENT !!!!!!!"); + m_log.Debug("[CONTENT MANAGEMENT] message was: " + e.Message); + Work moreWork = new Work(); + moreWork.Type = WorkType.SIMCHAT; + moreWork.Data1 = e; + m_WorkQueue.Enqueue(moreWork); + } + + protected void ObjectDuplicated(uint localID, LLVector3 offset, uint dupeFlags, LLUUID AgentID, LLUUID GroupID) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTDUPLICATED; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] dup queue"); + } + + protected void ObjectDuplicatedOnRay(uint localID, uint dupeFlags, LLUUID AgentID, LLUUID GroupID, + LLUUID RayTargetObj, LLVector3 RayEnd, LLVector3 RayStart, + bool BypassRaycast, bool RayEndIsIntersection, bool CopyCenters, bool CopyRotates) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTDUPLICATED; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] dup queue"); + } + + protected void OnNewClient(IClientAPI client) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.NEWCLIENT; + moreWork.Data1 = client; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] new client"); + } + + protected void OnUnDid(IClientAPI remoteClient, LLUUID primId) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.UNDODID; + moreWork.UUID = primId; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] undid"); + } + + protected void UpdateSinglePosition(uint localID, LLVector3 pos, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] move"); + } + + /// + /// + /// + protected void UpdateSingleRotation(uint localID, LLQuaternion rot, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] rot"); + } + + protected void UpdateSingleScale(uint localID, LLVector3 scale, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] scale"); + } + + protected void UpdateMultiplePosition(uint localID, LLVector3 pos, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] pos"); + } + + protected void UpdateMultipleRotation(uint localID, LLQuaternion rot, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] rot"); + } + + protected void UpdateMultipleScale(uint localID, LLVector3 scale, IClientAPI remoteClient) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.OBJECTATTRIBUTECHANGE; + moreWork.LocalId = localID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT]scale"); + } + + protected void UpdateNewParts(LLUUID ownerID, LLVector3 RayEnd, LLQuaternion rot, PrimitiveBaseShape shape, + byte bypassRaycast, LLVector3 RayStart, LLUUID RayTargetID, + byte RayEndIsIntersection) + { + Work moreWork = new Work(); + moreWork.Type = WorkType.PRIMITIVEADDED; + moreWork.UUID = ownerID; + m_WorkQueue.Enqueue(moreWork); + m_log.Debug("[CONTENT MANAGEMENT] new parts"); + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMEntityCollection.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMEntityCollection.cs new file mode 100644 index 0000000000..9f50e239fb --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMEntityCollection.cs @@ -0,0 +1,148 @@ +// CMEntityCollection.cs created with MonoDevelop +// User: bongiojp at 10:09 AM 7/7/2008 +// +// Creates, Deletes, Stores ContentManagementEntities +// + + +using System; +using System.Collections.Generic; +using System.Collections; +using libsecondlife; +using Nini.Config; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using System.Threading; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + public class CMEntityCollection + { + // private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + // Any ContentManagementEntities that represent old versions of current SceneObjectGroups or + // old versions of deleted SceneObjectGroups will be stored in this hash table. + // The LLUUID keys are from the SceneObjectGroup RootPart UUIDs + protected Hashtable m_CMEntityHash = Hashtable.Synchronized(new Hashtable()); //LLUUID to ContentManagementEntity + + // SceneObjectParts that have not been revisioned will be given green auras stored in this hashtable + // The LLUUID keys are from the SceneObjectPart that they are supposed to be on. + protected Hashtable m_NewlyCreatedEntityAura = Hashtable.Synchronized(new Hashtable()); //LLUUID to AuraMetaEntity + + public Hashtable Entities + { + get { return m_CMEntityHash; } + } + + public Hashtable Auras + { + get {return m_NewlyCreatedEntityAura; } + } + + public CMEntityCollection() + {} + + public bool AddAura(ContentManagementEntity aura) + { + if (m_NewlyCreatedEntityAura.ContainsKey(aura.UUID)) + return false; + m_NewlyCreatedEntityAura.Add(aura.UUID, aura); + return true; + } + + public bool AddEntity(ContentManagementEntity ent) + { + if (m_CMEntityHash.ContainsKey(ent.UUID)) + return false; + m_CMEntityHash.Add(ent.UUID, ent); + return true; + } + + public bool RemoveNewlyCreatedEntityAura(LLUUID uuid) + { + if (!m_NewlyCreatedEntityAura.ContainsKey(uuid)) + return false; + m_NewlyCreatedEntityAura.Remove(uuid); + return true; + } + + public bool RemoveEntity(LLUUID uuid) + { + if (!m_CMEntityHash.ContainsKey(uuid)) + return false; + m_CMEntityHash.Remove(uuid); + return true; + } + + public void ClearAll() + { + m_CMEntityHash.Clear(); + m_NewlyCreatedEntityAura.Clear(); + } + + + + // Old uuid and new sceneobjectgroup + public AuraMetaEntity CreateAuraForNewlyCreatedEntity(SceneObjectPart part) + { + AuraMetaEntity ent = new AuraMetaEntity(part.ParentGroup.Scene, + part.ParentGroup.Scene.PrimIDAllocate(), + part.GetWorldPosition(), + MetaEntity.TRANSLUCENT, + new LLVector3(0,254,0), + part.Scale + ); + m_NewlyCreatedEntityAura.Add(part.UUID, ent); + return ent; + } + + // Old uuid and new sceneobjectgroup + public ContentManagementEntity CreateNewEntity(SceneObjectGroup group) + { + ContentManagementEntity ent = new ContentManagementEntity(group, false); + m_CMEntityHash.Add(group.UUID, ent); + return ent; + } + + public ContentManagementEntity CreateNewEntity(String xml, Scene scene) + { + ContentManagementEntity ent = new ContentManagementEntity(xml, scene, false); + if (ent == null) + return null; + m_CMEntityHash.Add(ent.UnchangedEntity.UUID, ent); + return ent; + } + + // Check if there are SceneObjectGroups in the list that do not have corresponding ContentManagementGroups in the CMEntityHash + public System.Collections.ArrayList CheckForMissingEntities(System.Collections.Generic.List currList) + { + System.Collections.ArrayList missingList = new System.Collections.ArrayList(); + SceneObjectGroup temp = null; + foreach( EntityBase currObj in currList ) + { + if (! (currObj is SceneObjectGroup)) + continue; + temp = (SceneObjectGroup) currObj; + + if (m_CMEntityHash.ContainsKey(temp.UUID)) + { + foreach(SceneObjectPart part in temp.Children.Values) + if (!((ContentManagementEntity)m_CMEntityHash[temp.UUID]).HasChildPrim(part.UUID)) + missingList.Add(part); + } + else //Entire group is missing from revision. (and is a new part in region) + { + foreach(SceneObjectPart part in temp.Children.Values) + missingList.Add(part); + } + } + return missingList; + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMModel.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMModel.cs new file mode 100644 index 0000000000..d3b3b77606 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMModel.cs @@ -0,0 +1,309 @@ +// CMModel.cs +// User: bongiojp +// +// + +using System; +using System.Collections.Generic; +using System.Collections; +using libsecondlife; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using System.Diagnostics; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + public class CMModel + { + static float TimeToUpdate = 0; + static float TimeToConvertXml = 0; + + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + IContentDatabase m_database = null; + + /// + /// The class that contains all auras and metaentities used in the CMS. + /// + CMEntityCollection m_MetaEntityCollection = new CMEntityCollection(); + + public CMEntityCollection MetaEntityCollection + { + get { return m_MetaEntityCollection; } + } + + public CMModel() + { + } + + public void Initialise(string database) + { + if (database == "FileSystemDatabase") + m_database = new FileSystemDatabase(); + else if (database == "GitDatabase") + m_database = new GitDatabase(); + } + + public void InitialiseDatabase(Scene scene, string dir) + { + m_database.Initialise(scene, dir); + } + + /// + /// Should be called just once to finish initializing the database. + /// + public void PostInitialise() + { + m_database.PostInitialise(); + } + + public ContentManagementEntity FindMetaEntityAffectedByUndo(LLUUID uuid) + { + ContentManagementEntity ent = GetMetaGroupByPrim(uuid); + return ent; + } + + /// + /// Removes the green aura when an a new scene object group is deleted. + /// + public void RemoveOrUpdateDeletedEntity(SceneObjectGroup group) + { + // Deal with new parts not revisioned that have been deleted. + foreach(SceneObjectPart part in group.Children.Values) + if (m_MetaEntityCollection.Auras.ContainsKey(part.UUID)) + m_MetaEntityCollection.RemoveNewlyCreatedEntityAura(part.UUID); + } + + /// + /// Uses the database to serialize all current scene objects into xml and save into a database with an accompanying log message. + /// + public void CommitRegion(Scene scene, String logMessage) + { + m_log.Debug("[CONTENT MANAG] saving " + scene.RegionInfo.RegionName + " with log message: " + logMessage + " length of message: " + logMessage.Length); + m_database.SaveRegion(scene.RegionInfo.RegionID, scene.RegionInfo.RegionName, logMessage); + m_log.Debug("[CONTENT MANAG] the region name we are dealing with heeeeeeeere: " + scene.RegionInfo.RegionName ); + } + + /// + /// Retrieves the latest revision of a region in xml form, + /// converts it to scene object groups and scene presences, + /// swaps the current scene's entity list with the revision's list. + /// Note: Since deleted objects while + /// + public void RollbackRegion(Scene scene) + { + System.Collections.ArrayList xmllist = null; + SceneObjectGroup temp = null; + System.Collections.Hashtable deleteListUUIDs = new Hashtable(); + Dictionary SearchList = new Dictionary(); + Dictionary ReplacementList = new Dictionary(); + int revision = m_database.GetMostRecentRevision(scene.RegionInfo.RegionID); + EntityBase[] searchArray; + + xmllist = m_database.GetRegionObjectXMLList(scene.RegionInfo.RegionID, revision); + if (xmllist == null) + { + m_log.Info("[CMMODEL]: Region (" + scene.RegionInfo.RegionID + ") does not have given revision number (" + revision + ")."); + return; + } + + m_log.Info("[CMMODEL]: Region (" + scene.RegionInfo.RegionID + ") revision number (" + revision + ")."); + m_log.Info("[CMMODEL]: Scene Objects = " + xmllist.Count); + m_log.Info("[CMMODEL]: Converting scene entities list to specified revision."); + + m_log.ErrorFormat("[CMMODEL]: 1"); + + foreach (string xml in xmllist) + { + try{ + temp = new SceneObjectGroup(xml); + temp.SetScene(scene); + foreach(SceneObjectPart part in temp.Children.Values) + part.RegionHandle = scene.RegionInfo.RegionHandle; + ReplacementList.Add(temp.UUID, (EntityBase)temp); + } + catch(Exception e) + { + m_log.Info("[CMMODEL]: Error while creating replacement list for rollback: " + e); + } + } + + //If in scene but not in revision and not a client, remove them + while (true) + { + try + { + foreach(EntityBase entity in scene.GetEntities()) + { + if (entity == null) + continue; + + if (entity is ScenePresence) + { + ReplacementList.Add(entity.UUID, entity); + continue; + } + else //if (!ReplacementList.ContainsKey(entity.UUID)) + deleteListUUIDs.Add(entity.UUID, 0); + } + } + catch(Exception e) + { + m_log.ErrorFormat("[CMMODEL]: " + e); + deleteListUUIDs.Clear(); + ReplacementList.Clear(); + continue; + } + break; + } + + foreach(LLUUID uuid in deleteListUUIDs.Keys) + { + try + { + // I thought that the DeleteGroup() function would handle all of this, but it doesn't. I'm not sure WHAT it handles. + ((SceneObjectGroup)scene.Entities[uuid]).DetachFromBackup((SceneObjectGroup)scene.Entities[uuid]); + scene.PhysicsScene.RemovePrim(((SceneObjectGroup)scene.Entities[uuid]).RootPart.PhysActor); + scene.SendKillObject(scene.Entities[uuid].LocalId); + scene.m_innerScene.DeleteSceneObject(uuid, false); + ((SceneObjectGroup)scene.Entities[uuid]).DeleteGroup(); + } + catch(Exception e) + { + m_log.ErrorFormat("[CMMODEL]: Error while removing objects from scene: " + e); + } + } + + lock (scene) + { + scene.Entities = ReplacementList; + } + + foreach(EntityBase ent in ReplacementList.Values) + { + try + { + if (!(ent is SceneObjectGroup)) + continue; + + if ((((SceneObjectGroup)ent).RootPart.GetEffectiveObjectFlags() & (uint) LLObject.ObjectFlags.Phantom) == 0) + ((SceneObjectGroup)ent).ApplyPhysics(true); + ((SceneObjectGroup)ent).AttachToBackup(); + ((SceneObjectGroup)ent).HasGroupChanged = true; // If not true, then attaching to backup does nothing because no change is detected. + ((SceneObjectGroup)ent).ScheduleGroupForFullUpdate(); + } + catch(Exception e) + { + m_log.ErrorFormat("[CMMODEL]: Error while attaching new scene entities to backup and scheduling for a full update: " + e); + } + } + m_log.Info("[CMMODEL]: Scheduling a backup of new scene object groups to backup."); + scene.Backup(); + } + + /// + /// Detects if a scene object group from the scene list has moved or changed scale. The green aura + /// that surrounds the object is then moved or scaled with the group. + /// + public System.Collections.ArrayList UpdateNormalEntityEffects(SceneObjectGroup group) + { + System.Collections.ArrayList auraList = new System.Collections.ArrayList(); + if (group == null) + return null; + foreach(SceneObjectPart part in group.Children.Values) + { + if (m_MetaEntityCollection.Auras.ContainsKey(part.UUID)) + { + ((AuraMetaEntity)m_MetaEntityCollection.Auras[part.UUID]).SetAura(new LLVector3(0,254,0), part.Scale); + ((AuraMetaEntity)m_MetaEntityCollection.Auras[part.UUID]).RootPart.GroupPosition = part.GetWorldPosition(); + auraList.Add((AuraMetaEntity)m_MetaEntityCollection.Auras[part.UUID]); + } + } + return auraList; + } + + public void DeleteAllMetaObjects() + { + m_MetaEntityCollection.ClearAll(); + } + + /// + /// Downloads the latest revision of the given scene and converts the xml file to CMEntities. After this method, the view can find the differences + /// and display the differences to clients. + /// + public void UpdateCMEntities(Scene scene) + { + Stopwatch x = new Stopwatch(); + x.Start(); + + System.Collections.ArrayList xmllist = null; + m_log.Debug("[CONTENT MANAGEMENT] Retrieving object xml files for region: " + scene.RegionInfo.RegionID); + xmllist = m_database.GetRegionObjectXMLList(scene.RegionInfo.RegionID); + m_log.Info("[FSDB]: got list"); + if (xmllist == null) + return; + + Stopwatch y = new Stopwatch(); + y.Start(); + foreach (string xml in xmllist) + m_MetaEntityCollection.CreateNewEntity(xml, scene); + y.Stop(); + TimeToConvertXml += y.ElapsedMilliseconds; + m_log.Info("[FileSystemDatabase] Time spent converting xml to metaentities for " + scene.RegionInfo.RegionName + ": " + y.ElapsedMilliseconds); + m_log.Info("[FileSystemDatabase] Time spent converting xml to metaentities so far: " + TimeToConvertXml); + + m_log.Info("[FSDB]: checking for new scene object parts missing green auras and create the auras"); + CheckForNewEntitiesMissingAuras(scene); + + x.Stop(); + TimeToUpdate += x.ElapsedMilliseconds; + m_log.Info("[FileSystemDatabase] Time spent Updating entity list for " + scene.RegionInfo.RegionName + ": " + x.ElapsedMilliseconds); + m_log.Info("[FileSystemDatabase] Time spent Updating so far: " + TimeToUpdate); + + } + + /// + /// Compares the scene's object group list to the list of meta entities. If there is an object group that does not have a corresponding meta entity + /// it is a new part that must have a green aura (for diff mode). + /// Returns list of ContentManagementEntities + /// + public ArrayList CheckForNewEntitiesMissingAuras(Scene scene) + { + ArrayList missingList = null; + ArrayList newList = new ArrayList(); + + m_log.Debug("[CONTENT MANAGEMENT] Checking for new scene object parts in scene: " + scene.RegionInfo.RegionName); + + //Check if the current scene has groups not included in the current list of MetaEntities + //If so, then the current scene's parts that are new should be marked green. + missingList = m_MetaEntityCollection.CheckForMissingEntities(scene.GetEntities()); + + foreach(Object missingPart in missingList) + { + if (m_MetaEntityCollection.Auras.ContainsKey(((SceneObjectPart)missingPart).UUID)) + continue; + newList.Add(m_MetaEntityCollection.CreateAuraForNewlyCreatedEntity((SceneObjectPart)missingPart)); + } + m_log.Info("Number of missing objects found: " + newList.Count); + return newList; + } + +//-------------------------------- HELPERS --------------------------------------------------------------------// + + public ContentManagementEntity GetMetaGroupByPrim(LLUUID uuid) + { + foreach (Object ent in m_MetaEntityCollection.Entities.Values) + { + if (((ContentManagementEntity)ent).HasChildPrim(uuid)) + return (ContentManagementEntity)ent; + } + return null; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMView.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMView.cs new file mode 100644 index 0000000000..f8b0ec9483 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/CMView.cs @@ -0,0 +1,156 @@ +// CMView.cs created with MonoDevelop +// User: bongiojp at 11:57 AM 7/3/2008 +// +// To change standard headers go to Edit->Preferences->Coding->Standard Headers +// + +using System; +using System.Collections.Generic; +using System.Collections; +using libsecondlife; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + public class CMView + { + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + CMModel m_model = null; + + public CMView() + {} + + public void Initialise(CMModel model) + { + m_model = model; + } + + public void SendMetaEntitiesToNewClient(IClientAPI client) + { + } + + /// + /// update all clients of red/green/blue auras and meta entities that the model knows about. + /// + public void DisplayRecentChanges() + { + m_log.Debug("[CONTENT MANAGEMENT] Sending update to clients for " + m_model.MetaEntityCollection.Entities.Count + " objects."); + DisplayEntities(m_model.MetaEntityCollection); + DisplayAuras(m_model.MetaEntityCollection); + } + + /// + /// Figures out if the part deleted was a new scene object part or a revisioned part that's been deleted. + /// If it's a new scene object, any green aura attached to it is deleted. + /// If a revisioned part is deleted, a new full update is sent to the environment of the meta entity, which will + /// figure out that there should be a red aura and not a blue aura/beam. + /// + public void RemoveOrUpdateDeletedEntity(SceneObjectGroup group) + { + // Deal with revisioned parts that have been deleted. + if (m_model.MetaEntityCollection.Entities.ContainsKey(group.UUID)) + ((ContentManagementEntity)m_model.MetaEntityCollection.Entities[group.UUID]).SendFullDiffUpdateToAll(); + + // Deal with new parts not revisioned that have been deleted. + foreach(SceneObjectPart part in group.Children.Values) + if (m_model.MetaEntityCollection.Auras.ContainsKey(part.UUID)) + ((AuraMetaEntity)m_model.MetaEntityCollection.Auras[part.UUID]).HideFromAll(); + } + + // Auras To + public void DisplayAuras(CMEntityCollection auraCollection) + { + foreach( Object ent in auraCollection.Auras.Values) + ((AuraMetaEntity)ent).SendFullUpdateToAll(); + } + + // Entities to ALL + public void DisplayEntities(CMEntityCollection entityCollection) + { + foreach( Object ent in entityCollection.Entities.Values) + ((ContentManagementEntity)ent).SendFullDiffUpdateToAll(); + } + + // Auras To Client + public void DisplayAuras(CMEntityCollection auraCollection, IClientAPI client) + { + foreach( Object ent in auraCollection.Auras.Values) + ((AuraMetaEntity)ent).SendFullUpdate(client); + } + + // Entities to Client + public void DisplayEntities(CMEntityCollection entityCollection, IClientAPI client) + { + foreach( Object ent in entityCollection.Entities.Values) + ((ContentManagementEntity)ent).SendFullDiffUpdate(client); + } + + // Entity to ALL + public void DisplayEntity(ContentManagementEntity ent) + { + ent.SendFullDiffUpdateToAll(); + } + + public void DisplayMetaEntity(LLUUID uuid) + { + ContentManagementEntity group = m_model.GetMetaGroupByPrim(uuid); + if (group != null) + group.SendFullDiffUpdateToAll(); + } + + // Auras from List To ALL + public void DisplayAuras(ArrayList list) + { + foreach( Object ent in list) + { + m_log.Debug("[CONTENT MANAGEMENT] displaying new aura riiiiiiiiiiiight NOW"); + ((AuraMetaEntity)ent).SendFullUpdateToAll(); + } + } + + // Entities from List to ALL + public void DisplayEntities(ArrayList list) + { + foreach( Object ent in list) + ((ContentManagementEntity)ent).SendFullDiffUpdateToAll(); + } + + public void DisplayHelpMenu(Scene scene) + { + string menu = "Menu:\n"; + menu += "commit (ci) - saves current state of the region to a database on the server\n"; + menu += "diff-mode (dm) - displays those aspects of region that have not been saved but changed since the very last revision. Will dynamically update as you change environment.\n"; + SendSimChatMessage(scene, menu); + } + + public void SendSimChatMessage(Scene scene, string message) + { + scene.SimChat(Helpers.StringToField(message), + ChatTypeEnum.Broadcast, 0, new LLVector3(0,0,0), "Content Manager", LLUUID.Zero, false); + } + + public void Hide(ContentManagementEntity ent) + { + ent.HideFromAll(); + } + + public void HideAllMetaEntities() + { + foreach(Object obj in m_model.MetaEntityCollection.Entities.Values) + ((ContentManagementEntity)obj).HideFromAll(); + } + + public void HideAllAuras() + { + foreach(Object obj in m_model.MetaEntityCollection.Auras.Values) + ((MetaEntity)obj).HideFromAll(); + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementEntity.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementEntity.cs new file mode 100644 index 0000000000..fb9df8f6ab --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementEntity.cs @@ -0,0 +1,326 @@ +// ContentManagementEntity.cs +// User: bongiojp +// +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + public class ContentManagementEntity : MetaEntity + { + static float TimeToDiff = 0; + static float TimeToCreateEntities = 0; + + // The LinkNum of parts in m_Entity and m_UnchangedEntity are the same though UUID and LocalId are different. + // This can come in handy. + protected SceneObjectGroup m_UnchangedEntity = null; + protected Dictionary m_BeamEntities = new Dictionary(); + protected Dictionary m_AuraEntities = new Dictionary(); + + /// + /// Should be set to true when there is a difference between m_UnchangedEntity and the corresponding scene object group in the scene entity list. + /// + bool DiffersFromSceneGroup = false; + + public SceneObjectGroup UnchangedEntity + { + get { return m_UnchangedEntity; } + } + + public ContentManagementEntity(SceneObjectGroup Unchanged, bool physics) : base(Unchanged, false) + { + m_UnchangedEntity = Unchanged.Copy(Unchanged.RootPart.OwnerID, Unchanged.RootPart.GroupID, false); + } + + public ContentManagementEntity(string objectXML, Scene scene, bool physics) : base(objectXML, scene, false) + { + m_UnchangedEntity = new SceneObjectGroup(objectXML); + } + + public override void Hide(IClientAPI client) + { + base.Hide(client); + foreach(MetaEntity group in m_AuraEntities.Values) + group.Hide(client); + foreach(MetaEntity group in m_BeamEntities.Values) + group.Hide(client); + } + + public override void HideFromAll() + { + base.HideFromAll(); + foreach(MetaEntity group in m_AuraEntities.Values) + group.HideFromAll(); + foreach(MetaEntity group in m_BeamEntities.Values) + group.HideFromAll(); + } + + public void SendFullDiffUpdateToAll() + { + FindDifferences(); + if (DiffersFromSceneGroup) + { + SendFullUpdateToAll(); + SendFullAuraUpdateToAll(); + SendFullBeamUpdateToAll(); + } + } + + public void SendFullDiffUpdate(IClientAPI client) + { + FindDifferences(); + if (DiffersFromSceneGroup) + { + SendFullUpdate(client); + SendFullAuraUpdate(client); + SendFullBeamUpdate(client); + } + } + + public void SendFullBeamUpdate(IClientAPI client) + { + if (DiffersFromSceneGroup) + { + foreach(BeamMetaEntity group in m_BeamEntities.Values) + group.SendFullUpdate(client); + } + } + + public void SendFullAuraUpdate(IClientAPI client) + { + if (DiffersFromSceneGroup) + { + foreach(AuraMetaEntity group in m_AuraEntities.Values) + group.SendFullUpdate(client); + } + } + + public void SendFullBeamUpdateToAll() + { + if (DiffersFromSceneGroup) + { + foreach(BeamMetaEntity group in m_BeamEntities.Values) + group.SendFullUpdateToAll(); + } + } + + public void SendFullAuraUpdateToAll() + { + if (DiffersFromSceneGroup) + { + foreach(AuraMetaEntity group in m_AuraEntities.Values) + group.SendFullUpdateToAll(); + } + } + + /// + /// Search for a corresponding group UUID in the scene. If not found, then the revisioned group this CMEntity represents has been deleted. Mark the metaentity appropriately. + /// If a matching UUID is found in a scene object group, compare the two for differences. If differences exist, Mark the metaentity appropriately. + /// + public void FindDifferences() + { + System.Collections.Generic.List sceneEntityList = m_Entity.Scene.GetEntities(); + DiffersFromSceneGroup = false; + // if group is not contained in scene's list + if(!ContainsKey(sceneEntityList, m_UnchangedEntity.UUID)) + { + foreach(SceneObjectPart part in m_UnchangedEntity.Children.Values) + { + // if scene list no longer contains this part, display translucent part and mark with red aura + if(! ContainsKey(sceneEntityList, part.UUID)) + { + // if already displaying a red aura over part, make sure its red + if (m_AuraEntities.ContainsKey(part.UUID)) + { + m_AuraEntities[part.UUID].SetAura(new LLVector3(254,0,0), part.Scale); + } + else + { + AuraMetaEntity auraGroup = new AuraMetaEntity(m_Entity.Scene, + m_Entity.Scene.PrimIDAllocate(), + part.GetWorldPosition(), + MetaEntity.TRANSLUCENT, + new LLVector3(254,0,0), + part.Scale + ); + m_AuraEntities.Add(part.UUID, auraGroup); + } + SceneObjectPart metaPart = m_Entity.GetLinkNumPart(part.LinkNum); + SetPartTransparency(metaPart, MetaEntity.TRANSLUCENT); + } + // otherwise, scene will not contain the part. note: a group can not remove a part without changing group id + } + + // a deleted part has no where to point a beam particle system, + // if a metapart had a particle system (maybe it represented a moved part) remove it + if (m_BeamEntities.ContainsKey(m_UnchangedEntity.RootPart.UUID)) + { + m_BeamEntities[m_UnchangedEntity.RootPart.UUID].HideFromAll(); + m_BeamEntities.Remove(m_UnchangedEntity.RootPart.UUID); + } + + DiffersFromSceneGroup = true; + } + // if scene list does contain group, compare each part in group for differences and display beams and auras appropriately + else + { + MarkWithDifferences((SceneObjectGroup)GetGroupByUUID(sceneEntityList, m_UnchangedEntity.UUID)); + } + } + + /// + /// Returns true if there was a change between meta entity and the entity group, false otherwise. + /// If true is returned, it is assumed the metaentity's appearance has changed to reflect the difference (though clients haven't been updated). + /// + public bool MarkWithDifferences(SceneObjectGroup sceneEntityGroup) + { + SceneObjectPart sceneEntityPart; + SceneObjectPart metaEntityPart; + Diff differences; + bool changed = false; + + // Use "UnchangedEntity" to do comparisons because its text, transparency, and other attributes will be just as the user + // had originally saved. + // m_Entity will NOT necessarily be the same entity as the user had saved. + foreach(SceneObjectPart UnchangedPart in m_UnchangedEntity.Children.Values) + { + //This is the part that we use to show changes. + metaEntityPart = m_Entity.GetLinkNumPart(UnchangedPart.LinkNum); + if (sceneEntityGroup.Children.ContainsKey(UnchangedPart.UUID)) + { + sceneEntityPart = sceneEntityGroup.Children[UnchangedPart.UUID]; + differences = Difference.FindDifferences(UnchangedPart, sceneEntityPart); + if (differences != Diff.NONE) + metaEntityPart.Text = "CHANGE: " + differences.ToString(); + if (differences != 0) + { + // Root Part that has been modified + if ((differences&Diff.POSITION) > 0) + { + // If the position of any part has changed, make sure the RootPart of the + // meta entity is pointing with a beam particle system + if (m_BeamEntities.ContainsKey(m_UnchangedEntity.RootPart.UUID)) + { + m_BeamEntities[m_UnchangedEntity.RootPart.UUID].HideFromAll(); + m_BeamEntities.Remove(m_UnchangedEntity.RootPart.UUID); + } + BeamMetaEntity beamGroup = new BeamMetaEntity(m_Entity.Scene, + m_Entity.Scene.PrimIDAllocate(), + m_UnchangedEntity.RootPart.GetWorldPosition(), + MetaEntity.TRANSLUCENT, + sceneEntityPart, + new LLVector3(0,0,254) + ); + m_BeamEntities.Add(m_UnchangedEntity.RootPart.UUID, beamGroup); + } + + if (m_AuraEntities.ContainsKey(UnchangedPart.UUID)) + { + m_AuraEntities[UnchangedPart.UUID].HideFromAll(); + m_AuraEntities.Remove(UnchangedPart.UUID); + } + AuraMetaEntity auraGroup = new AuraMetaEntity(m_Entity.Scene, + m_Entity.Scene.PrimIDAllocate(), + UnchangedPart.GetWorldPosition(), + MetaEntity.TRANSLUCENT, + new LLVector3(0,0,254), + UnchangedPart.Scale + ); + m_AuraEntities.Add(UnchangedPart.UUID, auraGroup); + SetPartTransparency(metaEntityPart, MetaEntity.TRANSLUCENT); + + DiffersFromSceneGroup = true; + } + else // no differences between scene part and meta part + { + if (m_BeamEntities.ContainsKey(m_UnchangedEntity.RootPart.UUID)) + { + m_BeamEntities[m_UnchangedEntity.RootPart.UUID].HideFromAll(); + m_BeamEntities.Remove(m_UnchangedEntity.RootPart.UUID); + } + if (m_AuraEntities.ContainsKey(UnchangedPart.UUID)) + { + m_AuraEntities[UnchangedPart.UUID].HideFromAll(); + m_AuraEntities.Remove(UnchangedPart.UUID); + } + SetPartTransparency(metaEntityPart, MetaEntity.NONE); + } + } + else //The entity currently in the scene is missing parts from the metaentity saved, so mark parts red as deleted. + { + if (m_AuraEntities.ContainsKey(UnchangedPart.UUID)) + { + m_AuraEntities[UnchangedPart.UUID].HideFromAll(); + m_AuraEntities.Remove(UnchangedPart.UUID); + } + AuraMetaEntity auraGroup = new AuraMetaEntity(m_Entity.Scene, + m_Entity.Scene.PrimIDAllocate(), + UnchangedPart.GetWorldPosition(), + MetaEntity.TRANSLUCENT, + new LLVector3(254,0,0), + UnchangedPart.Scale + ); + m_AuraEntities.Add(UnchangedPart.UUID, auraGroup); + SetPartTransparency(metaEntityPart, MetaEntity.TRANSLUCENT); + + DiffersFromSceneGroup = true; + } + } + return changed; + } + + private SceneObjectGroup GetGroupByUUID(System.Collections.Generic.List list, LLUUID uuid) + { + foreach (EntityBase ent in list) + { + if (ent is SceneObjectGroup) + if (ent.UUID == uuid) + return (SceneObjectGroup)ent; + } + return null; + } + + /// + /// Check if the revisioned scene object group that this CMEntity is based off of contains a child with the given UUID. + /// + public bool HasChildPrim(LLUUID uuid) + { + if (m_UnchangedEntity.Children.ContainsKey(uuid)) + return true; + return false; + } + + /// + /// Check if the revisioned scene object group that this CMEntity is based off of contains a child with the given LocalId. + /// + public bool HasChildPrim(uint localID) + { + foreach( SceneObjectPart part in m_UnchangedEntity.Children.Values) + if ( part.LocalId == localID ) + return true; + return false; + } + + /// + /// Check if an entitybase list (like that returned by scene.GetEntities() ) contains a group with the rootpart uuid that matches the current uuid. + /// + private bool ContainsKey(List list, LLUUID uuid) + { + foreach( EntityBase part in list) + if (part.UUID == uuid) + return true; + return false; + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementModule.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementModule.cs new file mode 100644 index 0000000000..22177ae031 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/ContentManagementModule.cs @@ -0,0 +1,111 @@ +// ContentManagementModule.cs +// User: bongiojp + + + +using System; +using System.Collections.Generic; +using libsecondlife; +using Nini.Config; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using System.Threading; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + public class ContentManagementModule : IRegionModule + { + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + CMController m_control = null; + CMModel m_model = null; + CMView m_view = null; + bool initialised = false; + bool m_posted = false; + bool m_enabled = true; + + public void Initialise(Scene scene, IConfigSource source) + { + string databaseDir = "./"; + string database = "FilesyStemDatabase"; + int channel = 345; + try + { + if (!source.Configs["CMS"].GetBoolean("Enabled", false)) + m_enabled = false; + + databaseDir = source.Configs["CMS"].GetString("Directory", databaseDir); + database = source.Configs["CMS"].GetString("Database", database); + channel = source.Configs["CMS"].GetInt("Channel", channel); + if (database != "FileSystemDatabase" && database != "GitDatabase") + { + m_log.ErrorFormat("[Content Management]: The Database attribute must be defined as either FileSystemDatabase or GitDatabase"); + m_enabled = false; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[Content Management]: Exception thrown while reading parameters from configuration file. Message: " + e); + m_enabled = false; + } + + if (!m_enabled) + { + m_log.Info("[Content Management]: Content Management System is not Enabled."); + return; + } + + lock(this) + { + if (!initialised) //only init once + { + m_view = new CMView(); + m_model = new CMModel(); + m_control = new CMController(m_model, m_view, scene, channel); + m_model.Initialise(database); + m_view.Initialise(m_model); + + initialised = true; + m_model.InitialiseDatabase(scene, databaseDir); + } + else + { + m_model.InitialiseDatabase(scene, databaseDir); + m_control.RegisterNewRegion(scene); + } + } + } + + public void PostInitialise() + { + if (! m_enabled) + return; + + lock(this) + { + if (!m_posted) //only post once + { + m_model.PostInitialise(); + m_posted = true; + } + } + } + + public void Close() + {} + + public string Name + { + get { return "ContentManagementModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/FileSystemDatabase.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/FileSystemDatabase.cs new file mode 100644 index 0000000000..e1f519b462 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/FileSystemDatabase.cs @@ -0,0 +1,260 @@ +// FileSystemDatabase.cs +// User: bongiojp + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Modules.World.Serialiser; +using OpenSim.Region.Environment.Modules.World.Terrain; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using Slash=System.IO.Path; +using System.Diagnostics; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + public class FileSystemDatabase : IContentDatabase + { + public static float TimeToDownload = 0; + public static float TimeToSave = 0; + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_repodir = null; + private Dictionary m_serialiser = new Dictionary(); + private Dictionary m_scenes = new Dictionary(); + + public FileSystemDatabase() + { + } + + public void Initialise(Scene scene, string dir) + { + lock(this) + { + if (m_repodir == null) + m_repodir = dir; + } + lock(m_scenes) + m_scenes.Add(scene.RegionInfo.RegionID, scene); + } + + + // Run once and only once. + public void PostInitialise() + { + SetupSerialiser(); + + m_log.Info("[FSDB]: Creating repository in " + m_repodir + "."); + CreateDirectory(); + } + + // called by postinitialise + private void SetupSerialiser() + { + if (m_serialiser.Count == 0) + foreach(LLUUID region in m_scenes.Keys) + m_serialiser.Add(region, + m_scenes[region].RequestModuleInterface() + ); + } + + // called by postinitialise + private void CreateDirectory() + { + string scenedir; + if (!Directory.Exists(m_repodir)) + Directory.CreateDirectory(m_repodir); + + foreach (LLUUID region in m_scenes.Keys) + { + scenedir = m_repodir + Slash.DirectorySeparatorChar + region + Slash.DirectorySeparatorChar; + if (!Directory.Exists(scenedir)) + Directory.CreateDirectory(scenedir); + } + } + + public int NumOfRegionRev(LLUUID regionid) + { + string scenedir = m_repodir + Slash.DirectorySeparatorChar + regionid + Slash.DirectorySeparatorChar; + m_log.Info("[FSDB]: Reading scene dir: " + scenedir); + string[] directories = Directory.GetDirectories(scenedir); + return directories.Length; + } + + public int GetMostRecentRevision(LLUUID regionid) + { + return NumOfRegionRev(regionid); + } + + public void SaveRegion(LLUUID regionid, string regionName, string logMessage) + { + m_log.Info("[FSDB]: ..............................."); + string scenedir = m_repodir + Slash.DirectorySeparatorChar + regionid + Slash.DirectorySeparatorChar; + + m_log.Info("[FSDB]: checking if scene directory exists: " + scenedir); + if (!Directory.Exists(scenedir)) + Directory.CreateDirectory(scenedir); + + int newRevisionNum = GetMostRecentRevision(regionid)+1; + string revisiondir = scenedir + newRevisionNum + Slash.DirectorySeparatorChar; + + m_log.Info("[FSDB]: checking if revision directory exists: " + revisiondir); + if (!Directory.Exists(revisiondir)) + Directory.CreateDirectory(revisiondir); + + try { + Stopwatch x = new Stopwatch(); + x.Start(); + if (m_scenes.ContainsKey(regionid)) + { + m_serialiser[regionid].SerialiseRegion(m_scenes[regionid], revisiondir); + } + x.Stop(); + TimeToSave += x.ElapsedMilliseconds; + m_log.Info("[FileSystemDatabase] Time spent serialising regions to files on disk for " + regionName + ": " + x.ElapsedMilliseconds); + m_log.Info("[FileSystemDatabase] Time spent serialising regions to files on disk so far: " + TimeToSave); + } + catch (Exception e) + { + m_log.ErrorFormat("[FSDB]: Serialisation of region failed: " + e); + return; + } + + try { + // Finish by writing log message. + FileStream file = new FileStream(revisiondir + "log", FileMode.Create, FileAccess.ReadWrite); + StreamWriter sw = new StreamWriter(file); + sw.Write(logMessage); + sw.Close(); + } + catch (Exception e) + { + m_log.ErrorFormat("[FSDB]: Failed trying to save log file " + e); + return; + } + } + + public System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid, int revision) + { + System.Collections.ArrayList objectList = new System.Collections.ArrayList(); + string filename = m_repodir + Slash.DirectorySeparatorChar + regionid + Slash.DirectorySeparatorChar + + + revision + Slash.DirectorySeparatorChar + "objects.xml"; + XmlDocument doc = new XmlDocument(); + XmlNode rootNode; + //int primCount = 0; + //SceneObjectGroup obj = null; + + if(File.Exists(filename)) + { + XmlTextReader reader = new XmlTextReader(filename); + reader.WhitespaceHandling = WhitespaceHandling.None; + doc.Load(reader); + reader.Close(); + rootNode = doc.FirstChild; + foreach (XmlNode aPrimNode in rootNode.ChildNodes) + { + objectList.Add(aPrimNode.OuterXml); + } + return objectList; + } + return null; + } + + public System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid) + { + int revision = NumOfRegionRev(regionid); + m_log.Info("[FSDB]: found revisions:" + revision); + System.Collections.ArrayList xmlList = new System.Collections.ArrayList(); + string filename = m_repodir + Slash.DirectorySeparatorChar + regionid + Slash.DirectorySeparatorChar + + + revision + Slash.DirectorySeparatorChar + "objects.xml"; + XmlDocument doc = new XmlDocument(); + XmlNode rootNode; + + + m_log.Info("[FSDB]: Checking if " + filename + " exists."); + if(File.Exists(filename)) + { + Stopwatch x = new Stopwatch(); + x.Start(); + + XmlTextReader reader = new XmlTextReader(filename); + reader.WhitespaceHandling = WhitespaceHandling.None; + doc.Load(reader); + reader.Close(); + rootNode = doc.FirstChild; + + foreach (XmlNode aPrimNode in rootNode.ChildNodes) + xmlList.Add(aPrimNode.OuterXml); + + x.Stop(); + TimeToDownload += x.ElapsedMilliseconds; + m_log.Info("[FileSystemDatabase] Time spent retrieving xml files so far: " + TimeToDownload); + + return xmlList; + } + return null; + } + + public string GetRegionObjectHeightMap(LLUUID regionid) + { + String filename = m_repodir + Slash.DirectorySeparatorChar + regionid + + Slash.DirectorySeparatorChar + "heightmap.r32"; + FileStream fs = new FileStream( filename, FileMode.Open); + StreamReader sr = new StreamReader(fs); + String result = sr.ReadToEnd(); + sr.Close(); + fs.Close(); + return result; + } + + public string GetRegionObjectHeightMap(LLUUID regionid, int revision) + { + String filename = m_repodir + Slash.DirectorySeparatorChar + regionid + + Slash.DirectorySeparatorChar + "heightmap.r32"; + FileStream fs = new FileStream( filename, FileMode.Open); + StreamReader sr = new StreamReader(fs); + String result = sr.ReadToEnd(); + sr.Close(); + fs.Close(); + return result; + } + + public System.Collections.Generic.SortedDictionary ListOfRegionRevisions(LLUUID regionid) + { + SortedDictionary revisionDict = new SortedDictionary(); + + string scenedir = m_repodir + Slash.DirectorySeparatorChar + regionid + Slash.DirectorySeparatorChar; + string[] directories = Directory.GetDirectories(scenedir); + + FileStream fs = null; + StreamReader sr = null; + String logMessage = ""; + String logLocation = ""; + foreach(string revisionDir in directories) + { + try { + logLocation = revisionDir + Slash.DirectorySeparatorChar + "log"; + fs = new FileStream( logLocation, FileMode.Open); + sr = new StreamReader(fs); + logMessage = sr.ReadToEnd(); + sr.Close(); + fs.Close(); + revisionDict.Add(revisionDir, logMessage); + } + catch (Exception) + {} + } + + return revisionDict; + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/GitDatabase.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/GitDatabase.cs new file mode 100644 index 0000000000..e337482c59 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/GitDatabase.cs @@ -0,0 +1,130 @@ +// GitDatabase.cs +// +// +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Modules.World.Serialiser; +using OpenSim.Region.Environment.Modules.World.Terrain; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; +using Slash=System.IO.Path; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + /// + /// Just a stub :-( + /// + public class GitDatabase : IContentDatabase + { + + public GitDatabase() + { + } + + public void Initialise(Scene scene, String dir) + { + + } + + public void PostInitialise() + { + + } + + public int NumOfObjectRev(LLUUID id) + { + return 0; + } + + public int NumOfRegionRev(LLUUID regionid) + { + return 0; + } + + public bool InRepository(LLUUID id) + { + return false; + } + + public void SaveRegion(LLUUID regionid, string regionName, string logMessage) + { + + } + + public System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid) + { + return null; + } + + public System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid, int revision) + { + return null; + } + + public string GetRegionObjectXML(LLUUID regionid) + { + return null; + } + + public string GetRegionObjectXML(LLUUID regionid, int revision) + { + return null; + } + + public string GetRegionObjectHeightMap(LLUUID regionid) + { + return null; + } + + public string GetRegionObjectHeightMap(LLUUID regionid, int revision) + { + return null; + } + + public System.Collections.ArrayList GetObjectsFromRegion(LLUUID regionid, int revision) + { + return null; + } + + public System.Collections.Generic.SortedDictionary ListOfRegionRevisions(LLUUID id) + { + return null; + } + + public void SaveObject(SceneObjectGroup entity) + { + } + + public SceneObjectGroup GetMostRecentObjectRevision(LLUUID id) + { + return null; + } + + public SceneObjectGroup GetObjectRevision(LLUUID id, int revision) + { + return null; + } + + public System.Collections.Generic.SortedDictionary ListOfObjectRevisions(LLUUID id) + { + return null; + } + + public int GetMostRecentRevision(LLUUID regionid) + { + return 0; + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/IContentDatabase.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/IContentDatabase.cs new file mode 100644 index 0000000000..f37f95df12 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/IContentDatabase.cs @@ -0,0 +1,57 @@ +// IContentDatabase.cs +// User: bongiojp +// +// +// + +using System; +using libsecondlife; +using OpenSim.Region.Environment.Scenes; +using Nini.Config; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + public interface IContentDatabase + { + /// + /// Similar to the IRegionModule function. This is the function to be called before attempting to interface with the database. + /// Initialise should be called one for each region to be contained in the database. The directory should be the full path + /// to the repository and will only be defined once, regardless of how many times the method is called. + /// + void Initialise(Scene scene, String dir); + + /// + /// Should be called once after Initialise has been called. + /// + void PostInitialise(); + + /// + /// Returns the total number of revisions saved for a specific region. + /// + int NumOfRegionRev(LLUUID regionid); + + /// + /// Saves the Region terrain map and objects within the region as xml to the database. + /// + void SaveRegion(LLUUID regionid, string regionName, string logMessage); + + /// + /// Retrieves the xml that describes each individual object from the last revision or specific revision of the given region. + /// + System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid); + System.Collections.ArrayList GetRegionObjectXMLList(LLUUID regionid, int revision); + + string GetRegionObjectHeightMap(LLUUID regionid); + string GetRegionObjectHeightMap(LLUUID regionid, int revision); + + /// + /// Returns a list of the revision numbers and corresponding log messages for a given region. + /// + System.Collections.Generic.SortedDictionary ListOfRegionRevisions(LLUUID id); + + /// + /// Returns the most recent revision number of a region. + /// + int GetMostRecentRevision(LLUUID regionid); + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/MetaEntity.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/MetaEntity.cs new file mode 100644 index 0000000000..f0763d36b6 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/MetaEntity.cs @@ -0,0 +1,219 @@ +// MetaEntity.cs +// User: bongiojp +// +// TODO: +// Create a physics manager to the meta object if there isn't one or the object knows of no scene but the user wants physics enabled. + + +using System; +using System.Collections.Generic; +using System.Drawing; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + public class MetaEntity + { + protected static readonly ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + protected SceneObjectGroup m_Entity = null; // The scene object group that represents this meta entity. + protected uint m_metaLocalid; + + // Settings for transparency of metaentity + public const float NONE = 0f; + public const float TRANSLUCENT = .5f; + public const float INVISIBLE = .95f; + + public Scene Scene + { + get { return m_Entity.Scene; } + } + + public SceneObjectPart RootPart + { + get { return m_Entity.RootPart; } + set { m_Entity.RootPart = value; } + } + + public LLUUID UUID + { + get { return m_Entity.UUID; } + set { m_Entity.UUID = value; } + } + + public uint LocalId + { + get { return m_Entity.LocalId; } + set { m_Entity.LocalId = value; } + } + + public SceneObjectGroup ObjectGroup + { + get { return m_Entity; } + } + + public Dictionary Children + { + get { return m_Entity.Children; } + set { m_Entity.Children = value; } + } + + public int PrimCount + { + get { return m_Entity.PrimCount; } + } + + public MetaEntity() + { + } + + /// + /// Makes a new meta entity by copying the given scene object group. + /// The physics boolean is just a stub right now. + /// + public MetaEntity(SceneObjectGroup orig, bool physics) + { + m_Entity = orig.Copy(orig.RootPart.OwnerID, orig.RootPart.GroupID, false); + Initialize(physics); + } + + /// + /// Takes an XML description of a scene object group and converts it to a meta entity. + /// + public MetaEntity(string objectXML, Scene scene, bool physics) + { + m_Entity = new SceneObjectGroup(objectXML); + m_Entity.SetScene(scene); + Initialize(physics); + } + + // The metaentity objectgroup must have unique localids as well as unique uuids. + // localids are used by the client to refer to parts. + // uuids are sent to the client and back to the server to identify parts on the server side. + /// + /// Changes localids and uuids of m_Entity. + /// + protected void Initialize(bool physics) + { + //make new uuids + Dictionary parts = new Dictionary(); + foreach(SceneObjectPart part in m_Entity.Children.Values) + { + part.ResetIDs(part.LinkNum); + parts.Add(part.UUID, part); + } + + // make new localids + foreach (SceneObjectPart part in m_Entity.Children.Values) + part.LocalId = m_Entity.Scene.PrimIDAllocate(); + + //finalize + m_Entity.UpdateParentIDs(); + m_Entity.RootPart.PhysActor = null; + m_Entity.Children = parts; + + } + + public void SendFullUpdate(IClientAPI client) + { + // Not sure what clientFlags should be but 0 seems to work + SendFullUpdate(client, 0); + } + public void SendFullUpdateToAll() + { + uint clientFlags = 0; + m_Entity.Scene.ClientManager.ForEachClient(delegate(IClientAPI controller) + { m_Entity.SendFullUpdateToClient(controller, clientFlags); } + ); + } + + public void SendFullUpdate(IClientAPI client, uint clientFlags) + { + m_Entity.SendFullUpdateToClient(client, clientFlags); + } + + /// + /// Hides the metaentity from a single client. + /// + public virtual void Hide(IClientAPI client) + { + //This deletes the group without removing from any databases. + //This is important because we are not IN any database. + //m_Entity.FakeDeleteGroup(); + foreach( SceneObjectPart part in m_Entity.Children.Values) + client.SendKillObject(m_Entity.RegionHandle, part.LocalId); + } + + /// + /// Sends a kill object message to all clients, effectively "hiding" the metaentity even though it's still on the server. + /// + public virtual void HideFromAll() + { + foreach( SceneObjectPart part in m_Entity.Children.Values) + m_Entity.Scene.ClientManager.ForEachClient(delegate(IClientAPI controller) + { controller.SendKillObject(m_Entity.RegionHandle, part.LocalId); } + ); + } + + /// + /// Makes a single SceneObjectPart see through. + /// + /// + /// A + /// The part to make see through + /// + /// + /// A + /// The degree of transparency to imbue the part with, 0f being solid and .95f being invisible. + /// + public static void SetPartTransparency(SceneObjectPart part, float transparencyAmount) + { + LLObject.TextureEntry tex = null; + LLColor texcolor; + try + { + tex = part.Shape.Textures; + texcolor = new LLColor(); + } + catch(Exception) + { + //m_log.ErrorFormat("[Content Management]: Exception thrown while accessing textures of scene object: " + e); + return; + } + + for (uint i = 0; i < tex.FaceTextures.Length; i++) + { + try { + if (tex.FaceTextures[i] != null) + { + texcolor = tex.FaceTextures[i].RGBA; + texcolor.A = transparencyAmount; + tex.FaceTextures[i].RGBA = texcolor; + } + } + catch (Exception) + { + //m_log.ErrorFormat("[Content Management]: Exception thrown while accessing different face textures of object: " + e); + continue; + } + } + try { + texcolor = tex.DefaultTexture.RGBA; + texcolor.A = transparencyAmount; + tex.DefaultTexture.RGBA = texcolor; + part.Shape.TextureEntry = tex.ToBytes(); + } + catch (Exception) + { + //m_log.Info("[Content Management]: Exception thrown while accessing default face texture of object: " + e); + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/PointMetaEntity.cs b/OpenSim/Region/Environment/Modules/ContentManagementSystem/PointMetaEntity.cs new file mode 100644 index 0000000000..55365ac4c4 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/PointMetaEntity.cs @@ -0,0 +1,81 @@ +// PointMetaEntity.cs created with MonoDevelop +// User: bongiojp at 3:03 PM 8/6/2008 +// +// To change standard headers go to Edit->Preferences->Coding->Standard Headers +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using libsecondlife; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Environment.Interfaces; +using OpenSim.Region.Environment.Scenes; +using log4net; +using OpenSim.Region.Physics.Manager; +using Axiom.Math; + +namespace OpenSim.Region.Environment.Modules.ContentManagement +{ + + + public class PointMetaEntity : MetaEntity + { + + public PointMetaEntity(Scene scene, uint LocalId, LLVector3 groupPos, float transparency) : base() + { + CreatePointEntity(scene, LLUUID.Random(), LocalId, groupPos); + SetPartTransparency(m_Entity.RootPart, transparency); + } + + public PointMetaEntity(Scene scene, LLUUID uuid, uint LocalId, LLVector3 groupPos, float transparency) : base() + { + CreatePointEntity(scene, uuid, LocalId, groupPos); + SetPartTransparency(m_Entity.RootPart, transparency); + } + + private void CreatePointEntity(Scene scene, LLUUID uuid, uint LocalId, LLVector3 groupPos) + { + SceneObjectGroup x = new SceneObjectGroup(); + SceneObjectPart y = new SceneObjectPart(); + + //Initialize part + y.Name = "Very Small Point"; + y.RegionHandle = scene.RegionInfo.RegionHandle; + y.CreationDate = (Int32) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; + y.OwnerID = LLUUID.Zero; + y.CreatorID = LLUUID.Zero; + y.LastOwnerID = LLUUID.Zero; + y.UUID = uuid; + + y.LocalId = LocalId; + + y.Shape = PrimitiveBaseShape.CreateBox(); + y.Scale = new LLVector3(0.01f,0.01f,0.01f); + y.LastOwnerID = LLUUID.Zero; + y.GroupPosition = groupPos; + y.OffsetPosition = new LLVector3(0, 0, 0); + y.RotationOffset = new LLQuaternion(0,0,0,0); + y.Velocity = new LLVector3(0, 0, 0); + y.RotationalVelocity = new LLVector3(0, 0, 0); + y.AngularVelocity = new LLVector3(0, 0, 0); + y.Acceleration = new LLVector3(0, 0, 0); + + y.Flags = 0; + y.TrimPermissions(); + + //Initialize group and add part as root part + x.SetScene(scene); + y.SetParent(x); + y.ParentID = 0; + y.LinkNum = 0; + x.Children.Add(y.UUID, y); + x.RootPart = y; + x.RegionHandle = scene.RegionInfo.RegionHandle; + x.SetScene(scene); + + m_Entity = x; + } + } +} diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/README b/OpenSim/Region/Environment/Modules/ContentManagementSystem/README new file mode 100644 index 0000000000..1a69fef231 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/ContentManagementSystem/README @@ -0,0 +1,52 @@ +This module is meant to be built alone and not added to the Opensim code base. References are made to required dlls through a +reference file, ContentManagement.mdp. Originally, for development, this project was contained in the Opensim/Region/Modules/ +directory. + +To compile: nant +To use: Copy ContentManagement.dll into the bin directory of your Opensim build. You should find many other dlls in the same directory. + + +-------------------------------------------------------------------------------------------------------------------- +To build the libgit.so file: + +#Download GIT git repository +$ git clone git://git2.kernel.org/pub/OpenSim/Region/Environment/Modules/ContentManagementSystem/scm/git/git.git +$ cd git + +#Compile GIT +#Note that we are adding two extra flags to pass to gcc while compiling (-c and -fPIC) +$ autoconf +$ ./configure +$ CFLAGS="-g -O2 -Wall -c -fPIC" make + +#Copy necessary object files (and some not so necessary) to their own directory for shared object file creation +$ mkdir ../libgit-objects +$ cp builtin*.o ../libgit-objects +$ cp xdiff/*.o ../libgit-objects +$ cp libgit.a ../libgit-objects + +#Remove the main symbol from any object files (like git.o) +$ cd ../libgit-objects +$ strip -N main *.o + +#Uncompress the plumbing objects from archive created by git +$ ar x libgit.a + +#Create shared object file from all objects (including the zlib library) +$ ld -shared -soname libgit.so.1 -o libgit.so.1.5.6.3 -lc -lz *.o + + +#You can also just copy the following commands into a file and run as a script inside the git directory + +make clean +autoconf +./configure +CFLAGS="-g -O2 -Wall -c -fPIC" make +mkdir libgit-objects +cp builtin*.o libgit-objects +cp xdiff/*.o libgit-objects +cp libgit.a libgit-objects +cd libgit-objects +strip -N main *.o +ar x libgit.a +ld -shared -soname libgit.so.1 -o libgit.so.1.5.6.3 -lc -lz *.o \ No newline at end of file diff --git a/OpenSim/Region/Environment/Modules/ContentManagementSystem/Requirements_documentation.odt b/OpenSim/Region/Environment/Modules/ContentManagementSystem/Requirements_documentation.odt new file mode 100644 index 0000000000000000000000000000000000000000..3fe7382f30ca8df106c3731789b07340a7f371fd GIT binary patch literal 38196 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 0) + return new LLVector3((float)NormalizeAngle(Math.Atan2(2.0 * (r.X * r.W - r.Y * r.Z), (-t.X - t.Y + t.Z + t.W))), + (float)NormalizeAngle(Math.Atan2(n, Math.Sqrt(p))), + (float)NormalizeAngle(Math.Atan2(2.0 * (r.Z * r.W - r.X * r.Y), (t.X - t.Y - t.Z + t.W)))); + else if (n > 0) + return new LLVector3(0.0f, (float)(Math.PI / 2), (float)NormalizeAngle(Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Z))); + else + return new LLVector3(0.0f, (float)(-Math.PI / 2), (float)NormalizeAngle(Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Z))); + } + + // Taken from Region/ScriptEngine/Common/LSL_BuiltIn_Commands.cs + private static double NormalizeAngle(double angle) + { + angle = angle % (Math.PI * 2); + if (angle < 0) angle = angle + Math.PI * 2; + return angle; + } + + ///

+ /// Compares the attributes (Vectors, Quaternions, Strings, etc.) between two scene object parts + /// and returns a Diff bitmask which details what the differences are. + /// + public static Diff FindDifferences(SceneObjectPart first, SceneObjectPart second) + { + Stopwatch x = new Stopwatch(); + x.Start(); + + Diff result = 0; + + // VECTOR COMPARISONS + if(! AreVectorsEquivalent(first.Acceleration, second.Acceleration)) + result |= Diff.ACCELERATION; + if(! AreVectorsEquivalent(first.AbsolutePosition, second.AbsolutePosition)) + result |= Diff.POSITION; + if(! AreVectorsEquivalent(first.AngularVelocity, second.AngularVelocity)) + result |= Diff.ANGULARVELOCITY; + if(! AreVectorsEquivalent(first.OffsetPosition, second.OffsetPosition)) + result |= Diff.OFFSETPOSITION; + if(! AreVectorsEquivalent(first.RotationalVelocity, second.RotationalVelocity)) + result |= Diff.ROTATIONALVELOCITY; + if(! AreVectorsEquivalent(first.Scale, second.Scale)) + result |= Diff.SCALE; + if(! AreVectorsEquivalent(first.Velocity, second.Velocity)) + result |= Diff.VELOCITY; + + + // QUATERNION COMPARISONS + if(! AreQuaternionsEquivalent(first.RotationOffset, second.RotationOffset)) + result |= Diff.ROTATIONOFFSET; + + + // MISC COMPARISONS (LLUUID, Byte) + if(first.ClickAction != second.ClickAction) + result |= Diff.CLICKACTION; + if(first.ObjectOwner != second.ObjectOwner) + result |= Diff.OBJECTOWNER; + + + // STRING COMPARISONS + if(first.Description != second.Description) + result |= Diff.DESCRIPTION; + if(first.Material != second.Material) + result |= Diff.MATERIAL; + if(first.Name != second.Name) + result |= Diff.NAME; + if(first.SitName != second.SitName) + result |= Diff.SITNAME; + if(first.Text != second.Text) + result |= Diff.TEXT; + if(first.TouchName != second.TouchName) + result |= Diff.TOUCHNAME; + + x.Stop(); + TimeToDiff += x.ElapsedMilliseconds; + //m_log.Info("[DIFFERENCES] Time spent diffing objects so far" + TimeToDiff); + + return result; + } + } +} diff --git a/bin/opensim-ode.sh b/bin/opensim-ode.sh index 86353174c6..b901425fc1 100755 --- a/bin/opensim-ode.sh +++ b/bin/opensim-ode.sh @@ -1,5 +1,4 @@ #!/bin/sh echo "Starting OpenSimulator with ODE. If you get an error saying limit: Operation not permitted. Then you will need to chmod 0600 /etc/limits" ulimit -s 262144 -sleep 5 mono OpenSim.exe -physics=OpenDynamicsEngine