From 805fb7c18ddfffeb4299f1d2a1ca8ebba9286945 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Mon, 15 Sep 2008 23:17:21 +0000 Subject: [PATCH] Simian: * Decode layer boundaries when storing a texture asset * Started fleshing out correct texture downloading * Add avatars to the scene along with prims git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@2224 52acb1d6-8a22-11de-b505-999d5b087335 --- Programs/Simian/Extensions/AssetManager.cs | 44 +++-- Programs/Simian/Extensions/ImageDelivery.cs | 183 +++++++++++++++++-- Programs/Simian/Extensions/SceneManager.cs | 34 ++-- Programs/Simian/Interfaces/IAssetProvider.cs | 1 - Programs/Simian/Simian.cs | 1 + 5 files changed, 215 insertions(+), 48 deletions(-) diff --git a/Programs/Simian/Extensions/AssetManager.cs b/Programs/Simian/Extensions/AssetManager.cs index 4800be03..55b1f6de 100644 --- a/Programs/Simian/Extensions/AssetManager.cs +++ b/Programs/Simian/Extensions/AssetManager.cs @@ -10,7 +10,7 @@ namespace Simian.Extensions public class AssetManager : ISimianExtension, IAssetProvider { Simian Server; - public Dictionary AssetStore = new Dictionary(); + Dictionary AssetStore = new Dictionary(); Dictionary CurrentUploads = new Dictionary(); public AssetManager(Simian server) @@ -34,29 +34,33 @@ namespace Simian.Extensions public void StoreAsset(Asset asset) { - if (asset.Decode()) + if (asset is AssetTexture) { - lock (AssetStore) - AssetStore[asset.AssetID] = asset; - } - else - { - Logger.Log(String.Format("Failed to decode {0} asset {1}", asset.AssetType, asset.AssetID), - Helpers.LogLevel.Warning); - } - } + AssetTexture texture = (AssetTexture)asset; - public void StoreTexture(AssetTexture texture) - { - if (texture.DecodeLayerBoundaries()) - { - lock (AssetStore) - AssetStore[texture.AssetID] = texture; + if (texture.DecodeLayerBoundaries()) + { + lock (AssetStore) + AssetStore[asset.AssetID] = asset; + } + else + { + Logger.Log(String.Format("Failed to decoded layer boundaries on texture {0}", texture.AssetID), + Helpers.LogLevel.Warning); + } } else { - Logger.Log(String.Format("Failed to decoded layer boundaries on texture {0}", texture.AssetID), - Helpers.LogLevel.Warning); + if (asset.Decode()) + { + lock (AssetStore) + AssetStore[asset.AssetID] = asset; + } + else + { + Logger.Log(String.Format("Failed to decode {0} asset {1}", asset.AssetType, asset.AssetID), + Helpers.LogLevel.Warning); + } } } @@ -390,7 +394,7 @@ namespace Simian.Extensions for (int i = 0; i < textures.Length; i++) { UUID assetID = ParseUUIDFromFilename(textures[i]); - StoreTexture(new AssetTexture(assetID, File.ReadAllBytes(textures[i]))); + StoreAsset(new AssetTexture(assetID, File.ReadAllBytes(textures[i]))); } for (int i = 0; i < clothing.Length; i++) diff --git a/Programs/Simian/Extensions/ImageDelivery.cs b/Programs/Simian/Extensions/ImageDelivery.cs index 14a1f2eb..d2d1884f 100644 --- a/Programs/Simian/Extensions/ImageDelivery.cs +++ b/Programs/Simian/Extensions/ImageDelivery.cs @@ -1,17 +1,85 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Threading; using OpenMetaverse; using OpenMetaverse.Imaging; using OpenMetaverse.Packets; namespace Simian.Extensions { + public struct ImageDownload + { + public const int FIRST_IMAGE_PACKET_SIZE = 600; + public const int IMAGE_PACKET_SIZE = 1000; + + public AssetTexture Texture; + public int DiscardLevel; + public float Priority; + public int Packet; + + public ImageDownload(AssetTexture texture, int discardLevel, float priority) + { + Texture = texture; + DiscardLevel = discardLevel; + Priority = priority; + Packet = 0; + } + + public int GetRemainingBytes() + { + return GetEndPosition() - GetBytesSent(); + } + + public int GetPacketCount() + { + int length = GetRemainingBytes(); + return ((length - FIRST_IMAGE_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; + } + + public int GetBytesSent() + { + if (Packet < 1) + return 0; + else + return FIRST_IMAGE_PACKET_SIZE + ((Packet - 1) * IMAGE_PACKET_SIZE); + } + + public int GetEndPosition() + { + if (Texture == null || Texture.LayerInfo == null || Texture.AssetData == null) + throw new InvalidOperationException("Cannot get end position while texture information is null"); + + int layerCount = Texture.LayerInfo.Length; + int requestedLayer = layerCount + DiscardLevel; + + if (requestedLayer == layerCount) + { + // No discard, go to the end of the image data + return Texture.AssetData.Length - 1; + } + else if (requestedLayer >= 0 && requestedLayer < layerCount) + { + return Texture.LayerInfo[requestedLayer].End; + } + else + { + Logger.Log(String.Format( + "DiscardLevel {0} is out of range for texture {1}, which has {2} decoded layer boundaries", + DiscardLevel, Texture.AssetID, Texture.LayerInfo.Length), Helpers.LogLevel.Error); + + return Texture.AssetData.Length - 1; + } + } + } + public class ImageDelivery : ISimianExtension { Simian Server; AssetTexture defaultJP2; AssetTexture defaultBakedJP2; + Dictionary CurrentDownloads = new Dictionary(); + BlockingQueue CurrentDownloadQueue = new BlockingQueue(); public ImageDelivery(Simian server) { @@ -54,23 +122,114 @@ namespace Simian.Extensions RequestImagePacket.RequestImageBlock block = request.RequestImage[i]; bool bake = ((ImageType)block.Type == ImageType.Baked); - ImageDataPacket imageData = new ImageDataPacket(); - imageData.ImageID.ID = block.Image; - imageData.ImageID.Codec = 1; - imageData.ImageID.Packets = 1; - if (bake) + // Check if we have this image + Asset asset; + ImageDownload download; + if (Server.Assets.TryGetAsset(block.Image, out asset) && asset is AssetTexture) { - Logger.DebugLog(String.Format("Sending default bake texture for {0}", block.Image)); - imageData.ImageData.Data = defaultBakedJP2.AssetData; + if (block.DiscardLevel == -1 && block.DownloadPriority == 0.0f) + { + // FIXME: Cancel download + Logger.Log("Canceling image download " + block.Image, Helpers.LogLevel.Info); + } + else + { + lock (CurrentDownloads) + { + if (CurrentDownloads.TryGetValue(block.Image, out download)) + { + // Modifying existing download + if (block.DiscardLevel < download.DiscardLevel) + { + // FIXME: Do we need to do something here? + Logger.Log(String.Format("Image download {0} is changing from DiscardLevel {1} to {2}", + block.Image, download.DiscardLevel, block.DiscardLevel), Helpers.LogLevel.Info); + download.DiscardLevel = block.DiscardLevel; + } + + download.Priority = block.DownloadPriority; + + if (block.Packet > 0 && block.Packet < download.Packet) + { + Logger.Log(String.Format("Rolling back image download {0} from packet {1} to {2}", + block.Image, download.Packet, block.Packet), Helpers.LogLevel.Warning); + download.Packet = (int)block.Packet; + } + + // Re-insert this download into the dictionary + CurrentDownloads[block.Image] = download; + } + else + { + // New download + download = new ImageDownload((AssetTexture)asset, block.DiscardLevel, block.DownloadPriority); + download.Packet = (int)block.Packet; + + // Send initial data + ImageDataPacket data = new ImageDataPacket(); + data.ImageID.Codec = (byte)ImageCodec.J2C; + data.ImageID.ID = download.Texture.AssetID; + data.ImageID.Packets = (ushort)download.GetPacketCount(); + data.ImageID.Size = (uint)download.GetRemainingBytes(); + // The Linden Lab servers actually prepend two bytes in this data with the + // size of the following data. It is redundant and ignored by every client, + // so we skip it + data.ImageData = new ImageDataPacket.ImageDataBlock(); + + if (data.ImageID.Packets == 1) + { + // Single packet image + data.ImageData.Data = new byte[download.Texture.AssetData.Length]; + Buffer.BlockCopy(download.Texture.AssetData, download.GetBytesSent(), + data.ImageData.Data, 0, (int)data.ImageID.Size); + } + else + { + // Multi-packet image + data.ImageData.Data = new byte[ImageDownload.FIRST_IMAGE_PACKET_SIZE]; + Buffer.BlockCopy(download.Texture.AssetData, download.GetBytesSent(), + data.ImageData.Data, 0, ImageDownload.FIRST_IMAGE_PACKET_SIZE); + + // Insert this download into the dictionary + CurrentDownloads[block.Image] = download; + + // Send all of the remaining packets + ThreadPool.QueueUserWorkItem( + delegate(object obj) + { + ImagePacketPacket transfer = new ImagePacketPacket(); + transfer.ImageID.ID = block.Image; + //transfer.ImageID.Packet = + } + ); + } + + Server.UDP.SendPacket(agent.AgentID, data, PacketCategory.Texture); + } + } + } } else { - Logger.DebugLog(String.Format("Sending default texture for {0}", block.Image)); - imageData.ImageData.Data = defaultJP2.AssetData; - } - imageData.ImageID.Size = (uint)imageData.ImageData.Data.Length; + // TODO: Technically we should return ImageNotInDatabasePacket, but for now return a default texture + ImageDataPacket imageData = new ImageDataPacket(); + imageData.ImageID.ID = block.Image; + imageData.ImageID.Codec = 1; + imageData.ImageID.Packets = 1; + if (bake) + { + Logger.DebugLog(String.Format("Sending default bake texture for {0}", block.Image)); + imageData.ImageData.Data = defaultBakedJP2.AssetData; + } + else + { + Logger.DebugLog(String.Format("Sending default texture for {0}", block.Image)); + imageData.ImageData.Data = defaultJP2.AssetData; + } + imageData.ImageID.Size = (uint)imageData.ImageData.Data.Length; - Server.UDP.SendPacket(agent.AgentID, imageData, PacketCategory.Texture); + Server.UDP.SendPacket(agent.AgentID, imageData, PacketCategory.Texture); + } } } } diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs index 68e04ee8..eb77a64c 100644 --- a/Programs/Simian/Extensions/SceneManager.cs +++ b/Programs/Simian/Extensions/SceneManager.cs @@ -203,6 +203,7 @@ namespace Simian.Extensions // Give testers a provisionary balance of 1000L agent.Balance = 1000; + // Send a response back to the client AgentMovementCompletePacket complete = new AgentMovementCompletePacket(); complete.AgentData.AgentID = agent.AgentID; complete.AgentData.SessionID = agent.SessionID; @@ -214,6 +215,9 @@ namespace Simian.Extensions server.UDP.SendPacket(agent.AgentID, complete, PacketCategory.Transaction); + // Add this avatar as an object in the scene + ObjectAdd(this, agent, new SimulationObject(agent.Avatar, server), PrimFlags.None); + // Send updates and appearances for every avatar to this new avatar SynchronizeStateTo(agent); @@ -228,21 +232,7 @@ namespace Simian.Extensions // HACK: The reduction provider will deprecate this at some point void SynchronizeStateTo(Agent agent) { - lock (server.Agents) - { - foreach (Agent otherAgent in server.Agents.Values) - { - // Send ObjectUpdate packets for this avatar - ObjectUpdatePacket update = SimulationObject.BuildFullUpdate(otherAgent.Avatar, - server.RegionHandle, otherAgent.State, otherAgent.Flags); - server.UDP.SendPacket(agent.AgentID, update, PacketCategory.State); - - // Send appearances for this avatar - AvatarAppearancePacket appearance = AvatarManager.BuildAppearancePacket(otherAgent); - server.UDP.SendPacket(agent.AgentID, appearance, PacketCategory.State); - } - } - + // Send object updates for objects and avatars sceneObjects.ForEach(delegate(SimulationObject obj) { ObjectUpdatePacket update = SimulationObject.BuildFullUpdate(obj.Prim, @@ -250,6 +240,20 @@ namespace Simian.Extensions server.UDP.SendPacket(agent.AgentID, update, PacketCategory.State); }); + // Send appearances for all avatars + lock (server.Agents) + { + foreach (Agent otherAgent in server.Agents.Values) + { + if (otherAgent != agent) + { + // Send appearances for this avatar + AvatarAppearancePacket appearance = AvatarManager.BuildAppearancePacket(otherAgent); + server.UDP.SendPacket(agent.AgentID, appearance, PacketCategory.State); + } + } + } + // Send terrain data SendLayerData(agent); } diff --git a/Programs/Simian/Interfaces/IAssetProvider.cs b/Programs/Simian/Interfaces/IAssetProvider.cs index 499bc8d8..761de6c8 100644 --- a/Programs/Simian/Interfaces/IAssetProvider.cs +++ b/Programs/Simian/Interfaces/IAssetProvider.cs @@ -6,7 +6,6 @@ namespace Simian public interface IAssetProvider { void StoreAsset(Asset asset); - void StoreTexture(AssetTexture texture); bool TryGetAsset(UUID id, out Asset asset); } } diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs index c64856b8..5ddc8b49 100644 --- a/Programs/Simian/Simian.cs +++ b/Programs/Simian/Simian.cs @@ -319,6 +319,7 @@ namespace Simian agent.LastName = lastName; agent.SessionID = UUID.Random(); agent.SecureSessionID = UUID.Random(); + // Assign a circuit code and insert the agent into the unassociatedAgents dictionary agent.CircuitCode = CreateAgentCircuit(agent); agent.InventoryRoot = UUID.Random(); agent.InventoryLibRoot = UUID.Random();