diff --git a/OpenMetaverse/Types/CircularQueue.cs b/OpenMetaverse/Types/CircularQueue.cs new file mode 100644 index 00000000..fb6bcc01 --- /dev/null +++ b/OpenMetaverse/Types/CircularQueue.cs @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009, openmetaverse.org + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the openmetaverse.org nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace OpenMetaverse +{ + public class CircularQueue + { + public readonly T[] Items; + + int first; + int next; + int capacity; + object syncRoot; + + public int First { get { return first; } } + public int Next { get { return next; } } + + public CircularQueue(int capacity) + { + this.capacity = capacity; + Items = new T[capacity]; + syncRoot = new object(); + } + + /// + /// Copy constructor + /// + /// Circular queue to copy + public CircularQueue(CircularQueue queue) + { + capacity = queue.capacity; + Items = new T[capacity]; + syncRoot = new object(); + + for (int i = 0; i < capacity; i++) + Items[i] = queue.Items[i]; + + first = queue.first; + next = queue.next; + } + + public void Clear() + { + lock (syncRoot) + { + // Explicitly remove references to help garbage collection + for (int i = 0; i < capacity; i++) + Items[i] = default(T); + + first = next; + } + } + + public void Enqueue(T value) + { + lock (syncRoot) + { + Items[next] = value; + next = (next + 1) % capacity; + if (next == first) first = (first + 1) % capacity; + } + } + + public T Dequeue() + { + lock (syncRoot) + { + T value = Items[first]; + Items[first] = default(T); + + if (first != next) + first = (first + 1) % capacity; + + return value; + } + } + + public T DequeueLast() + { + lock (syncRoot) + { + // If the next element is right behind the first element (queue is full), + // back up the first element by one + int firstTest = first - 1; + if (firstTest < 0) firstTest = capacity - 1; + + if (firstTest == next) + { + --next; + if (next < 0) next = capacity - 1; + + --first; + if (first < 0) first = capacity - 1; + } + else if (first != next) + { + --next; + if (next < 0) next = capacity - 1; + } + + T value = Items[next]; + Items[next] = default(T); + + return value; + } + } + } +} diff --git a/Programs/Simian/Extensions/AvatarManager.cs b/Programs/Simian/Extensions/AvatarManager.cs index 232860c7..29a4e14a 100644 --- a/Programs/Simian/Extensions/AvatarManager.cs +++ b/Programs/Simian/Extensions/AvatarManager.cs @@ -113,17 +113,18 @@ namespace Simian.Extensions void ViewerEffectHandler(Packet packet, Agent agent) { - ViewerEffectPacket effect = (ViewerEffectPacket)packet; + ViewerEffectPacket incomingEffects = (ViewerEffectPacket)packet; - ViewerEffect[] effects = new ViewerEffect[effect.Effect.Length]; + ViewerEffect[] outgoingEffects = new ViewerEffect[incomingEffects.Effect.Length]; - for (int i = 0; i < effects.Length; i++) + for (int i = 0; i < outgoingEffects.Length; i++) { - ViewerEffectPacket.EffectBlock block = effect.Effect[i]; - effects[i] = new ViewerEffect(block.ID, (EffectType)block.Type, block.AgentID, new Color4(block.Color, 0, true), block.Duration); + ViewerEffectPacket.EffectBlock block = incomingEffects.Effect[i]; + outgoingEffects[i] = new ViewerEffect(block.ID, (EffectType)block.Type, block.AgentID, + new Color4(block.Color, 0, true), block.Duration, block.TypeData); } - server.Scene.TriggerEffects(this, effects); + server.Scene.TriggerEffects(this, outgoingEffects); } void AvatarPropertiesRequestHandler(Packet packet, Agent agent) diff --git a/Programs/Simian/Extensions/ObjectManager.cs b/Programs/Simian/Extensions/ObjectManager.cs index 4b274be8..0645ca1b 100644 --- a/Programs/Simian/Extensions/ObjectManager.cs +++ b/Programs/Simian/Extensions/ObjectManager.cs @@ -12,7 +12,6 @@ namespace Simian.Extensions { Simian server; - public ObjectManager() { } @@ -31,6 +30,8 @@ namespace Simian.Extensions server.UDP.RegisterPacketCallback(PacketType.ObjectFlagUpdate, new PacketCallback(ObjectFlagUpdateHandler)); server.UDP.RegisterPacketCallback(PacketType.ObjectExtraParams, new PacketCallback(ObjectExtraParamsHandler)); server.UDP.RegisterPacketCallback(PacketType.ObjectImage, new PacketCallback(ObjectImageHandler)); + server.UDP.RegisterPacketCallback(PacketType.Undo, new PacketCallback(UndoHandler)); + server.UDP.RegisterPacketCallback(PacketType.Redo, new PacketCallback(RedoHandler)); server.UDP.RegisterPacketCallback(PacketType.DeRezObject, new PacketCallback(DeRezObjectHandler)); server.UDP.RegisterPacketCallback(PacketType.MultipleObjectUpdate, new PacketCallback(MultipleObjectUpdateHandler)); server.UDP.RegisterPacketCallback(PacketType.RequestObjectPropertiesFamily, new PacketCallback(RequestObjectPropertiesFamilyHandler)); @@ -465,7 +466,7 @@ namespace Simian.Extensions data.ProfileEnd = Primitive.UnpackEndCut(block.ProfileEnd); data.ProfileHollow = Primitive.UnpackProfileHollow(block.ProfileHollow); - server.Scene.ObjectModify(this, obj.Prim.LocalID, data); + server.Scene.ObjectModify(this, obj, data); } else { @@ -544,6 +545,30 @@ namespace Simian.Extensions } } + void UndoHandler(Packet packet, Agent agent) + { + UndoPacket undo = (UndoPacket)packet; + + for (int i = 0; i < undo.ObjectData.Length; i++) + { + SimulationObject obj; + if (server.Scene.TryGetObject(undo.ObjectData[i].ObjectID, out obj)) + server.Scene.ObjectUndo(this, obj); + } + } + + void RedoHandler(Packet packet, Agent agent) + { + RedoPacket redo = (RedoPacket)packet; + + for (int i = 0; i < redo.ObjectData.Length; i++) + { + SimulationObject obj; + if (server.Scene.TryGetObject(redo.ObjectData[i].ObjectID, out obj)) + server.Scene.ObjectRedo(this, obj); + } + } + void DeRezObjectHandler(Packet packet, Agent agent) { DeRezObjectPacket derez = (DeRezObjectPacket)packet; @@ -674,7 +699,7 @@ namespace Simian.Extensions } else { - server.Scene.ObjectTransform(this, obj.Prim.LocalID, position, rotation, + server.Scene.ObjectTransform(this, obj, position, rotation, obj.Prim.Velocity, obj.Prim.Acceleration, obj.Prim.AngularVelocity); } } diff --git a/Programs/Simian/Extensions/Periscope.cs b/Programs/Simian/Extensions/Periscope.cs index 5aef028b..2cad9784 100644 --- a/Programs/Simian/Extensions/Periscope.cs +++ b/Programs/Simian/Extensions/Periscope.cs @@ -98,8 +98,12 @@ namespace Simian.Extensions void Objects_OnObjectUpdated(Simulator simulator, ObjectUpdate update, ulong regionHandle, ushort timeDilation) { - server.Scene.ObjectTransform(this, update.LocalID, update.Position, update.Rotation, update.Velocity, - update.Acceleration, update.AngularVelocity); + SimulationObject obj; + if (server.Scene.TryGetObject(update.LocalID, out obj)) + { + server.Scene.ObjectTransform(this, obj, update.Position, update.Rotation, update.Velocity, + update.Acceleration, update.AngularVelocity); + } if (update.LocalID == client.Self.LocalID) { diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 881aae52..b84bd367 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -31,8 +31,11 @@ namespace Simian.Extensions public class SceneManager : IExtension, ISceneProvider { Simian server; + // Contains all scene objects, including prims and avatars DoubleDictionary sceneObjects = new DoubleDictionary(); + // A duplicate of the avatar information stored in sceneObjects, improves operations such as iterating over all agents DoubleDictionary sceneAgents = new DoubleDictionary(); + // Event queues for each avatar in the scene Dictionary eventQueues = new Dictionary(); int currentLocalID = 1; ulong regionHandle; @@ -46,6 +49,8 @@ namespace Simian.Extensions public event ObjectModifyCallback OnObjectModify; public event ObjectModifyTexturesCallback OnObjectModifyTextures; public event ObjectAnimateCallback OnObjectAnimate; + public event ObjectUndoCallback OnObjectUndo; + public event ObjectRedoCallback OnObjectRedo; public event AgentAddCallback OnAgentAdd; public event AgentRemoveCallback OnAgentRemove; public event AgentAppearanceCallback OnAgentAppearance; @@ -112,9 +117,21 @@ namespace Simian.Extensions public bool ObjectAdd(object sender, SimulationObject obj, PrimFlags creatorFlags) { + if (OnObjectAdd != null) + { + OnObjectAdd(sender, obj, creatorFlags); + } + // Check if the object already exists in the scene - if (sceneObjects.ContainsKey(obj.Prim.ID)) - sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID); + SimulationObject oldObj; + if (sceneObjects.TryGetValue(obj.Prim.ID, out oldObj)) + { + sceneObjects.Remove(oldObj.Prim.LocalID, oldObj.Prim.ID); + + // Copy the undo/redo steps to the new object + obj.UndoSteps = new CircularQueue(oldObj.UndoSteps); + obj.RedoSteps = new CircularQueue(oldObj.RedoSteps); + } if (obj.Prim.LocalID == 0) { @@ -122,9 +139,6 @@ namespace Simian.Extensions obj.Prim.LocalID = (uint)Interlocked.Increment(ref currentLocalID); } - if (OnObjectAdd != null) - OnObjectAdd(sender, obj, creatorFlags); - // Add the object to the scene dictionary sceneObjects.Add(obj.Prim.LocalID, obj.Prim.ID, obj); @@ -155,6 +169,9 @@ namespace Simian.Extensions SimulationObject obj; Agent agent; + if (sceneAgents.TryGetValue(localID, out agent)) + AgentRemove(sender, agent); + if (sceneObjects.TryGetValue(localID, out obj)) { if (OnObjectRemove != null) @@ -170,15 +187,8 @@ namespace Simian.Extensions server.UDP.BroadcastPacket(kill, PacketCategory.State); return true; } - else if (sceneAgents.TryGetValue(localID, out agent)) - { - AgentRemove(sender, agent); - return true; - } - else - { - return false; - } + + return false; } public bool ObjectRemove(object sender, UUID id) @@ -186,6 +196,9 @@ namespace Simian.Extensions SimulationObject obj; Agent agent; + if (sceneAgents.TryGetValue(id, out agent)) + AgentRemove(sender, agent); + if (sceneObjects.TryGetValue(id, out obj)) { if (OnObjectRemove != null) @@ -201,15 +214,8 @@ namespace Simian.Extensions server.UDP.BroadcastPacket(kill, PacketCategory.State); return true; } - else if (sceneAgents.TryGetValue(id, out agent)) - { - AgentRemove(sender, agent); - return true; - } - else - { - return false; - } + + return false; } void AgentRemove(object sender, Agent agent) @@ -242,48 +248,28 @@ namespace Simian.Extensions server.UDP.BroadcastPacket(offline, PacketCategory.State); } - public void ObjectTransform(object sender, uint localID, Vector3 position, Quaternion rotation, + public void ObjectTransform(object sender, SimulationObject obj, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity) { - SimulationObject obj; - Agent agent; - - if (sceneObjects.TryGetValue(localID, out obj)) + if (OnObjectTransform != null) { - if (OnObjectTransform != null) - { - OnObjectTransform(sender, obj, position, rotation, velocity, - acceleration, angularVelocity); - } - - // Update the object - obj.Prim.Position = position; - obj.Prim.Rotation = rotation; - obj.Prim.Velocity = velocity; - obj.Prim.Acceleration = acceleration; - obj.Prim.AngularVelocity = angularVelocity; - - // Inform clients - BroadcastObjectUpdate(obj.Prim); + OnObjectTransform(sender, obj, position, rotation, velocity, + acceleration, angularVelocity); } - else if (sceneAgents.TryGetValue(localID, out agent)) - { - if (OnObjectTransform != null) - { - OnObjectTransform(sender, obj, position, rotation, velocity, - acceleration, angularVelocity); - } - // Update the avatar - agent.Avatar.Position = position; - agent.Avatar.Rotation = rotation; - agent.Avatar.Velocity = velocity; - agent.Avatar.Acceleration = acceleration; - agent.Avatar.AngularVelocity = angularVelocity; + // Add an undo step for prims (not avatars) + if (!(obj.Prim is Avatar)) + obj.CreateUndoStep(); - // Inform clients - BroadcastObjectUpdate(agent.Avatar); - } + // Update the object + obj.Prim.Position = position; + obj.Prim.Rotation = rotation; + obj.Prim.Velocity = velocity; + obj.Prim.Acceleration = acceleration; + obj.Prim.AngularVelocity = angularVelocity; + + // Inform clients + BroadcastObjectUpdate(obj.Prim); } public void ObjectFlags(object sender, SimulationObject obj, PrimFlags flags) @@ -293,6 +279,10 @@ namespace Simian.Extensions OnObjectFlags(sender, obj, flags); } + // Add an undo step for prims (not avatars) + if (!(obj.Prim is Avatar)) + obj.CreateUndoStep(); + // Update the object obj.Prim.Flags = flags; @@ -300,22 +290,22 @@ namespace Simian.Extensions BroadcastObjectUpdate(obj.Prim); } - public void ObjectModify(object sender, uint localID, Primitive.ConstructionData data) + public void ObjectModify(object sender, SimulationObject obj, Primitive.ConstructionData data) { - SimulationObject obj; - if (sceneObjects.TryGetValue(localID, out obj)) + if (OnObjectModify != null) { - if (OnObjectModify != null) - { - OnObjectModify(sender, obj, data); - } - - // Update the object - obj.Prim.PrimData = data; - - // Inform clients - BroadcastObjectUpdate(obj.Prim); + OnObjectModify(sender, obj, data); } + + // Add an undo step for prims (not avatars) + if (!(obj.Prim is Avatar)) + obj.CreateUndoStep(); + + // Update the object + obj.Prim.PrimData = data; + + // Inform clients + BroadcastObjectUpdate(obj.Prim); } public void ObjectModifyTextures(object sender, SimulationObject obj, string mediaURL, Primitive.TextureEntry textureEntry) @@ -325,6 +315,10 @@ namespace Simian.Extensions OnObjectModifyTextures(sender, obj, mediaURL, textureEntry); } + // Add an undo step for prims (not avatars) + if (!(obj.Prim is Avatar)) + obj.CreateUndoStep(); + // Update the object obj.Prim.Textures = textureEntry; obj.Prim.MediaURL = mediaURL; @@ -357,6 +351,56 @@ namespace Simian.Extensions server.UDP.BroadcastPacket(sendAnim, PacketCategory.State); } + public void ObjectUndo(object sender, SimulationObject obj) + { + if (OnObjectUndo != null) + { + OnObjectUndo(sender, obj); + } + + Primitive prim = obj.UndoSteps.DequeueLast(); + if (prim != null) + { + Logger.Log(String.Format("Performing undo on object {0}", obj.Prim.ID), Helpers.LogLevel.Debug); + + obj.RedoSteps.Enqueue(prim); + obj.Prim = prim; + + // Inform clients + BroadcastObjectUpdate(obj.Prim); + } + else + { + Logger.Log(String.Format("Undo requested on object {0} with no remaining undo steps", obj.Prim.ID), + Helpers.LogLevel.Debug); + } + } + + public void ObjectRedo(object sender, SimulationObject obj) + { + if (OnObjectRedo != null) + { + OnObjectRedo(sender, obj); + } + + Primitive prim = obj.RedoSteps.DequeueLast(); + if (prim != null) + { + Logger.Log(String.Format("Performing redo on object {0}", obj.Prim.ID), Helpers.LogLevel.Debug); + + obj.UndoSteps.Enqueue(prim); + obj.Prim = prim; + + // Inform clients + BroadcastObjectUpdate(obj.Prim); + } + else + { + Logger.Log(String.Format("Redo requested on object {0} with no remaining redo steps", obj.Prim.ID), + Helpers.LogLevel.Debug); + } + } + public void TriggerSound(object sender, UUID objectID, UUID parentID, UUID ownerID, UUID soundID, Vector3 position, float gain) { if (OnTriggerSound != null) @@ -399,6 +443,9 @@ namespace Simian.Extensions block.Duration = currentEffect.Duration; block.ID = currentEffect.EffectID; block.Type = (byte)currentEffect.Type; + block.TypeData = currentEffect.TypeData; + + effect.Effect[i] = block; } server.UDP.BroadcastPacket(effect, PacketCategory.State); diff --git a/Programs/Simian/Extensions/UDPManager.cs b/Programs/Simian/Extensions/UDPManager.cs index dc25806e..a5763bb0 100644 --- a/Programs/Simian/Extensions/UDPManager.cs +++ b/Programs/Simian/Extensions/UDPManager.cs @@ -383,8 +383,8 @@ namespace Simian { if (outgoing.ResendCount < 3) { - Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed", - outgoing.Packet.Header.Sequence, outgoing.Packet.GetType(), now - outgoing.TickCount)); + //Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed", + // outgoing.Packet.Header.Sequence, outgoing.Packet.GetType(), now - outgoing.TickCount)); // The TickCount will be set to the current time when the packet // is actually sent out again @@ -394,7 +394,20 @@ namespace Simian //++Stats.ResentPackets; - SendPacket(client, outgoing); + try + { + SendPacket(client, outgoing); + } + catch (NullReferenceException) + { + // Programming error elsewhere can put bad packets in the outgoing queue. + // These will never successfully serialize, so just drop them + Logger.Log(String.Format("Dropping bad packet #{0} ({1}) from the outgoing queue", + outgoing.Packet.Header.Sequence, outgoing.Packet.GetType()), + Helpers.LogLevel.Warning); + + lock (client.NeedAcks) client.NeedAcks.Remove(outgoing.Packet.Header.Sequence); + } } else { diff --git a/Programs/Simian/Interfaces/ISceneProvider.cs b/Programs/Simian/Interfaces/ISceneProvider.cs index 4de9cafc..73589d3b 100644 --- a/Programs/Simian/Interfaces/ISceneProvider.cs +++ b/Programs/Simian/Interfaces/ISceneProvider.cs @@ -35,14 +35,16 @@ namespace Simian public UUID AgentID; public Color4 Color; public float Duration; + public byte[] TypeData; - public ViewerEffect(UUID effectID, EffectType type, UUID agentID, Color4 color, float duration) + public ViewerEffect(UUID effectID, EffectType type, UUID agentID, Color4 color, float duration, byte[] typeData) { EffectID = effectID; Type = type; AgentID = agentID; Color = color; Duration = duration; + TypeData = typeData; } } @@ -72,6 +74,8 @@ namespace Simian public delegate void ObjectModifyCallback(object sender, SimulationObject obj, Primitive.ConstructionData data); public delegate void ObjectModifyTexturesCallback(object sender, SimulationObject obj, string mediaURL, Primitive.TextureEntry textureEntry); public delegate void ObjectAnimateCallback(object sender, UUID senderID, UUID objectID, AnimationTrigger[] animations); + public delegate void ObjectUndoCallback(object sender, SimulationObject obj); + public delegate void ObjectRedoCallback(object sender, SimulationObject obj); public delegate void AgentAddCallback(object sender, Agent agent, PrimFlags creatorFlags); public delegate void AgentRemoveCallback(object sender, Agent agent); public delegate void AgentAppearanceCallback(object sender, Agent agent, Primitive.TextureEntry textures, byte[] visualParams); @@ -88,6 +92,8 @@ namespace Simian event ObjectModifyCallback OnObjectModify; event ObjectModifyTexturesCallback OnObjectModifyTextures; event ObjectAnimateCallback OnObjectAnimate; + event ObjectUndoCallback OnObjectUndo; + event ObjectRedoCallback OnObjectRedo; event AgentAddCallback OnAgentAdd; event AgentRemoveCallback OnAgentRemove; event AgentAppearanceCallback OnAgentAppearance; @@ -111,11 +117,13 @@ namespace Simian bool ObjectAdd(object sender, SimulationObject obj, PrimFlags creatorFlags); bool ObjectRemove(object sender, uint localID); bool ObjectRemove(object sender, UUID id); - void ObjectTransform(object sender, uint localID, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity); + void ObjectTransform(object sender, SimulationObject obj, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity); void ObjectFlags(object sender, SimulationObject obj, PrimFlags flags); - void ObjectModify(object sender, uint localID, Primitive.ConstructionData data); + void ObjectModify(object sender, SimulationObject obj, Primitive.ConstructionData data); void ObjectModifyTextures(object sender, SimulationObject obj, string mediaURL, Primitive.TextureEntry textureEntry); void ObjectAnimate(object sender, UUID senderID, UUID objectID, AnimationTrigger[] animations); + void ObjectUndo(object sender, SimulationObject obj); + void ObjectRedo(object sender, SimulationObject obj); void TriggerSound(object sender, UUID objectID, UUID parentID, UUID ownerID, UUID soundID, Vector3 position, float gain); void TriggerEffects(object sender, ViewerEffect[] effects); bool AgentAdd(object sender, Agent agent, PrimFlags creatorFlags); diff --git a/Programs/Simian/README.txt b/Programs/Simian/README.txt index ef291698..088998aa 100644 --- a/Programs/Simian/README.txt +++ b/Programs/Simian/README.txt @@ -2,17 +2,28 @@ Introduction ------------ Simian is a lightweight simulator built on the libOpenMetaverse framework. Its -primary uses are rapid prototyping of new designs, a lightweight benchmarking -suite, and unit testing of client applications. +primary uses are protocol translation and new protocol testing, rapid +prototyping of new designs, a lightweight benchmarking suite, and unit testing +of client applications. + +The design of Simian is centered around the one client-facing protocol it +supports. Where OpenSim defines a generic framework to support many backend +simulation systems and many frontend clients, Simian works with the concepts +that the Second Life viewer understands. Avatars, prims, and terrain become the +basic entities of the metaverse. Concepts of space are converted into 256x256 +meter squares of terrain. Chat and instant messaging are translated into the +mechanisms used in Second Life. This allows new protocols and behaviors to be +added without having to modify the Second Life viewer directly, or work with the +complex (many-to-many) problem of translating concepts between all virtual +worlds. Extensions ------------ Extensions can be written in one of three ways. -1) Add a class that inherits from ISimianExtension directly in the Simian - project. Typically this is done by adding a new .cs file in the - extensions folder. +1) Add a class that inherits from IExtension directly in the Simian project. + Typically this is done by adding a new .cs file in the extensions folder. 2) Create a new assembly containing one or more extensions. The assembly must follow the naming convention of Simian.*.dll. @@ -20,8 +31,15 @@ Extensions can be written in one of three ways. 3) Put a source code file alongside the running Simian.exe binary that will be compiled at runtime. The code must follow the naming convention Simian.*.cs. Look at Simian.ViewerEffectPrinter.cs.example for an example. Remove the - .example extension and drop the file alongside the Simian.exe binary to see - it in action. + .example extension and put the file alongside the Simian.exe binary to see it + working. -All extensions must inherit from ISimianExtension and have a constructor that -takes a Simian object as the only parameter. +*NOTE*: Extensions will only be loaded if they are listed in the Simian.ini file +in the [Extensions] section. You can comment out extensions, but if an extension +implements an interface and there is no other loaded extension that implements +that interface the Simian server will stop with an error message. + +All extensions must inherit from IExtension and have a Start method that +takes a Simian object as the only parameter, along with an empty Stop method. +See the http://code.google.com/p/extensionloader/ project for more details on +writing extensions. diff --git a/Programs/Simian/SimulationObject.cs b/Programs/Simian/SimulationObject.cs index bf873d81..78f64e6d 100644 --- a/Programs/Simian/SimulationObject.cs +++ b/Programs/Simian/SimulationObject.cs @@ -15,6 +15,10 @@ namespace Simian /// True when an avatar grabs this object. Stops movement and /// rotation public bool Frozen; + /// Holds the state of the object after each edit to enable undo + public CircularQueue UndoSteps = new CircularQueue(10); + /// Holds the state of the object after each undo to enable redo + public CircularQueue RedoSteps = new CircularQueue(10); protected Simian Server; protected SimpleMesh[] Meshes; @@ -35,6 +39,14 @@ namespace Simian Server = server; } + /// + /// Copy the current state of the object into the next undo step + /// + public void CreateUndoStep() + { + UndoSteps.Enqueue(new Primitive(Prim)); + } + public SimpleMesh GetMesh(DetailLevel lod) { int i = (int)lod; @@ -47,8 +59,7 @@ namespace Simian } else { - Primitive prim = (Primitive)Prim; - SimpleMesh mesh = Server.Mesher.GenerateSimpleMesh(prim, lod); + SimpleMesh mesh = Server.Mesher.GenerateSimpleMesh(Prim, lod); Meshes[i] = mesh; return mesh; } @@ -67,22 +78,7 @@ namespace Simian else { // Get the untransformed mesh - SimpleMesh mesh = GetMesh(lod); - - // Copy to our new mesh - SimpleMesh transformedMesh = new SimpleMesh(); - transformedMesh.Indices = new List(mesh.Indices); - transformedMesh.Path.Open = mesh.Path.Open; - transformedMesh.Path.Points = new List(mesh.Path.Points); - transformedMesh.Prim = mesh.Prim; - transformedMesh.Profile.Concave = mesh.Profile.Concave; - transformedMesh.Profile.Faces = new List(mesh.Profile.Faces); - transformedMesh.Profile.MaxX = mesh.Profile.MaxX; - transformedMesh.Profile.MinX = mesh.Profile.MinX; - transformedMesh.Profile.Open = mesh.Profile.Open; - transformedMesh.Profile.Positions = new List(mesh.Profile.Positions); - transformedMesh.Profile.TotalOutsidePoints = mesh.Profile.TotalOutsidePoints; - transformedMesh.Vertices = new List(mesh.Vertices); + SimpleMesh mesh = Server.Mesher.GenerateSimpleMesh(Prim, lod); // Construct a matrix to transform to world space Matrix4 transform = Matrix4.Identity; @@ -94,20 +90,20 @@ namespace Simian transform *= Matrix4.CreateTranslation(parent.Prim.Position); } - transform *= Matrix4.CreateScale(this.Prim.Scale); - transform *= Matrix4.CreateFromQuaternion(this.Prim.Rotation); - transform *= Matrix4.CreateTranslation(this.Prim.Position); + transform *= Matrix4.CreateScale(Prim.Scale); + transform *= Matrix4.CreateFromQuaternion(Prim.Rotation); + transform *= Matrix4.CreateTranslation(Prim.Position); // Transform the mesh - for (int j = 0; j < transformedMesh.Vertices.Count; j++) + for (int j = 0; j < mesh.Vertices.Count; j++) { - Vertex vertex = transformedMesh.Vertices[j]; + Vertex vertex = mesh.Vertices[j]; vertex.Position *= transform; - transformedMesh.Vertices[j] = vertex; + mesh.Vertices[j] = vertex; } - WorldTransformedMeshes[i] = transformedMesh; - return transformedMesh; + WorldTransformedMeshes[i] = mesh; + return mesh; } }