diff options
Diffstat (limited to 'OpenSim/Region')
17 files changed, 785 insertions, 1234 deletions
diff --git a/OpenSim/Region/Application/OpenSim.cs b/OpenSim/Region/Application/OpenSim.cs index f070812..4d8409bb 100644 --- a/OpenSim/Region/Application/OpenSim.cs +++ b/OpenSim/Region/Application/OpenSim.cs | |||
@@ -306,10 +306,6 @@ namespace OpenSim | |||
306 | "delete-region <name>", | 306 | "delete-region <name>", |
307 | "Delete a region from disk", RunCommand); | 307 | "Delete a region from disk", RunCommand); |
308 | 308 | ||
309 | m_console.Commands.AddCommand("region", false, "predecode-j2k", | ||
310 | "predecode-j2k [<num threads>]>", | ||
311 | "Precache assets,decode j2k layerdata", RunCommand); | ||
312 | |||
313 | m_console.Commands.AddCommand("region", false, "modules list", | 309 | m_console.Commands.AddCommand("region", false, "modules list", |
314 | "modules list", | 310 | "modules list", |
315 | "List modules", HandleModules); | 311 | "List modules", HandleModules); |
@@ -744,17 +740,6 @@ namespace OpenSim | |||
744 | } | 740 | } |
745 | break; | 741 | break; |
746 | 742 | ||
747 | case "predecode-j2k": | ||
748 | if (cmdparams.Length > 0) | ||
749 | { | ||
750 | m_sceneManager.CacheJ2kDecode(Convert.ToInt32(cmdparams[0])); | ||
751 | } | ||
752 | else | ||
753 | { | ||
754 | m_sceneManager.CacheJ2kDecode(1); | ||
755 | } | ||
756 | break; | ||
757 | |||
758 | } | 743 | } |
759 | } | 744 | } |
760 | 745 | ||
diff --git a/OpenSim/Region/ClientStack/LindenUDP/ILLPacketHandler.cs b/OpenSim/Region/ClientStack/LindenUDP/ILLPacketHandler.cs index 32a4ad4..31f9580 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/ILLPacketHandler.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/ILLPacketHandler.cs | |||
@@ -34,16 +34,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
34 | { | 34 | { |
35 | public delegate void PacketStats(int inPackets, int outPackets, int unAckedBytes); | 35 | public delegate void PacketStats(int inPackets, int outPackets, int unAckedBytes); |
36 | public delegate void PacketDrop(Packet pack, Object id); | 36 | public delegate void PacketDrop(Packet pack, Object id); |
37 | public delegate void QueueEmpty(ThrottleOutPacketType queue); | ||
37 | public delegate bool SynchronizeClientHandler(IScene scene, Packet packet, UUID agentID, ThrottleOutPacketType throttlePacketType); | 38 | public delegate bool SynchronizeClientHandler(IScene scene, Packet packet, UUID agentID, ThrottleOutPacketType throttlePacketType); |
38 | 39 | ||
39 | /// <summary> | 40 | /// <summary> |
40 | /// Interface to a class that handles all the activity involved with maintaining the client circuit (handling acks, | 41 | /// Interface to a class that handles all the activity involved with maintaining the client circuit (handling acks, |
41 | /// resends, pings, etc.) | 42 | /// resends, pings, etc.) |
42 | /// </summary> | 43 | /// </summary> |
43 | public interface ILLPacketHandler | 44 | public interface ILLPacketHandler : IDisposable |
44 | { | 45 | { |
45 | event PacketStats OnPacketStats; | 46 | event PacketStats OnPacketStats; |
46 | event PacketDrop OnPacketDrop; | 47 | event PacketDrop OnPacketDrop; |
48 | event QueueEmpty OnQueueEmpty; | ||
47 | SynchronizeClientHandler SynchronizeClient { set; } | 49 | SynchronizeClientHandler SynchronizeClient { set; } |
48 | 50 | ||
49 | int PacketsReceived { get; } | 51 | int PacketsReceived { get; } |
@@ -70,12 +72,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
70 | void OutPacket(Packet NewPack, | 72 | void OutPacket(Packet NewPack, |
71 | ThrottleOutPacketType throttlePacketType, Object id); | 73 | ThrottleOutPacketType throttlePacketType, Object id); |
72 | LLPacketQueue PacketQueue { get; } | 74 | LLPacketQueue PacketQueue { get; } |
73 | void Stop(); | ||
74 | void Flush(); | 75 | void Flush(); |
75 | void Clear(); | 76 | void Clear(); |
76 | ClientInfo GetClientInfo(); | 77 | ClientInfo GetClientInfo(); |
77 | void SetClientInfo(ClientInfo info); | 78 | void SetClientInfo(ClientInfo info); |
78 | void AddImportantPacket(PacketType type); | 79 | void AddImportantPacket(PacketType type); |
79 | void RemoveImportantPacket(PacketType type); | 80 | void RemoveImportantPacket(PacketType type); |
81 | int GetQueueCount(ThrottleOutPacketType queue); | ||
80 | } | 82 | } |
81 | } | 83 | } |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs index 638c765..5f549b5 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/J2KImage.cs | |||
@@ -38,40 +38,37 @@ using System.Reflection; | |||
38 | namespace OpenSim.Region.ClientStack.LindenUDP | 38 | namespace OpenSim.Region.ClientStack.LindenUDP |
39 | { | 39 | { |
40 | /// <summary> | 40 | /// <summary> |
41 | /// We use this class to store image data and associated request data and attributes | 41 | /// Stores information about a current texture download and a reference to the texture asset |
42 | /// </summary> | 42 | /// </summary> |
43 | public class J2KImage | 43 | public class J2KImage |
44 | { | 44 | { |
45 | private const int IMAGE_PACKET_SIZE = 1000; | ||
46 | private const int FIRST_PACKET_SIZE = 600; | ||
47 | |||
45 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 48 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
46 | 49 | ||
47 | public double m_designatedPriorityKey; | 50 | public uint m_lastSequence; |
48 | public double m_requestedPriority = 0.0d; | 51 | public float m_requestedPriority; |
49 | public uint m_lastSequence = 0; | ||
50 | public uint m_requestedPacketNumber; | 52 | public uint m_requestedPacketNumber; |
51 | public sbyte m_requestedDiscardLevel; | 53 | public sbyte m_requestedDiscardLevel; |
52 | public UUID m_requestedUUID; | 54 | public UUID m_requestedUUID; |
53 | public IJ2KDecoder m_j2kDecodeModule; | 55 | public IJ2KDecoder m_j2kDecodeModule; |
54 | public IAssetService m_assetCache; | 56 | public IAssetService m_assetCache; |
55 | public OpenJPEG.J2KLayerInfo[] Layers = new OpenJPEG.J2KLayerInfo[0]; | 57 | public OpenJPEG.J2KLayerInfo[] m_layers; |
56 | public AssetBase m_MissingSubstitute = null; | 58 | public bool m_decoded; |
57 | public bool m_decoded = false; | 59 | public bool m_hasasset; |
58 | public bool m_completedSendAtCurrentDiscardLevel; | 60 | public C5.IPriorityQueueHandle<J2KImage> m_priorityQueueHandle; |
59 | 61 | ||
60 | private sbyte m_discardLevel=-1; | ||
61 | private uint m_packetNumber; | 62 | private uint m_packetNumber; |
62 | private bool m_decoderequested = false; | 63 | private bool m_decoderequested; |
63 | private bool m_hasasset = false; | 64 | private bool m_asset_requested; |
64 | private bool m_asset_requested = false; | 65 | private bool m_sentinfo; |
65 | private bool m_sentinfo = false; | 66 | private uint m_stopPacket; |
66 | private uint m_stopPacket = 0; | 67 | private AssetBase m_asset; |
67 | private const int cImagePacketSize = 1000; | 68 | private int m_assetDataLength; |
68 | private const int cFirstPacketSize = 600; | 69 | private LLImageManager m_imageManager; |
69 | private AssetBase m_asset = null; | 70 | |
70 | private LLImageManager m_image; | 71 | #region Properties |
71 | public J2KImage(LLImageManager image) | ||
72 | { | ||
73 | m_image = image; | ||
74 | } | ||
75 | 72 | ||
76 | public uint m_pPacketNumber | 73 | public uint m_pPacketNumber |
77 | { | 74 | { |
@@ -84,10 +81,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
84 | 81 | ||
85 | public byte[] Data | 82 | public byte[] Data |
86 | { | 83 | { |
87 | get | 84 | get |
88 | { | 85 | { |
89 | if (m_asset != null) | 86 | if (m_asset != null) |
90 | return m_asset.Data; | 87 | return m_asset.Data; |
91 | else | 88 | else |
92 | return null; | 89 | return null; |
93 | } | 90 | } |
@@ -97,9 +94,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
97 | { | 94 | { |
98 | if (!m_decoded) | 95 | if (!m_decoded) |
99 | return 0; | 96 | return 0; |
97 | |||
100 | try | 98 | try |
101 | { | 99 | { |
102 | return (ushort)(((m_asset.Data.Length - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1); | 100 | return (ushort)(((m_assetDataLength - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1); |
103 | } | 101 | } |
104 | catch (Exception) | 102 | catch (Exception) |
105 | { | 103 | { |
@@ -110,119 +108,154 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
110 | } | 108 | } |
111 | } | 109 | } |
112 | 110 | ||
113 | public void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) | 111 | #endregion Properties |
114 | { | ||
115 | m_image.m_outstandingtextures++; | ||
116 | Layers = layers; | ||
117 | m_decoded = true; | ||
118 | RunUpdate(); | ||
119 | } | ||
120 | 112 | ||
121 | public void AssetDataCallback(UUID AssetID, AssetBase asset) | 113 | public J2KImage(LLImageManager imageManager) |
122 | { | 114 | { |
123 | m_hasasset = true; | 115 | m_imageManager = imageManager; |
124 | if (asset == null || asset.Data == null) | ||
125 | { | ||
126 | m_asset = m_MissingSubstitute; | ||
127 | } | ||
128 | else | ||
129 | { | ||
130 | m_asset = asset; | ||
131 | } | ||
132 | RunUpdate(); | ||
133 | } | 116 | } |
134 | 117 | ||
135 | protected void AssetReceived(string id, Object sender, AssetBase asset) | 118 | public bool SendPackets(LLClientView client, int maxpack) |
136 | { | 119 | { |
137 | UUID assetID = UUID.Zero; | 120 | if (m_packetNumber <= m_stopPacket) |
138 | if (asset != null) | 121 | { |
139 | assetID = asset.FullID; | 122 | bool SendMore = true; |
140 | 123 | if (!m_sentinfo || (m_packetNumber == 0)) | |
141 | AssetDataCallback(assetID, asset); | 124 | { |
142 | 125 | if (SendFirstPacket(client)) | |
143 | } | 126 | { |
127 | SendMore = false; | ||
128 | } | ||
129 | m_sentinfo = true; | ||
130 | m_packetNumber++; | ||
131 | } | ||
132 | // bool ignoreStop = false; | ||
133 | if (m_packetNumber < 2) | ||
134 | { | ||
135 | m_packetNumber = 2; | ||
136 | } | ||
144 | 137 | ||
145 | private int GetPacketForBytePosition(int bytePosition) | 138 | int count = 0; |
146 | { | 139 | while (SendMore && count < maxpack && m_packetNumber <= m_stopPacket) |
147 | return ((bytePosition - cFirstPacketSize + cImagePacketSize - 1) / cImagePacketSize) + 1; | 140 | { |
148 | } | 141 | count++; |
142 | SendMore = SendPacket(client); | ||
143 | m_packetNumber++; | ||
144 | } | ||
149 | 145 | ||
150 | public int LastPacketSize() | 146 | if (m_packetNumber > m_stopPacket) |
151 | { | 147 | return true; |
152 | if (m_packetNumber == 1) | ||
153 | return m_asset.Data.Length; | ||
154 | int lastsize = (m_asset.Data.Length - cFirstPacketSize) % cImagePacketSize; | ||
155 | //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary | ||
156 | if (lastsize == 0) | ||
157 | { | ||
158 | lastsize = cImagePacketSize; | ||
159 | } | 148 | } |
160 | return lastsize; | ||
161 | } | ||
162 | |||
163 | public int CurrentBytePosition() | ||
164 | { | ||
165 | if (m_packetNumber == 0) | ||
166 | return 0; | ||
167 | if (m_packetNumber == 1) | ||
168 | return cFirstPacketSize; | ||
169 | 149 | ||
170 | int result = cFirstPacketSize + ((int)m_packetNumber - 2) * cImagePacketSize; | 150 | return false; |
171 | if (result < 0) | ||
172 | { | ||
173 | result = cFirstPacketSize; | ||
174 | } | ||
175 | return result; | ||
176 | } | 151 | } |
177 | 152 | ||
178 | public bool SendFirstPacket(LLClientView client) | 153 | public void RunUpdate() |
179 | { | 154 | { |
180 | // this means we don't have | 155 | //This is where we decide what we need to update |
181 | if (Data == null) | 156 | //and assign the real discardLevel and packetNumber |
182 | { | 157 | //assuming of course that the connected client might be bonkers |
183 | client.SendImageNotFound(m_requestedUUID); | 158 | |
184 | m_log.WarnFormat("[TEXTURE]: Got null Data element on a asset {0}.. and the missing image Data property is al", m_requestedUUID); | 159 | if (!m_hasasset) |
185 | return true; | ||
186 | } | ||
187 | // Do we have less then 1 packet's worth of data? | ||
188 | else if (m_asset.Data.Length <= cFirstPacketSize) | ||
189 | { | 160 | { |
190 | // Send only 1 packet | 161 | if (!m_asset_requested) |
191 | client.SendImageFirstPart(1, m_requestedUUID, (uint)m_asset.Data.Length, m_asset.Data, 2); | 162 | { |
192 | m_stopPacket = 0; | 163 | m_asset_requested = true; |
193 | return true; | 164 | m_assetCache.Get(m_requestedUUID.ToString(), this, AssetReceived); |
165 | } | ||
194 | } | 166 | } |
195 | else | 167 | else |
196 | { | 168 | { |
197 | byte[] firstImageData = new byte[cFirstPacketSize]; | 169 | if (!m_decoded) |
198 | try | 170 | { |
199 | { | 171 | //We need to decode the requested image first |
200 | Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, (int)cFirstPacketSize); | 172 | if (!m_decoderequested) |
201 | client.SendImageFirstPart(TexturePacketCount(), m_requestedUUID, (uint)m_asset.Data.Length, firstImageData, 2); | 173 | { |
174 | //Request decode | ||
175 | m_decoderequested = true; | ||
176 | // Do we have a jpeg decoder? | ||
177 | if (m_j2kDecodeModule != null) | ||
178 | { | ||
179 | if (Data == null) | ||
180 | { | ||
181 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
182 | } | ||
183 | else | ||
184 | { | ||
185 | // Send it off to the jpeg decoder | ||
186 | m_j2kDecodeModule.BeginDecode(m_requestedUUID, Data, J2KDecodedCallback); | ||
187 | } | ||
188 | |||
189 | } | ||
190 | else | ||
191 | { | ||
192 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
193 | } | ||
194 | } | ||
202 | } | 195 | } |
203 | catch (Exception) | 196 | else |
204 | { | 197 | { |
205 | m_log.Error("Texture block copy failed. Possibly out of memory?"); | 198 | // Check for missing image asset data |
206 | return true; | 199 | if (m_asset == null || m_asset.Data == null) |
200 | { | ||
201 | // FIXME: | ||
202 | m_packetNumber = m_stopPacket; | ||
203 | return; | ||
204 | } | ||
205 | |||
206 | if (m_requestedDiscardLevel >= 0 || m_stopPacket == 0) | ||
207 | { | ||
208 | int maxDiscardLevel = Math.Max(0, m_layers.Length - 1); | ||
209 | |||
210 | // Treat initial texture downloads with a DiscardLevel of -1 a request for the highest DiscardLevel | ||
211 | if (m_requestedDiscardLevel < 0 && m_stopPacket == 0) | ||
212 | m_requestedDiscardLevel = (sbyte)maxDiscardLevel; | ||
213 | |||
214 | // Clamp at the highest discard level | ||
215 | m_requestedDiscardLevel = (sbyte)Math.Min(m_requestedDiscardLevel, maxDiscardLevel); | ||
216 | |||
217 | //Calculate the m_stopPacket | ||
218 | if (m_layers.Length > 0) | ||
219 | { | ||
220 | m_stopPacket = (uint)GetPacketForBytePosition(m_layers[(m_layers.Length - 1) - m_requestedDiscardLevel].End); | ||
221 | //I don't know why, but the viewer seems to expect the final packet if the file | ||
222 | //is just one packet bigger. | ||
223 | if (TexturePacketCount() == m_stopPacket + 1) | ||
224 | { | ||
225 | m_stopPacket = TexturePacketCount(); | ||
226 | } | ||
227 | } | ||
228 | else | ||
229 | { | ||
230 | m_stopPacket = TexturePacketCount(); | ||
231 | } | ||
232 | |||
233 | m_packetNumber = m_requestedPacketNumber; | ||
234 | } | ||
235 | |||
236 | if (m_imageManager.Client.PacketHandler.GetQueueCount(ThrottleOutPacketType.Texture) == 0) | ||
237 | { | ||
238 | //m_log.Debug("No textures queued, sending one packet to kickstart it"); | ||
239 | SendPacket(m_imageManager.Client); | ||
240 | } | ||
207 | } | 241 | } |
208 | } | 242 | } |
209 | return false; | ||
210 | } | 243 | } |
211 | 244 | ||
212 | private bool SendPacket(LLClientView client) | 245 | private bool SendPacket(LLClientView client) |
213 | { | 246 | { |
214 | bool complete = false; | 247 | bool complete = false; |
215 | int imagePacketSize = ((int)m_packetNumber == (TexturePacketCount())) ? LastPacketSize() : cImagePacketSize; | 248 | int imagePacketSize = ((int)m_packetNumber == (TexturePacketCount())) ? LastPacketSize() : IMAGE_PACKET_SIZE; |
216 | 249 | ||
217 | try | 250 | try |
218 | { | 251 | { |
219 | if ((CurrentBytePosition() + cImagePacketSize) > m_asset.Data.Length) | 252 | if ((CurrentBytePosition() + IMAGE_PACKET_SIZE) > m_assetDataLength) |
220 | { | 253 | { |
221 | imagePacketSize = LastPacketSize(); | 254 | imagePacketSize = LastPacketSize(); |
222 | complete=true; | 255 | complete = true; |
223 | if ((CurrentBytePosition() + imagePacketSize) > m_asset.Data.Length) | 256 | if ((CurrentBytePosition() + imagePacketSize) > m_assetDataLength) |
224 | { | 257 | { |
225 | imagePacketSize = m_asset.Data.Length - CurrentBytePosition(); | 258 | imagePacketSize = m_assetDataLength - CurrentBytePosition(); |
226 | complete = true; | 259 | complete = true; |
227 | } | 260 | } |
228 | } | 261 | } |
@@ -244,7 +277,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
244 | } | 277 | } |
245 | 278 | ||
246 | //Send the packet | 279 | //Send the packet |
247 | client.SendImageNextPart((ushort)(m_packetNumber-1), m_requestedUUID, imageData); | 280 | client.SendImageNextPart((ushort)(m_packetNumber - 1), m_requestedUUID, imageData); |
248 | } | 281 | } |
249 | if (complete) | 282 | if (complete) |
250 | { | 283 | { |
@@ -260,143 +293,115 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
260 | return false; | 293 | return false; |
261 | } | 294 | } |
262 | } | 295 | } |
263 | public bool SendPackets(LLClientView client, int maxpack) | 296 | |
297 | private int GetPacketForBytePosition(int bytePosition) | ||
264 | { | 298 | { |
299 | return ((bytePosition - FIRST_PACKET_SIZE + IMAGE_PACKET_SIZE - 1) / IMAGE_PACKET_SIZE) + 1; | ||
300 | } | ||
265 | 301 | ||
266 | if (!m_completedSendAtCurrentDiscardLevel) | 302 | private int LastPacketSize() |
303 | { | ||
304 | if (m_packetNumber == 1) | ||
305 | return m_assetDataLength; | ||
306 | int lastsize = (m_assetDataLength - FIRST_PACKET_SIZE) % IMAGE_PACKET_SIZE; | ||
307 | //If the last packet size is zero, it's really cImagePacketSize, it sits on the boundary | ||
308 | if (lastsize == 0) | ||
267 | { | 309 | { |
268 | if (m_packetNumber <= m_stopPacket) | 310 | lastsize = IMAGE_PACKET_SIZE; |
269 | { | 311 | } |
270 | bool SendMore = true; | 312 | return lastsize; |
271 | if (!m_sentinfo || (m_packetNumber == 0)) | 313 | } |
272 | { | ||
273 | if (SendFirstPacket(client)) | ||
274 | { | ||
275 | SendMore = false; | ||
276 | } | ||
277 | m_sentinfo = true; | ||
278 | m_packetNumber++; | ||
279 | } | ||
280 | // bool ignoreStop = false; | ||
281 | if (m_packetNumber < 2) | ||
282 | { | ||
283 | m_packetNumber = 2; | ||
284 | } | ||
285 | 314 | ||
286 | int count = 0; | 315 | private int CurrentBytePosition() |
287 | while (SendMore && count < maxpack && m_packetNumber <= m_stopPacket) | 316 | { |
288 | { | 317 | if (m_packetNumber == 0) |
289 | count++; | 318 | return 0; |
290 | SendMore = SendPacket(client); | 319 | if (m_packetNumber == 1) |
291 | m_packetNumber++; | 320 | return FIRST_PACKET_SIZE; |
292 | } | ||
293 | 321 | ||
294 | if (m_packetNumber > m_stopPacket) | 322 | int result = FIRST_PACKET_SIZE + ((int)m_packetNumber - 2) * IMAGE_PACKET_SIZE; |
295 | { | 323 | if (result < 0) |
296 | return true; | 324 | { |
297 | } | 325 | result = FIRST_PACKET_SIZE; |
326 | } | ||
327 | return result; | ||
328 | } | ||
329 | |||
330 | private bool SendFirstPacket(LLClientView client) | ||
331 | { | ||
332 | // this means we don't have | ||
333 | if (Data == null) | ||
334 | { | ||
335 | client.SendImageNotFound(m_requestedUUID); | ||
336 | m_log.WarnFormat("[TEXTURE]: Got null Data element on a asset {0}.. and the missing image Data property is also null", m_requestedUUID); | ||
337 | return true; | ||
338 | } | ||
339 | // Do we have less then 1 packet's worth of data? | ||
340 | else if (m_assetDataLength <= FIRST_PACKET_SIZE) | ||
341 | { | ||
342 | // Send only 1 packet | ||
343 | client.SendImageFirstPart(1, m_requestedUUID, (uint)m_assetDataLength, m_asset.Data, 2); | ||
344 | m_stopPacket = 0; | ||
345 | return true; | ||
346 | } | ||
347 | else | ||
348 | { | ||
349 | byte[] firstImageData = new byte[FIRST_PACKET_SIZE]; | ||
350 | try | ||
351 | { | ||
352 | Buffer.BlockCopy(m_asset.Data, 0, firstImageData, 0, (int)FIRST_PACKET_SIZE); | ||
353 | client.SendImageFirstPart(TexturePacketCount(), m_requestedUUID, (uint)m_assetDataLength, firstImageData, 2); | ||
354 | } | ||
355 | catch (Exception) | ||
356 | { | ||
357 | m_log.Error("Texture block copy failed. Possibly out of memory?"); | ||
358 | return true; | ||
298 | } | 359 | } |
299 | } | 360 | } |
300 | return false; | 361 | return false; |
301 | } | 362 | } |
302 | 363 | ||
303 | public void RunUpdate() | 364 | private void J2KDecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers) |
304 | { | 365 | { |
305 | //This is where we decide what we need to update | 366 | m_layers = layers; |
306 | //and assign the real discardLevel and packetNumber | 367 | m_decoded = true; |
307 | //assuming of course that the connected client might be bonkers | 368 | RunUpdate(); |
369 | } | ||
308 | 370 | ||
309 | if (!m_hasasset) | 371 | private void AssetDataCallback(UUID AssetID, AssetBase asset) |
310 | { | 372 | { |
373 | m_hasasset = true; | ||
311 | 374 | ||
312 | if (!m_asset_requested) | 375 | if (asset == null || asset.Data == null) |
376 | { | ||
377 | if (m_imageManager.MissingImage != null) | ||
313 | { | 378 | { |
314 | m_asset_requested = true; | 379 | m_asset = m_imageManager.MissingImage; |
315 | m_assetCache.Get(m_requestedUUID.ToString(), this, AssetReceived); | 380 | m_assetDataLength = m_asset.Data.Length; |
316 | 381 | } | |
382 | else | ||
383 | { | ||
384 | m_asset = null; | ||
385 | m_decoded = true; | ||
317 | } | 386 | } |
318 | |||
319 | } | 387 | } |
320 | else | 388 | else |
321 | { | 389 | { |
390 | m_asset = asset; | ||
391 | m_assetDataLength = m_asset.Data.Length; | ||
392 | } | ||
322 | 393 | ||
394 | RunUpdate(); | ||
395 | } | ||
323 | 396 | ||
324 | if (!m_decoded) | 397 | private void AssetReceived(string id, Object sender, AssetBase asset) |
325 | { | 398 | { |
326 | //We need to decode the requested image first | 399 | UUID assetID = UUID.Zero; |
327 | if (!m_decoderequested) | 400 | if (asset != null) |
328 | { | 401 | assetID = asset.FullID; |
329 | //Request decode | ||
330 | m_decoderequested = true; | ||
331 | // Do we have a jpeg decoder? | ||
332 | if (m_j2kDecodeModule != null) | ||
333 | { | ||
334 | if (Data == null) | ||
335 | { | ||
336 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
337 | } | ||
338 | // Send it off to the jpeg decoder | ||
339 | m_j2kDecodeModule.decode(m_requestedUUID, Data, J2KDecodedCallback); | ||
340 | |||
341 | } | ||
342 | else | ||
343 | { | ||
344 | J2KDecodedCallback(m_requestedUUID, new OpenJPEG.J2KLayerInfo[0]); | ||
345 | } | ||
346 | } | ||
347 | |||
348 | } | ||
349 | else | ||
350 | { | ||
351 | //discardLevel of -1 means just update the priority | ||
352 | if (m_requestedDiscardLevel != -1) | ||
353 | { | ||
354 | //Evaluate the discard level | ||
355 | //First, is it positive? | ||
356 | if (m_requestedDiscardLevel >= 0) | ||
357 | { | ||
358 | if (m_requestedDiscardLevel > Layers.Length - 1) | ||
359 | { | ||
360 | m_discardLevel = (sbyte)(Layers.Length - 1); | ||
361 | } | ||
362 | else | ||
363 | { | ||
364 | m_discardLevel = m_requestedDiscardLevel; | ||
365 | } | ||
366 | 402 | ||
367 | //Calculate the m_stopPacket | 403 | AssetDataCallback(assetID, asset); |
368 | if (Layers.Length > 0) | ||
369 | { | ||
370 | m_stopPacket = (uint)GetPacketForBytePosition(Layers[(Layers.Length - 1) - m_discardLevel].End); | ||
371 | //I don't know why, but the viewer seems to expect the final packet if the file | ||
372 | //is just one packet bigger. | ||
373 | if (TexturePacketCount() == m_stopPacket + 1) | ||
374 | { | ||
375 | m_stopPacket = TexturePacketCount(); | ||
376 | } | ||
377 | } | ||
378 | else | ||
379 | { | ||
380 | m_stopPacket = TexturePacketCount(); | ||
381 | } | ||
382 | //Don't reset packet number unless we're waiting or it's ahead of us | ||
383 | if (m_completedSendAtCurrentDiscardLevel || m_requestedPacketNumber>m_packetNumber) | ||
384 | { | ||
385 | m_packetNumber = m_requestedPacketNumber; | ||
386 | } | ||
387 | 404 | ||
388 | if (m_packetNumber <= m_stopPacket) | ||
389 | { | ||
390 | m_completedSendAtCurrentDiscardLevel = false; | ||
391 | } | ||
392 | } | ||
393 | } | ||
394 | else | ||
395 | { | ||
396 | m_packetNumber = m_stopPacket; | ||
397 | } | ||
398 | } | ||
399 | } | ||
400 | } | 405 | } |
401 | } | 406 | } |
402 | } | 407 | } |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 88ace6a..3b43771 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs | |||
@@ -81,8 +81,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
81 | private List<ObjectUpdatePacket.ObjectDataBlock> m_primFullUpdates = | 81 | private List<ObjectUpdatePacket.ObjectDataBlock> m_primFullUpdates = |
82 | new List<ObjectUpdatePacket.ObjectDataBlock>(); | 82 | new List<ObjectUpdatePacket.ObjectDataBlock>(); |
83 | 83 | ||
84 | private Timer m_textureRequestTimer; | ||
85 | |||
86 | private bool m_clientBlocked; | 84 | private bool m_clientBlocked; |
87 | 85 | ||
88 | private int m_probesWithNoIngressPackets; | 86 | private int m_probesWithNoIngressPackets; |
@@ -143,9 +141,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
143 | protected int m_primTerseUpdateRate = 10; | 141 | protected int m_primTerseUpdateRate = 10; |
144 | protected int m_primFullUpdateRate = 14; | 142 | protected int m_primFullUpdateRate = 14; |
145 | 143 | ||
146 | protected int m_textureRequestRate = 100; | 144 | protected int m_textureSendLimit = 20; |
147 | protected int m_textureSendLimit = 10; | 145 | protected int m_textureDataLimit = 10; |
148 | protected int m_textureDataLimit = 5; | ||
149 | 146 | ||
150 | protected int m_packetMTU = 1400; | 147 | protected int m_packetMTU = 1400; |
151 | 148 | ||
@@ -534,6 +531,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
534 | m_PacketHandler = new LLPacketHandler(this, m_networkServer, userSettings); | 531 | m_PacketHandler = new LLPacketHandler(this, m_networkServer, userSettings); |
535 | m_PacketHandler.SynchronizeClient = SynchronizeClient; | 532 | m_PacketHandler.SynchronizeClient = SynchronizeClient; |
536 | m_PacketHandler.OnPacketStats += PopulateStats; | 533 | m_PacketHandler.OnPacketStats += PopulateStats; |
534 | m_PacketHandler.OnQueueEmpty += HandleQueueEmpty; | ||
537 | 535 | ||
538 | if (scene.Config != null) | 536 | if (scene.Config != null) |
539 | { | 537 | { |
@@ -555,9 +553,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
555 | m_primFullUpdateRate = clientConfig.GetInt("FullUpdateRate", | 553 | m_primFullUpdateRate = clientConfig.GetInt("FullUpdateRate", |
556 | m_primFullUpdateRate); | 554 | m_primFullUpdateRate); |
557 | 555 | ||
558 | m_textureRequestRate = clientConfig.GetInt("TextureRequestRate", | ||
559 | m_textureRequestRate); | ||
560 | |||
561 | m_textureSendLimit = clientConfig.GetInt("TextureSendLimit", | 556 | m_textureSendLimit = clientConfig.GetInt("TextureSendLimit", |
562 | m_textureSendLimit); | 557 | m_textureSendLimit); |
563 | 558 | ||
@@ -607,9 +602,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
607 | if (m_primFullUpdateTimer.Enabled) | 602 | if (m_primFullUpdateTimer.Enabled) |
608 | lock (m_primFullUpdateTimer) | 603 | lock (m_primFullUpdateTimer) |
609 | m_primFullUpdateTimer.Stop(); | 604 | m_primFullUpdateTimer.Stop(); |
610 | if (m_textureRequestTimer.Enabled) | ||
611 | lock (m_textureRequestTimer) | ||
612 | m_textureRequestTimer.Stop(); | ||
613 | 605 | ||
614 | // This is just to give the client a reasonable chance of | 606 | // This is just to give the client a reasonable chance of |
615 | // flushing out all it's packets. There should probably | 607 | // flushing out all it's packets. There should probably |
@@ -633,6 +625,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
633 | // of the client thread regardless of where Close() is called. | 625 | // of the client thread regardless of where Close() is called. |
634 | KillEndDone(); | 626 | KillEndDone(); |
635 | } | 627 | } |
628 | |||
629 | Terminate(); | ||
636 | } | 630 | } |
637 | 631 | ||
638 | /// <summary> | 632 | /// <summary> |
@@ -704,10 +698,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
704 | if (m_primFullUpdateTimer.Enabled) | 698 | if (m_primFullUpdateTimer.Enabled) |
705 | lock (m_primFullUpdateTimer) | 699 | lock (m_primFullUpdateTimer) |
706 | m_primFullUpdateTimer.Stop(); | 700 | m_primFullUpdateTimer.Stop(); |
707 | |||
708 | if (m_textureRequestTimer.Enabled) | ||
709 | lock (m_textureRequestTimer) | ||
710 | m_textureRequestTimer.Stop(); | ||
711 | } | 701 | } |
712 | 702 | ||
713 | public void Restart() | 703 | public void Restart() |
@@ -730,23 +720,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
730 | m_primFullUpdateTimer = new Timer(m_primFullUpdateRate); | 720 | m_primFullUpdateTimer = new Timer(m_primFullUpdateRate); |
731 | m_primFullUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessPrimFullUpdates); | 721 | m_primFullUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessPrimFullUpdates); |
732 | m_primFullUpdateTimer.AutoReset = false; | 722 | m_primFullUpdateTimer.AutoReset = false; |
733 | |||
734 | m_textureRequestTimer = new Timer(m_textureRequestRate); | ||
735 | m_textureRequestTimer.Elapsed += new ElapsedEventHandler(ProcessTextureRequests); | ||
736 | m_textureRequestTimer.AutoReset = false; | ||
737 | |||
738 | } | 723 | } |
739 | 724 | ||
740 | public void Terminate() | 725 | private void Terminate() |
741 | { | 726 | { |
727 | IsActive = false; | ||
728 | |||
729 | m_clientPingTimer.Close(); | ||
730 | m_avatarTerseUpdateTimer.Close(); | ||
731 | m_primTerseUpdateTimer.Close(); | ||
732 | m_primFullUpdateTimer.Close(); | ||
733 | |||
742 | m_PacketHandler.OnPacketStats -= PopulateStats; | 734 | m_PacketHandler.OnPacketStats -= PopulateStats; |
743 | m_PacketHandler.Stop(); | 735 | m_PacketHandler.Dispose(); |
744 | 736 | ||
745 | // wait for thread stoped | 737 | // wait for thread stoped |
746 | m_clientThread.Join(); | 738 | // m_clientThread.Join(); |
747 | 739 | ||
748 | // delete circuit code | 740 | // delete circuit code |
749 | m_networkServer.CloseClient(this); | 741 | //m_networkServer.CloseClient(this); |
750 | } | 742 | } |
751 | 743 | ||
752 | #endregion | 744 | #endregion |
@@ -876,6 +868,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
876 | while (IsActive) | 868 | while (IsActive) |
877 | { | 869 | { |
878 | LLQueItem nextPacket = m_PacketHandler.PacketQueue.Dequeue(); | 870 | LLQueItem nextPacket = m_PacketHandler.PacketQueue.Dequeue(); |
871 | |||
872 | if (nextPacket == null) { | ||
873 | m_log.DebugFormat("[CLIENT]: PacketQueue return null LLQueItem"); | ||
874 | continue; | ||
875 | } | ||
879 | 876 | ||
880 | if (nextPacket.Incoming) | 877 | if (nextPacket.Incoming) |
881 | { | 878 | { |
@@ -967,10 +964,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
967 | m_primFullUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessPrimFullUpdates); | 964 | m_primFullUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessPrimFullUpdates); |
968 | m_primFullUpdateTimer.AutoReset = false; | 965 | m_primFullUpdateTimer.AutoReset = false; |
969 | 966 | ||
970 | m_textureRequestTimer = new Timer(m_textureRequestRate); | ||
971 | m_textureRequestTimer.Elapsed += new ElapsedEventHandler(ProcessTextureRequests); | ||
972 | m_textureRequestTimer.AutoReset = false; | ||
973 | |||
974 | m_scene.AddNewClient(this); | 967 | m_scene.AddNewClient(this); |
975 | 968 | ||
976 | RefreshGroupMembership(); | 969 | RefreshGroupMembership(); |
@@ -1042,26 +1035,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1042 | } | 1035 | } |
1043 | } | 1036 | } |
1044 | 1037 | ||
1045 | protected virtual void TextureRequestHandler() | ||
1046 | { | ||
1047 | m_log.DebugFormat("[TRH] Thread started"); | ||
1048 | while (m_imageManager != null) | ||
1049 | { | ||
1050 | try | ||
1051 | { | ||
1052 | while (m_imageManager != null) | ||
1053 | { | ||
1054 | } | ||
1055 | } | ||
1056 | catch (Exception e) | ||
1057 | { | ||
1058 | m_log.WarnFormat("[TRH] Exception in handler loop: {0}", e.Message); | ||
1059 | m_log.Debug(e); | ||
1060 | } | ||
1061 | } | ||
1062 | m_log.DebugFormat("[TRH] Thread terminated"); | ||
1063 | } | ||
1064 | |||
1065 | # endregion | 1038 | # endregion |
1066 | 1039 | ||
1067 | // Previously ClientView.API partial class | 1040 | // Previously ClientView.API partial class |
@@ -3161,22 +3134,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
3161 | } | 3134 | } |
3162 | } | 3135 | } |
3163 | 3136 | ||
3164 | // Unlike the other timers, this one is only started after | 3137 | void HandleQueueEmpty(ThrottleOutPacketType queue) |
3165 | // the first request is seen. | ||
3166 | |||
3167 | void ProcessTextureRequests(object sender, ElapsedEventArgs e) | ||
3168 | { | 3138 | { |
3169 | if (m_imageManager != null) | 3139 | switch (queue) |
3170 | { | 3140 | { |
3171 | if (m_imageManager.ProcessImageQueue(m_textureSendLimit, | 3141 | case ThrottleOutPacketType.Texture: |
3172 | m_textureDataLimit)) | 3142 | ProcessTextureRequests(); |
3173 | { | 3143 | break; |
3174 | lock (m_textureRequestTimer) | ||
3175 | m_textureRequestTimer.Start(); | ||
3176 | } | ||
3177 | } | 3144 | } |
3178 | } | 3145 | } |
3179 | 3146 | ||
3147 | void ProcessTextureRequests() | ||
3148 | { | ||
3149 | if (m_imageManager != null) | ||
3150 | m_imageManager.ProcessImageQueue(m_textureSendLimit, m_textureDataLimit); | ||
3151 | } | ||
3152 | |||
3180 | void ProcessPrimFullUpdates(object sender, ElapsedEventArgs e) | 3153 | void ProcessPrimFullUpdates(object sender, ElapsedEventArgs e) |
3181 | { | 3154 | { |
3182 | lock (m_primFullUpdates) | 3155 | lock (m_primFullUpdates) |
@@ -5275,13 +5248,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
5275 | // for the client session anyway, in order to protect ourselves against bad code in plugins | 5248 | // for the client session anyway, in order to protect ourselves against bad code in plugins |
5276 | try | 5249 | try |
5277 | { | 5250 | { |
5278 | List<byte> visualparams = new List<byte>(); | 5251 | byte[] visualparams = new byte[appear.VisualParam.Length]; |
5279 | foreach (AgentSetAppearancePacket.VisualParamBlock x in appear.VisualParam) | 5252 | for (int i = 0; i < appear.VisualParam.Length; i++) |
5280 | { | 5253 | visualparams[i] = appear.VisualParam[i].ParamValue; |
5281 | visualparams.Add(x.ParamValue); | ||
5282 | } | ||
5283 | 5254 | ||
5284 | handlerSetAppearance(appear.ObjectData.TextureEntry, visualparams); | 5255 | Primitive.TextureEntry te = null; |
5256 | if (appear.ObjectData.TextureEntry.Length > 1) | ||
5257 | te = new Primitive.TextureEntry(appear.ObjectData.TextureEntry, 0, appear.ObjectData.TextureEntry.Length); | ||
5258 | |||
5259 | handlerSetAppearance(te, visualparams); | ||
5285 | } | 5260 | } |
5286 | catch (Exception e) | 5261 | catch (Exception e) |
5287 | { | 5262 | { |
@@ -6624,8 +6599,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
6624 | if (m_imageManager != null) | 6599 | if (m_imageManager != null) |
6625 | { | 6600 | { |
6626 | m_imageManager.EnqueueReq(args); | 6601 | m_imageManager.EnqueueReq(args); |
6627 | lock (m_textureRequestTimer) | ||
6628 | m_textureRequestTimer.Start(); | ||
6629 | } | 6602 | } |
6630 | } | 6603 | } |
6631 | } | 6604 | } |
@@ -11026,5 +10999,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
11026 | } | 10999 | } |
11027 | 11000 | ||
11028 | #endregion | 11001 | #endregion |
11002 | |||
11003 | public void SendRebakeAvatarTextures(UUID textureID) | ||
11004 | { | ||
11005 | RebakeAvatarTexturesPacket pack = | ||
11006 | (RebakeAvatarTexturesPacket)PacketPool.Instance.GetPacket(PacketType.RebakeAvatarTextures); | ||
11007 | |||
11008 | pack.TextureData = new RebakeAvatarTexturesPacket.TextureDataBlock(); | ||
11009 | pack.TextureData.TextureID = textureID; | ||
11010 | OutPacket(pack, ThrottleOutPacketType.Task); | ||
11011 | } | ||
11029 | } | 11012 | } |
11030 | } | 11013 | } |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs index 295a5e6..a484fdf 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLImageManager.cs | |||
@@ -27,58 +27,60 @@ | |||
27 | 27 | ||
28 | using System; | 28 | using System; |
29 | using System.Threading; | 29 | using System.Threading; |
30 | using System.Collections; | ||
30 | using System.Collections.Generic; | 31 | using System.Collections.Generic; |
32 | using System.Reflection; | ||
31 | using OpenMetaverse; | 33 | using OpenMetaverse; |
32 | using OpenMetaverse.Imaging; | 34 | using OpenMetaverse.Imaging; |
33 | using OpenSim.Framework; | 35 | using OpenSim.Framework; |
34 | using OpenSim.Region.Framework.Interfaces; | 36 | using OpenSim.Region.Framework.Interfaces; |
35 | using OpenSim.Services.Interfaces; | 37 | using OpenSim.Services.Interfaces; |
36 | using log4net; | 38 | using log4net; |
37 | using System.Reflection; | ||
38 | 39 | ||
39 | namespace OpenSim.Region.ClientStack.LindenUDP | 40 | namespace OpenSim.Region.ClientStack.LindenUDP |
40 | { | 41 | { |
41 | |||
42 | public class LLImageManager | 42 | public class LLImageManager |
43 | { | 43 | { |
44 | 44 | private sealed class J2KImageComparer : IComparer<J2KImage> | |
45 | //Public interfaces: | 45 | { |
46 | //Constructor - (LLClientView, IAssetCache, IJ2KDecoder); | 46 | public int Compare(J2KImage x, J2KImage y) |
47 | //void EnqueueReq - (TextureRequestArgs) | 47 | { |
48 | //ProcessImageQueue | 48 | return x.m_requestedPriority.CompareTo(y.m_requestedPriority); |
49 | //Close | 49 | } |
50 | } | ||
51 | |||
50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 52 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
51 | private bool m_shuttingdown = false; | 53 | private bool m_shuttingdown = false; |
52 | private long m_lastloopprocessed = 0; | 54 | private long m_lastloopprocessed = 0; |
55 | private AssetBase m_missingImage = null; | ||
53 | 56 | ||
54 | private LLClientView m_client; //Client we're assigned to | 57 | private LLClientView m_client; //Client we're assigned to |
55 | private IAssetService m_assetCache; //Asset Cache | 58 | private IAssetService m_assetCache; //Asset Cache |
56 | private IJ2KDecoder m_j2kDecodeModule; //Our J2K module | 59 | private IJ2KDecoder m_j2kDecodeModule; //Our J2K module |
60 | private C5.IntervalHeap<J2KImage> m_priorityQueue = new C5.IntervalHeap<J2KImage>(10, new J2KImageComparer()); | ||
57 | 61 | ||
58 | private readonly AssetBase m_missingsubstitute; //Sustitute for bad decodes | ||
59 | private Dictionary<UUID,J2KImage> m_imagestore; // Our main image storage dictionary | ||
60 | private SortedList<double,UUID> m_priorities; // For fast image lookup based on priority | ||
61 | private Dictionary<int, int> m_priorityresolver; //Enabling super fast assignment of images with the same priorities | ||
62 | |||
63 | private const double doubleMinimum = .0000001; | ||
64 | |||
65 | public int m_outstandingtextures = 0; | ||
66 | //Constructor | ||
67 | public LLImageManager(LLClientView client, IAssetService pAssetCache, IJ2KDecoder pJ2kDecodeModule) | 62 | public LLImageManager(LLClientView client, IAssetService pAssetCache, IJ2KDecoder pJ2kDecodeModule) |
68 | { | 63 | { |
69 | |||
70 | m_imagestore = new Dictionary<UUID,J2KImage>(); | ||
71 | m_priorities = new SortedList<double,UUID>(); | ||
72 | m_priorityresolver = new Dictionary<int, int>(); | ||
73 | m_client = client; | 64 | m_client = client; |
74 | m_assetCache = pAssetCache; | 65 | m_assetCache = pAssetCache; |
75 | if (pAssetCache != null) | 66 | if (pAssetCache != null) |
76 | m_missingsubstitute = pAssetCache.Get("5748decc-f629-461c-9a36-a35a221fe21f"); | 67 | m_missingImage = pAssetCache.Get("5748decc-f629-461c-9a36-a35a221fe21f"); |
77 | else | 68 | else |
78 | m_log.Error("[ClientView] - couldn't set missing image, all manner of things will probably break"); | 69 | m_log.Error("[ClientView] - couldn't set missing image asset, falling back to missing image packet. This is known to crash the client"); |
70 | |||
79 | m_j2kDecodeModule = pJ2kDecodeModule; | 71 | m_j2kDecodeModule = pJ2kDecodeModule; |
80 | } | 72 | } |
81 | 73 | ||
74 | public LLClientView Client | ||
75 | { | ||
76 | get { return m_client; } | ||
77 | } | ||
78 | |||
79 | public AssetBase MissingImage | ||
80 | { | ||
81 | get { return m_missingImage; } | ||
82 | } | ||
83 | |||
82 | public void EnqueueReq(TextureRequestArgs newRequest) | 84 | public void EnqueueReq(TextureRequestArgs newRequest) |
83 | { | 85 | { |
84 | //newRequest is the properties of our new texture fetch request. | 86 | //newRequest is the properties of our new texture fetch request. |
@@ -88,234 +90,203 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
88 | //Make sure we're not shutting down.. | 90 | //Make sure we're not shutting down.. |
89 | if (!m_shuttingdown) | 91 | if (!m_shuttingdown) |
90 | { | 92 | { |
93 | J2KImage imgrequest; | ||
91 | 94 | ||
92 | //Do we already know about this UUID? | 95 | // Do a linear search for this texture download |
93 | if (m_imagestore.ContainsKey(newRequest.RequestedAssetID)) | 96 | lock (m_priorityQueue) |
94 | { | 97 | m_priorityQueue.Find(delegate(J2KImage img) { return img.m_requestedUUID == newRequest.RequestedAssetID; }, out imgrequest); |
95 | //Check the packet sequence to make sure this isn't older than | ||
96 | //one we've already received | ||
97 | |||
98 | J2KImage imgrequest = m_imagestore[newRequest.RequestedAssetID]; | ||
99 | |||
100 | // This is the inbound request sequence number. We can ignore | ||
101 | // "old" ones. | ||
102 | 98 | ||
103 | if (newRequest.requestSequence > imgrequest.m_lastSequence) | 99 | if (imgrequest != null) |
100 | { | ||
101 | if (newRequest.DiscardLevel == -1 && newRequest.Priority == 0f) | ||
104 | { | 102 | { |
103 | //m_log.Debug("[TEX]: (CAN) ID=" + newRequest.RequestedAssetID); | ||
105 | 104 | ||
106 | imgrequest.m_lastSequence = newRequest.requestSequence; | 105 | try |
107 | |||
108 | //Check the priority | ||
109 | |||
110 | double priority = imgrequest.m_requestedPriority; | ||
111 | if (priority != newRequest.Priority) | ||
112 | { | 106 | { |
113 | //Remove the old priority | 107 | lock (m_priorityQueue) |
114 | m_priorities.Remove(imgrequest.m_designatedPriorityKey); | 108 | m_priorityQueue.Delete(imgrequest.m_priorityQueueHandle); |
115 | //Assign a new unique priority | ||
116 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
117 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); | ||
118 | } | 109 | } |
110 | catch (Exception) { } | ||
111 | } | ||
112 | else | ||
113 | { | ||
114 | //m_log.DebugFormat("[TEX]: (UPD) ID={0}: D={1}, S={2}, P={3}", | ||
115 | // newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); | ||
119 | 116 | ||
120 | //Update the requested discard level | 117 | //Check the packet sequence to make sure this isn't older than |
121 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; | 118 | //one we've already received |
119 | if (newRequest.requestSequence > imgrequest.m_lastSequence) | ||
120 | { | ||
121 | //Update the sequence number of the last RequestImage packet | ||
122 | imgrequest.m_lastSequence = newRequest.requestSequence; | ||
122 | 123 | ||
123 | //Update the requested packet number | 124 | //Update the requested discard level |
124 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; | 125 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
125 | 126 | ||
126 | //Check if this will create an outstanding texture request | 127 | //Update the requested packet number |
127 | bool activated = imgrequest.m_completedSendAtCurrentDiscardLevel; | 128 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
128 | //Run an update | ||
129 | 129 | ||
130 | imgrequest.RunUpdate(); | 130 | //Update the requested priority |
131 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
132 | try | ||
133 | { | ||
134 | lock (m_priorityQueue) | ||
135 | m_priorityQueue.Replace(imgrequest.m_priorityQueueHandle, imgrequest); | ||
136 | } | ||
137 | catch (Exception) | ||
138 | { | ||
139 | imgrequest.m_priorityQueueHandle = null; | ||
140 | lock (m_priorityQueue) | ||
141 | m_priorityQueue.Add(ref imgrequest.m_priorityQueueHandle, imgrequest); | ||
142 | } | ||
131 | 143 | ||
132 | if (activated && !imgrequest.m_completedSendAtCurrentDiscardLevel && imgrequest.m_decoded) | 144 | //Run an update |
133 | { | 145 | imgrequest.RunUpdate(); |
134 | Interlocked.Increment(ref m_outstandingtextures); | ||
135 | } | 146 | } |
136 | } | 147 | } |
137 | } | 148 | } |
138 | else | 149 | else |
139 | { | 150 | { |
140 | J2KImage imgrequest = new J2KImage(this); | 151 | if (newRequest.DiscardLevel == -1 && newRequest.Priority == 0f) |
141 | 152 | { | |
142 | //Assign our missing substitute | 153 | //m_log.DebugFormat("[TEX]: (IGN) ID={0}: D={1}, S={2}, P={3}", |
143 | imgrequest.m_MissingSubstitute = m_missingsubstitute; | 154 | // newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); |
155 | } | ||
156 | else | ||
157 | { | ||
158 | //m_log.DebugFormat("[TEX]: (NEW) ID={0}: D={1}, S={2}, P={3}", | ||
159 | // newRequest.RequestedAssetID, newRequest.DiscardLevel, newRequest.PacketNumber, newRequest.Priority); | ||
144 | 160 | ||
145 | //Assign our decoder module | 161 | imgrequest = new J2KImage(this); |
146 | imgrequest.m_j2kDecodeModule = m_j2kDecodeModule; | ||
147 | 162 | ||
148 | //Assign our asset cache module | 163 | //Assign our decoder module |
149 | imgrequest.m_assetCache = m_assetCache; | 164 | imgrequest.m_j2kDecodeModule = m_j2kDecodeModule; |
150 | 165 | ||
151 | //Assign a priority based on our request | 166 | //Assign our asset cache module |
152 | imgrequest.m_designatedPriorityKey = AssignPriority(newRequest.RequestedAssetID, newRequest.Priority); | 167 | imgrequest.m_assetCache = m_assetCache; |
153 | 168 | ||
154 | //Assign the requested discard level | 169 | //Assign the requested discard level |
155 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; | 170 | imgrequest.m_requestedDiscardLevel = newRequest.DiscardLevel; |
156 | 171 | ||
157 | //Assign the requested packet number | 172 | //Assign the requested packet number |
158 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; | 173 | imgrequest.m_requestedPacketNumber = newRequest.PacketNumber; |
159 | 174 | ||
160 | //Assign the requested priority | 175 | //Assign the requested priority |
161 | imgrequest.m_requestedPriority = newRequest.Priority; | 176 | imgrequest.m_requestedPriority = newRequest.Priority; |
162 | 177 | ||
163 | //Assign the asset uuid | 178 | //Assign the asset uuid |
164 | imgrequest.m_requestedUUID = newRequest.RequestedAssetID; | 179 | imgrequest.m_requestedUUID = newRequest.RequestedAssetID; |
165 | 180 | ||
166 | m_imagestore.Add(imgrequest.m_requestedUUID, imgrequest); | 181 | //Assign the requested priority |
182 | imgrequest.m_requestedPriority = newRequest.Priority; | ||
167 | 183 | ||
168 | //Run an update | 184 | //Add this download to the priority queue |
169 | imgrequest.RunUpdate(); | 185 | lock (m_priorityQueue) |
186 | m_priorityQueue.Add(ref imgrequest.m_priorityQueueHandle, imgrequest); | ||
170 | 187 | ||
188 | //Run an update | ||
189 | imgrequest.RunUpdate(); | ||
190 | } | ||
171 | } | 191 | } |
172 | } | 192 | } |
173 | } | 193 | } |
174 | 194 | ||
175 | private double AssignPriority(UUID pAssetID, double pPriority) | 195 | public bool ProcessImageQueue(int count, int maxpack) |
176 | { | 196 | { |
177 | 197 | lock (this) | |
178 | //First, find out if we can just assign directly | ||
179 | if (m_priorityresolver.ContainsKey((int)pPriority) == false) | ||
180 | { | 198 | { |
181 | m_priorities.Add((double)((int)pPriority), pAssetID); | 199 | //count is the number of textures we want to process in one go. |
182 | m_priorityresolver.Add((int)pPriority, 0); | 200 | //As part of this class re-write, that number will probably rise |
183 | return (double)((int)pPriority); | 201 | //since we're processing in a more efficient manner. |
184 | } | ||
185 | else | ||
186 | { | ||
187 | //Use the hash lookup goodness of a secondary dictionary to find a free slot | ||
188 | double mFreePriority = ((int)pPriority) + (doubleMinimum * (m_priorityresolver[(int)pPriority] + 1)); | ||
189 | m_priorities[mFreePriority] = pAssetID; | ||
190 | m_priorityresolver[(int)pPriority]++; | ||
191 | return mFreePriority; | ||
192 | } | ||
193 | 202 | ||
203 | // this can happen during Close() | ||
204 | if (m_client == null) | ||
205 | return false; | ||
194 | 206 | ||
207 | int numCollected = 0; | ||
195 | 208 | ||
196 | } | 209 | //Calculate our threshold |
210 | int threshold; | ||
211 | if (m_lastloopprocessed == 0) | ||
212 | { | ||
213 | if (m_client.PacketHandler == null || m_client.PacketHandler.PacketQueue == null || m_client.PacketHandler.PacketQueue.TextureThrottle == null) | ||
214 | return false; | ||
215 | //This is decent for a semi fast machine, but we'll calculate it more accurately based on time below | ||
216 | threshold = m_client.PacketHandler.PacketQueue.TextureThrottle.Current / 6300; | ||
217 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
218 | } | ||
219 | else | ||
220 | { | ||
221 | double throttleseconds = ((double)DateTime.Now.Ticks - (double)m_lastloopprocessed) / (double)TimeSpan.TicksPerSecond; | ||
222 | throttleseconds = throttleseconds * m_client.PacketHandler.PacketQueue.TextureThrottle.Current; | ||
197 | 223 | ||
198 | public bool ProcessImageQueue(int count, int maxpack) | 224 | //Average of 1000 bytes per packet |
199 | { | 225 | throttleseconds = throttleseconds / 1000; |
226 | |||
227 | //Safe-zone multiplier of 2.0 | ||
228 | threshold = (int)(throttleseconds * 2.0); | ||
229 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
230 | |||
231 | } | ||
232 | |||
233 | if (m_client.PacketHandler == null) | ||
234 | return false; | ||
200 | 235 | ||
201 | // this can happen during Close() | 236 | if (m_client.PacketHandler.PacketQueue == null) |
202 | if (m_client == null) | 237 | return false; |
203 | return false; | 238 | |
204 | 239 | if (threshold < 10) | |
205 | //Count is the number of textures we want to process in one go. | 240 | threshold = 10; |
206 | //As part of this class re-write, that number will probably rise | 241 | |
207 | //since we're processing in a more efficient manner. | 242 | //Uncomment this to see what the texture stack is doing |
208 | 243 | //m_log.Debug("Queue: " + m_client.PacketHandler.PacketQueue.getQueueCount(ThrottleOutPacketType.Texture).ToString() + " Threshold: " + threshold.ToString() + " outstanding: " + m_outstandingtextures.ToString()); | |
209 | int numCollected = 0; | 244 | if (true) //m_client.PacketHandler.PacketQueue.GetQueueCount(ThrottleOutPacketType.Texture) < threshold) |
210 | |||
211 | //Calculate our threshold | ||
212 | int threshold; | ||
213 | if (m_lastloopprocessed == 0) | ||
214 | { | ||
215 | if (m_client.PacketHandler == null || m_client.PacketHandler.PacketQueue == null || m_client.PacketHandler.PacketQueue.TextureThrottle == null) | ||
216 | return false; | ||
217 | //This is decent for a semi fast machine, but we'll calculate it more accurately based on time below | ||
218 | threshold = m_client.PacketHandler.PacketQueue.TextureThrottle.Current / 6300; | ||
219 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
220 | } | ||
221 | else | ||
222 | { | ||
223 | double throttleseconds = ((double)DateTime.Now.Ticks - (double)m_lastloopprocessed) / (double)TimeSpan.TicksPerSecond; | ||
224 | throttleseconds = throttleseconds * m_client.PacketHandler.PacketQueue.TextureThrottle.Current; | ||
225 | |||
226 | //Average of 1000 bytes per packet | ||
227 | throttleseconds = throttleseconds / 1000; | ||
228 | |||
229 | //Safe-zone multiplier of 2.0 | ||
230 | threshold = (int)(throttleseconds * 2.0); | ||
231 | m_lastloopprocessed = DateTime.Now.Ticks; | ||
232 | |||
233 | } | ||
234 | |||
235 | if (threshold < 10) | ||
236 | { | ||
237 | threshold = 10; | ||
238 | } | ||
239 | |||
240 | if (m_client.PacketHandler == null) | ||
241 | return false; | ||
242 | |||
243 | if (m_client.PacketHandler.PacketQueue == null) | ||
244 | return false; | ||
245 | |||
246 | //First of all make sure our packet queue isn't above our threshold | ||
247 | |||
248 | //Uncomment this to see what the texture stack is doing | ||
249 | //m_log.Debug("Queue: " + m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount.ToString() + " Threshold: " + threshold.ToString() + " outstanding: " + m_outstandingtextures.ToString()); | ||
250 | if (m_client.PacketHandler.PacketQueue.TextureOutgoingPacketQueueCount < threshold && m_outstandingtextures > 0) | ||
251 | { | ||
252 | bool justreset = false; | ||
253 | |||
254 | for (int x = m_priorities.Count - 1; x > -1; x--) | ||
255 | { | 245 | { |
256 | 246 | while (m_priorityQueue.Count > 0) | |
257 | J2KImage imagereq = m_imagestore[m_priorities.Values[x]]; | ||
258 | if (imagereq.m_decoded == true && !imagereq.m_completedSendAtCurrentDiscardLevel) | ||
259 | { | 247 | { |
260 | numCollected++; | 248 | J2KImage imagereq = null; |
261 | //SendPackets will send up to ten packets per cycle | 249 | lock (m_priorityQueue) |
262 | if (imagereq.SendPackets(m_client, maxpack)) | 250 | imagereq = m_priorityQueue.FindMax(); |
251 | |||
252 | if (imagereq.m_decoded == true) | ||
263 | { | 253 | { |
264 | //Send complete | 254 | // we need to test this here now that we are dropping assets |
265 | if (!imagereq.m_completedSendAtCurrentDiscardLevel) | 255 | if (!imagereq.m_hasasset) |
266 | { | 256 | { |
267 | imagereq.m_completedSendAtCurrentDiscardLevel = true; | 257 | m_log.WarnFormat("[LLIMAGE MANAGER]: Re-requesting the image asset {0}", imagereq.m_requestedUUID); |
268 | Interlocked.Decrement(ref m_outstandingtextures); | 258 | imagereq.RunUpdate(); |
269 | //Re-assign priority to bottom | 259 | continue; |
270 | //Remove the old priority | 260 | } |
271 | m_priorities.Remove(imagereq.m_designatedPriorityKey); | 261 | |
272 | int lowest; | 262 | ++numCollected; |
273 | if (m_priorities.Count > 0) | 263 | |
274 | { | 264 | //SendPackets will send up to ten packets per cycle |
275 | lowest = (int)m_priorities.Keys[0]; | 265 | if (imagereq.SendPackets(m_client, maxpack)) |
276 | lowest--; | 266 | { |
277 | } | 267 | // Send complete. Destroy any knowledge of this transfer |
278 | else | 268 | try |
279 | { | 269 | { |
280 | lowest = -10000; | 270 | lock (m_priorityQueue) |
281 | } | 271 | m_priorityQueue.Delete(imagereq.m_priorityQueueHandle); |
282 | m_priorities.Add((double)lowest, imagereq.m_requestedUUID); | ||
283 | imagereq.m_designatedPriorityKey = (double)lowest; | ||
284 | if (m_priorityresolver.ContainsKey((int)lowest)) | ||
285 | { | ||
286 | m_priorityresolver[(int)lowest]++; | ||
287 | } | ||
288 | else | ||
289 | { | ||
290 | m_priorityresolver.Add((int)lowest, 0); | ||
291 | } | 272 | } |
273 | catch (Exception) { } | ||
292 | } | 274 | } |
293 | } | 275 | } |
276 | |||
294 | if (numCollected == count) | 277 | if (numCollected == count) |
295 | { | ||
296 | break; | 278 | break; |
297 | } | ||
298 | } | ||
299 | if (numCollected == count || m_outstandingtextures == 0) | ||
300 | break; | ||
301 | if (numCollected % m_outstandingtextures == 0 && !justreset) | ||
302 | { | ||
303 | //We've gotten as much as we can from the stack, | ||
304 | //reset to the top so that we can send MOAR DATA (nomnomnom)! | ||
305 | x = m_priorities.Count - 1; | ||
306 | |||
307 | justreset = true; //prevents us from getting stuck in a loop | ||
308 | } | 279 | } |
309 | } | 280 | } |
310 | } | ||
311 | 281 | ||
312 | return m_outstandingtextures != 0; | 282 | return m_priorityQueue.Count > 0; |
283 | } | ||
313 | } | 284 | } |
314 | 285 | ||
315 | //Faux destructor | 286 | //Faux destructor |
316 | public void Close() | 287 | public void Close() |
317 | { | 288 | { |
318 | 289 | ||
319 | m_shuttingdown = true; | 290 | m_shuttingdown = true; |
320 | m_j2kDecodeModule = null; | 291 | m_j2kDecodeModule = null; |
321 | m_assetCache = null; | 292 | m_assetCache = null; |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs b/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs index eaf8f60..e98a360 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLPacketHandler.cs | |||
@@ -129,6 +129,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
129 | // | 129 | // |
130 | public event PacketStats OnPacketStats; | 130 | public event PacketStats OnPacketStats; |
131 | public event PacketDrop OnPacketDrop; | 131 | public event PacketDrop OnPacketDrop; |
132 | public event QueueEmpty OnQueueEmpty; | ||
132 | 133 | ||
133 | 134 | ||
134 | //private SynchronizeClientHandler m_SynchronizeClient = null; | 135 | //private SynchronizeClientHandler m_SynchronizeClient = null; |
@@ -172,13 +173,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
172 | 173 | ||
173 | m_PacketQueue = new LLPacketQueue(client.AgentId, userSettings); | 174 | m_PacketQueue = new LLPacketQueue(client.AgentId, userSettings); |
174 | 175 | ||
176 | m_PacketQueue.OnQueueEmpty += TriggerOnQueueEmpty; | ||
177 | |||
175 | m_AckTimer.Elapsed += AckTimerElapsed; | 178 | m_AckTimer.Elapsed += AckTimerElapsed; |
176 | m_AckTimer.Start(); | 179 | m_AckTimer.Start(); |
177 | } | 180 | } |
178 | 181 | ||
179 | public void Stop() | 182 | public void Dispose() |
180 | { | 183 | { |
181 | m_AckTimer.Stop(); | 184 | m_AckTimer.Stop(); |
185 | m_AckTimer.Close(); | ||
182 | 186 | ||
183 | m_PacketQueue.Enqueue(null); | 187 | m_PacketQueue.Enqueue(null); |
184 | m_PacketQueue.Close(); | 188 | m_PacketQueue.Close(); |
@@ -768,6 +772,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
768 | handlerPacketDrop(packet, id); | 772 | handlerPacketDrop(packet, id); |
769 | } | 773 | } |
770 | 774 | ||
775 | private void TriggerOnQueueEmpty(ThrottleOutPacketType queue) | ||
776 | { | ||
777 | QueueEmpty handlerQueueEmpty = OnQueueEmpty; | ||
778 | |||
779 | if (handlerQueueEmpty != null) | ||
780 | handlerQueueEmpty(queue); | ||
781 | } | ||
782 | |||
771 | // Convert the packet to bytes and stuff it onto the send queue | 783 | // Convert the packet to bytes and stuff it onto the send queue |
772 | // | 784 | // |
773 | public void ProcessOutPacket(LLQueItem item) | 785 | public void ProcessOutPacket(LLQueItem item) |
@@ -849,5 +861,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
849 | m_PacketQueue.Close(); | 861 | m_PacketQueue.Close(); |
850 | Thread.CurrentThread.Abort(); | 862 | Thread.CurrentThread.Abort(); |
851 | } | 863 | } |
864 | |||
865 | public int GetQueueCount(ThrottleOutPacketType queue) | ||
866 | { | ||
867 | return m_PacketQueue.GetQueueCount(queue); | ||
868 | } | ||
852 | } | 869 | } |
853 | } | 870 | } |
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs b/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs index c427870..3eed2e0 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLPacketQueue.cs | |||
@@ -39,7 +39,7 @@ using Timer=System.Timers.Timer; | |||
39 | 39 | ||
40 | namespace OpenSim.Region.ClientStack.LindenUDP | 40 | namespace OpenSim.Region.ClientStack.LindenUDP |
41 | { | 41 | { |
42 | public class LLPacketQueue : IPullStatsProvider | 42 | public class LLPacketQueue : IPullStatsProvider, IDisposable |
43 | { | 43 | { |
44 | private static readonly ILog m_log | 44 | private static readonly ILog m_log |
45 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 45 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
@@ -62,6 +62,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
62 | private Queue<LLQueItem> TextureOutgoingPacketQueue; | 62 | private Queue<LLQueItem> TextureOutgoingPacketQueue; |
63 | private Queue<LLQueItem> AssetOutgoingPacketQueue; | 63 | private Queue<LLQueItem> AssetOutgoingPacketQueue; |
64 | 64 | ||
65 | private List<ThrottleOutPacketType> Empty = new List<ThrottleOutPacketType>(); | ||
66 | // m_log.Info("[THROTTLE]: Entering Throttle"); | ||
65 | // private Dictionary<uint, uint> PendingAcks = new Dictionary<uint, uint>(); | 67 | // private Dictionary<uint, uint> PendingAcks = new Dictionary<uint, uint>(); |
66 | // private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>(); | 68 | // private Dictionary<uint, Packet> NeedAck = new Dictionary<uint, Packet>(); |
67 | 69 | ||
@@ -85,26 +87,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
85 | 87 | ||
86 | private Dictionary<uint,int> contents = new Dictionary<uint, int>(); | 88 | private Dictionary<uint,int> contents = new Dictionary<uint, int>(); |
87 | 89 | ||
88 | /// <summary> | ||
89 | /// The number of packets in the OutgoingPacketQueue | ||
90 | /// | ||
91 | /// </summary> | ||
92 | internal int TextureOutgoingPacketQueueCount | ||
93 | { | ||
94 | get | ||
95 | { | ||
96 | if (TextureOutgoingPacketQueue == null) | ||
97 | return 0; | ||
98 | return TextureOutgoingPacketQueue.Count; | ||
99 | } | ||
100 | } | ||
101 | |||
102 | // private long LastThrottle; | 90 | // private long LastThrottle; |
103 | // private long ThrottleInterval; | 91 | // private long ThrottleInterval; |
104 | private Timer throttleTimer; | 92 | private Timer throttleTimer; |
105 | 93 | ||
106 | private UUID m_agentId; | 94 | private UUID m_agentId; |
107 | 95 | ||
96 | public event QueueEmpty OnQueueEmpty; | ||
97 | |||
108 | public LLPacketQueue(UUID agentId, ClientStackUserSettings userSettings) | 98 | public LLPacketQueue(UUID agentId, ClientStackUserSettings userSettings) |
109 | { | 99 | { |
110 | // While working on this, the BlockingQueue had me fooled for a bit. | 100 | // While working on this, the BlockingQueue had me fooled for a bit. |
@@ -210,28 +200,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
210 | switch (item.throttleType & ThrottleOutPacketType.TypeMask) | 200 | switch (item.throttleType & ThrottleOutPacketType.TypeMask) |
211 | { | 201 | { |
212 | case ThrottleOutPacketType.Resend: | 202 | case ThrottleOutPacketType.Resend: |
213 | ThrottleCheck(ref ResendThrottle, ref ResendOutgoingPacketQueue, item); | 203 | ThrottleCheck(ref ResendThrottle, ref ResendOutgoingPacketQueue, item, ThrottleOutPacketType.Resend); |
214 | break; | 204 | break; |
215 | case ThrottleOutPacketType.Texture: | 205 | case ThrottleOutPacketType.Texture: |
216 | ThrottleCheck(ref TextureThrottle, ref TextureOutgoingPacketQueue, item); | 206 | ThrottleCheck(ref TextureThrottle, ref TextureOutgoingPacketQueue, item, ThrottleOutPacketType.Texture); |
217 | break; | 207 | break; |
218 | case ThrottleOutPacketType.Task: | 208 | case ThrottleOutPacketType.Task: |
219 | if ((item.throttleType & ThrottleOutPacketType.LowPriority) != 0) | 209 | if ((item.throttleType & ThrottleOutPacketType.LowPriority) != 0) |
220 | ThrottleCheck(ref TaskThrottle, ref TaskLowpriorityPacketQueue, item); | 210 | ThrottleCheck(ref TaskThrottle, ref TaskLowpriorityPacketQueue, item, ThrottleOutPacketType.Task); |
221 | else | 211 | else |
222 | ThrottleCheck(ref TaskThrottle, ref TaskOutgoingPacketQueue, item); | 212 | ThrottleCheck(ref TaskThrottle, ref TaskOutgoingPacketQueue, item, ThrottleOutPacketType.Task); |
223 | break; | 213 | break; |
224 | case ThrottleOutPacketType.Land: | 214 | case ThrottleOutPacketType.Land: |
225 | ThrottleCheck(ref LandThrottle, ref LandOutgoingPacketQueue, item); | 215 | ThrottleCheck(ref LandThrottle, ref LandOutgoingPacketQueue, item, ThrottleOutPacketType.Land); |
226 | break; | 216 | break; |
227 | case ThrottleOutPacketType.Asset: | 217 | case ThrottleOutPacketType.Asset: |
228 | ThrottleCheck(ref AssetThrottle, ref AssetOutgoingPacketQueue, item); | 218 | ThrottleCheck(ref AssetThrottle, ref AssetOutgoingPacketQueue, item, ThrottleOutPacketType.Asset); |
229 | break; | 219 | break; |
230 | case ThrottleOutPacketType.Cloud: | 220 | case ThrottleOutPacketType.Cloud: |
231 | ThrottleCheck(ref CloudThrottle, ref CloudOutgoingPacketQueue, item); | 221 | ThrottleCheck(ref CloudThrottle, ref CloudOutgoingPacketQueue, item, ThrottleOutPacketType.Cloud); |
232 | break; | 222 | break; |
233 | case ThrottleOutPacketType.Wind: | 223 | case ThrottleOutPacketType.Wind: |
234 | ThrottleCheck(ref WindThrottle, ref WindOutgoingPacketQueue, item); | 224 | ThrottleCheck(ref WindThrottle, ref WindOutgoingPacketQueue, item, ThrottleOutPacketType.Wind); |
235 | break; | 225 | break; |
236 | 226 | ||
237 | default: | 227 | default: |
@@ -283,43 +273,25 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
283 | { | 273 | { |
284 | lock (this) | 274 | lock (this) |
285 | { | 275 | { |
286 | while (PacketsWaiting()) | 276 | // These categories do not contain transactional packets so we can safely drop any pending data in them |
277 | LandOutgoingPacketQueue.Clear(); | ||
278 | WindOutgoingPacketQueue.Clear(); | ||
279 | CloudOutgoingPacketQueue.Clear(); | ||
280 | TextureOutgoingPacketQueue.Clear(); | ||
281 | AssetOutgoingPacketQueue.Clear(); | ||
282 | |||
283 | // Now comes the fun part.. we dump all remaining resend and task packets into the send queue | ||
284 | while (ResendOutgoingPacketQueue.Count > 0 || TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) | ||
287 | { | 285 | { |
288 | //Now comes the fun part.. we dump all our elements into m_packetQueue that we've saved up. | ||
289 | if (ResendOutgoingPacketQueue.Count > 0) | 286 | if (ResendOutgoingPacketQueue.Count > 0) |
290 | { | ||
291 | SendQueue.Enqueue(ResendOutgoingPacketQueue.Dequeue()); | 287 | SendQueue.Enqueue(ResendOutgoingPacketQueue.Dequeue()); |
292 | } | 288 | |
293 | if (LandOutgoingPacketQueue.Count > 0) | ||
294 | { | ||
295 | SendQueue.Enqueue(LandOutgoingPacketQueue.Dequeue()); | ||
296 | } | ||
297 | if (WindOutgoingPacketQueue.Count > 0) | ||
298 | { | ||
299 | SendQueue.Enqueue(WindOutgoingPacketQueue.Dequeue()); | ||
300 | } | ||
301 | if (CloudOutgoingPacketQueue.Count > 0) | ||
302 | { | ||
303 | SendQueue.Enqueue(CloudOutgoingPacketQueue.Dequeue()); | ||
304 | } | ||
305 | if (TaskOutgoingPacketQueue.Count > 0) | 289 | if (TaskOutgoingPacketQueue.Count > 0) |
306 | { | ||
307 | SendQueue.PriorityEnqueue(TaskOutgoingPacketQueue.Dequeue()); | 290 | SendQueue.PriorityEnqueue(TaskOutgoingPacketQueue.Dequeue()); |
308 | } | 291 | |
309 | if (TaskLowpriorityPacketQueue.Count > 0) | 292 | if (TaskLowpriorityPacketQueue.Count > 0) |
310 | { | ||
311 | SendQueue.Enqueue(TaskLowpriorityPacketQueue.Dequeue()); | 293 | SendQueue.Enqueue(TaskLowpriorityPacketQueue.Dequeue()); |
312 | } | ||
313 | if (TextureOutgoingPacketQueue.Count > 0) | ||
314 | { | ||
315 | SendQueue.Enqueue(TextureOutgoingPacketQueue.Dequeue()); | ||
316 | } | ||
317 | if (AssetOutgoingPacketQueue.Count > 0) | ||
318 | { | ||
319 | SendQueue.Enqueue(AssetOutgoingPacketQueue.Dequeue()); | ||
320 | } | ||
321 | } | 294 | } |
322 | // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets"); | ||
323 | } | 295 | } |
324 | } | 296 | } |
325 | 297 | ||
@@ -342,11 +314,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
342 | 314 | ||
343 | public void Close() | 315 | public void Close() |
344 | { | 316 | { |
317 | Dispose(); | ||
318 | } | ||
319 | |||
320 | public void Dispose() | ||
321 | { | ||
345 | Flush(); | 322 | Flush(); |
346 | WipeClean(); // I'm sure there's a dirty joke in here somewhere. -AFrisby | 323 | WipeClean(); // I'm sure there's a dirty joke in here somewhere. -AFrisby |
347 | 324 | ||
348 | m_enabled = false; | 325 | m_enabled = false; |
349 | throttleTimer.Stop(); | 326 | throttleTimer.Stop(); |
327 | throttleTimer.Close(); | ||
350 | 328 | ||
351 | if (StatsManager.SimExtraStats != null) | 329 | if (StatsManager.SimExtraStats != null) |
352 | { | 330 | { |
@@ -388,6 +366,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
388 | 366 | ||
389 | int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once. | 367 | int MaxThrottleLoops = 4550; // 50*7 packets can be dequeued at once. |
390 | int throttleLoops = 0; | 368 | int throttleLoops = 0; |
369 | List<ThrottleOutPacketType> e; | ||
391 | 370 | ||
392 | // We're going to dequeue all of the saved up packets until | 371 | // We're going to dequeue all of the saved up packets until |
393 | // we've hit the throttle limit or there's no more packets to send | 372 | // we've hit the throttle limit or there's no more packets to send |
@@ -399,7 +378,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
399 | bool qchanged = true; | 378 | bool qchanged = true; |
400 | 379 | ||
401 | ResetCounters(); | 380 | ResetCounters(); |
402 | // m_log.Info("[THROTTLE]: Entering Throttle"); | 381 | |
403 | while (TotalThrottle.UnderLimit() && qchanged && throttleLoops <= MaxThrottleLoops) | 382 | while (TotalThrottle.UnderLimit() && qchanged && throttleLoops <= MaxThrottleLoops) |
404 | { | 383 | { |
405 | qchanged = false; // We will break out of the loop if no work was accomplished | 384 | qchanged = false; // We will break out of the loop if no work was accomplished |
@@ -425,6 +404,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
425 | TotalThrottle.AddBytes(qpack.Length); | 404 | TotalThrottle.AddBytes(qpack.Length); |
426 | LandThrottle.AddBytes(qpack.Length); | 405 | LandThrottle.AddBytes(qpack.Length); |
427 | qchanged = true; | 406 | qchanged = true; |
407 | |||
408 | if (LandOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Land)) | ||
409 | Empty.Add(ThrottleOutPacketType.Land); | ||
428 | } | 410 | } |
429 | 411 | ||
430 | if ((WindOutgoingPacketQueue.Count > 0) && WindThrottle.UnderLimit()) | 412 | if ((WindOutgoingPacketQueue.Count > 0) && WindThrottle.UnderLimit()) |
@@ -435,6 +417,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
435 | TotalThrottle.AddBytes(qpack.Length); | 417 | TotalThrottle.AddBytes(qpack.Length); |
436 | WindThrottle.AddBytes(qpack.Length); | 418 | WindThrottle.AddBytes(qpack.Length); |
437 | qchanged = true; | 419 | qchanged = true; |
420 | |||
421 | if (WindOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Wind)) | ||
422 | Empty.Add(ThrottleOutPacketType.Wind); | ||
438 | } | 423 | } |
439 | 424 | ||
440 | if ((CloudOutgoingPacketQueue.Count > 0) && CloudThrottle.UnderLimit()) | 425 | if ((CloudOutgoingPacketQueue.Count > 0) && CloudThrottle.UnderLimit()) |
@@ -445,6 +430,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
445 | TotalThrottle.AddBytes(qpack.Length); | 430 | TotalThrottle.AddBytes(qpack.Length); |
446 | CloudThrottle.AddBytes(qpack.Length); | 431 | CloudThrottle.AddBytes(qpack.Length); |
447 | qchanged = true; | 432 | qchanged = true; |
433 | |||
434 | if (CloudOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Cloud)) | ||
435 | Empty.Add(ThrottleOutPacketType.Cloud); | ||
448 | } | 436 | } |
449 | 437 | ||
450 | if ((TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) && TaskThrottle.UnderLimit()) | 438 | if ((TaskOutgoingPacketQueue.Count > 0 || TaskLowpriorityPacketQueue.Count > 0) && TaskThrottle.UnderLimit()) |
@@ -464,6 +452,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
464 | TotalThrottle.AddBytes(qpack.Length); | 452 | TotalThrottle.AddBytes(qpack.Length); |
465 | TaskThrottle.AddBytes(qpack.Length); | 453 | TaskThrottle.AddBytes(qpack.Length); |
466 | qchanged = true; | 454 | qchanged = true; |
455 | |||
456 | if (TaskOutgoingPacketQueue.Count == 0 && TaskLowpriorityPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Task)) | ||
457 | Empty.Add(ThrottleOutPacketType.Task); | ||
467 | } | 458 | } |
468 | 459 | ||
469 | if ((TextureOutgoingPacketQueue.Count > 0) && TextureThrottle.UnderLimit()) | 460 | if ((TextureOutgoingPacketQueue.Count > 0) && TextureThrottle.UnderLimit()) |
@@ -474,6 +465,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
474 | TotalThrottle.AddBytes(qpack.Length); | 465 | TotalThrottle.AddBytes(qpack.Length); |
475 | TextureThrottle.AddBytes(qpack.Length); | 466 | TextureThrottle.AddBytes(qpack.Length); |
476 | qchanged = true; | 467 | qchanged = true; |
468 | |||
469 | if (TextureOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Texture)) | ||
470 | Empty.Add(ThrottleOutPacketType.Texture); | ||
477 | } | 471 | } |
478 | 472 | ||
479 | if ((AssetOutgoingPacketQueue.Count > 0) && AssetThrottle.UnderLimit()) | 473 | if ((AssetOutgoingPacketQueue.Count > 0) && AssetThrottle.UnderLimit()) |
@@ -484,10 +478,30 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
484 | TotalThrottle.AddBytes(qpack.Length); | 478 | TotalThrottle.AddBytes(qpack.Length); |
485 | AssetThrottle.AddBytes(qpack.Length); | 479 | AssetThrottle.AddBytes(qpack.Length); |
486 | qchanged = true; | 480 | qchanged = true; |
481 | |||
482 | if (AssetOutgoingPacketQueue.Count == 0 && !Empty.Contains(ThrottleOutPacketType.Asset)) | ||
483 | Empty.Add(ThrottleOutPacketType.Asset); | ||
487 | } | 484 | } |
488 | } | 485 | } |
489 | // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets"); | 486 | // m_log.Info("[THROTTLE]: Processed " + throttleLoops + " packets"); |
487 | |||
488 | e = new List<ThrottleOutPacketType>(Empty); | ||
489 | Empty.Clear(); | ||
490 | } | 490 | } |
491 | |||
492 | foreach (ThrottleOutPacketType t in e) | ||
493 | { | ||
494 | if (GetQueueCount(t) == 0) | ||
495 | TriggerOnQueueEmpty(t); | ||
496 | } | ||
497 | } | ||
498 | |||
499 | private void TriggerOnQueueEmpty(ThrottleOutPacketType queue) | ||
500 | { | ||
501 | QueueEmpty handlerQueueEmpty = OnQueueEmpty; | ||
502 | |||
503 | if (handlerQueueEmpty != null) | ||
504 | handlerQueueEmpty(queue); | ||
491 | } | 505 | } |
492 | 506 | ||
493 | private void ThrottleTimerElapsed(object sender, ElapsedEventArgs e) | 507 | private void ThrottleTimerElapsed(object sender, ElapsedEventArgs e) |
@@ -497,7 +511,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
497 | ProcessThrottle(); | 511 | ProcessThrottle(); |
498 | } | 512 | } |
499 | 513 | ||
500 | private void ThrottleCheck(ref LLPacketThrottle throttle, ref Queue<LLQueItem> q, LLQueItem item) | 514 | private void ThrottleCheck(ref LLPacketThrottle throttle, ref Queue<LLQueItem> q, LLQueItem item, ThrottleOutPacketType itemType) |
501 | { | 515 | { |
502 | // The idea.. is if the packet throttle queues are empty | 516 | // The idea.. is if the packet throttle queues are empty |
503 | // and the client is under throttle for the type. Queue | 517 | // and the client is under throttle for the type. Queue |
@@ -513,6 +527,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
513 | throttle.AddBytes(item.Length); | 527 | throttle.AddBytes(item.Length); |
514 | TotalThrottle.AddBytes(item.Length); | 528 | TotalThrottle.AddBytes(item.Length); |
515 | SendQueue.Enqueue(item); | 529 | SendQueue.Enqueue(item); |
530 | lock (this) | ||
531 | { | ||
532 | if (!Empty.Contains(itemType)) | ||
533 | Empty.Add(itemType); | ||
534 | } | ||
516 | } | 535 | } |
517 | catch (Exception e) | 536 | catch (Exception e) |
518 | { | 537 | { |
@@ -698,5 +717,26 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
698 | { | 717 | { |
699 | get { return throttleMultiplier; } | 718 | get { return throttleMultiplier; } |
700 | } | 719 | } |
720 | |||
721 | public int GetQueueCount(ThrottleOutPacketType queue) | ||
722 | { | ||
723 | switch (queue) | ||
724 | { | ||
725 | case ThrottleOutPacketType.Land: | ||
726 | return LandOutgoingPacketQueue.Count; | ||
727 | case ThrottleOutPacketType.Wind: | ||
728 | return WindOutgoingPacketQueue.Count; | ||
729 | case ThrottleOutPacketType.Cloud: | ||
730 | return CloudOutgoingPacketQueue.Count; | ||
731 | case ThrottleOutPacketType.Task: | ||
732 | return TaskOutgoingPacketQueue.Count; | ||
733 | case ThrottleOutPacketType.Texture: | ||
734 | return TextureOutgoingPacketQueue.Count; | ||
735 | case ThrottleOutPacketType.Asset: | ||
736 | return AssetOutgoingPacketQueue.Count; | ||
737 | } | ||
738 | |||
739 | return 0; | ||
740 | } | ||
701 | } | 741 | } |
702 | } | 742 | } |
diff --git a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs index 1fdb003..a0f359b 100644 --- a/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs +++ b/OpenSim/Region/CoreModules/Agent/TextureSender/J2KDecoderModule.cs | |||
@@ -34,8 +34,8 @@ using System.Threading; | |||
34 | using log4net; | 34 | using log4net; |
35 | using Nini.Config; | 35 | using Nini.Config; |
36 | using OpenMetaverse; | 36 | using OpenMetaverse; |
37 | using OpenMetaverse.Assets; | ||
38 | using OpenMetaverse.Imaging; | 37 | using OpenMetaverse.Imaging; |
38 | using CSJ2K; | ||
39 | using OpenSim.Framework; | 39 | using OpenSim.Framework; |
40 | using OpenSim.Region.Framework.Interfaces; | 40 | using OpenSim.Region.Framework.Interfaces; |
41 | using OpenSim.Region.Framework.Scenes; | 41 | using OpenSim.Region.Framework.Scenes; |
@@ -43,31 +43,25 @@ using OpenSim.Services.Interfaces; | |||
43 | 43 | ||
44 | namespace OpenSim.Region.CoreModules.Agent.TextureSender | 44 | namespace OpenSim.Region.CoreModules.Agent.TextureSender |
45 | { | 45 | { |
46 | public delegate void J2KDecodeDelegate(UUID AssetId); | 46 | public delegate void J2KDecodeDelegate(UUID assetID); |
47 | 47 | ||
48 | public class J2KDecoderModule : IRegionModule, IJ2KDecoder | 48 | public class J2KDecoderModule : IRegionModule, IJ2KDecoder |
49 | { | 49 | { |
50 | #region IRegionModule Members | 50 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
51 | 51 | ||
52 | private static readonly ILog m_log | 52 | /// <summary>Temporarily holds deserialized layer data information in memory</summary> |
53 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 53 | private readonly ExpiringCache<UUID, OpenJPEG.J2KLayerInfo[]> m_decodedCache = new ExpiringCache<UUID,OpenJPEG.J2KLayerInfo[]>(); |
54 | /// <summary>List of client methods to notify of results of decode</summary> | ||
55 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); | ||
56 | /// <summary>Cache that will store decoded JPEG2000 layer boundary data</summary> | ||
57 | private IImprovedAssetCache m_cache; | ||
58 | /// <summary>Reference to a scene (doesn't matter which one as long as it can load the cache module)</summary> | ||
59 | private Scene m_scene; | ||
54 | 60 | ||
55 | /// <summary> | 61 | #region IRegionModule |
56 | /// Cached Decoded Layers | ||
57 | /// </summary> | ||
58 | private readonly Dictionary<UUID, OpenJPEG.J2KLayerInfo[]> m_cacheddecode = new Dictionary<UUID, OpenJPEG.J2KLayerInfo[]>(); | ||
59 | private bool OpenJpegFail = false; | ||
60 | private string CacheFolder = Util.dataDir() + "/j2kDecodeCache"; | ||
61 | private int CacheTimeout = 720; | ||
62 | private J2KDecodeFileCache fCache = null; | ||
63 | private Thread CleanerThread = null; | ||
64 | private IAssetService AssetService = null; | ||
65 | private Scene m_Scene = null; | ||
66 | 62 | ||
67 | /// <summary> | 63 | public string Name { get { return "J2KDecoderModule"; } } |
68 | /// List of client methods to notify of results of decode | 64 | public bool IsSharedModule { get { return true; } } |
69 | /// </summary> | ||
70 | private readonly Dictionary<UUID, List<DecodedCallback>> m_notifyList = new Dictionary<UUID, List<DecodedCallback>>(); | ||
71 | 65 | ||
72 | public J2KDecoderModule() | 66 | public J2KDecoderModule() |
73 | { | 67 | { |
@@ -75,630 +69,267 @@ namespace OpenSim.Region.CoreModules.Agent.TextureSender | |||
75 | 69 | ||
76 | public void Initialise(Scene scene, IConfigSource source) | 70 | public void Initialise(Scene scene, IConfigSource source) |
77 | { | 71 | { |
78 | if (m_Scene == null) | 72 | if (m_scene == null) |
79 | m_Scene = scene; | 73 | m_scene = scene; |
80 | |||
81 | IConfig j2kConfig = source.Configs["J2KDecoder"]; | ||
82 | if (j2kConfig != null) | ||
83 | { | ||
84 | CacheFolder = j2kConfig.GetString("CacheDir", CacheFolder); | ||
85 | CacheTimeout = j2kConfig.GetInt("CacheTimeout", CacheTimeout); | ||
86 | } | ||
87 | |||
88 | if (fCache == null) | ||
89 | fCache = new J2KDecodeFileCache(CacheFolder, CacheTimeout); | ||
90 | 74 | ||
91 | scene.RegisterModuleInterface<IJ2KDecoder>(this); | 75 | scene.RegisterModuleInterface<IJ2KDecoder>(this); |
92 | |||
93 | if (CleanerThread == null && CacheTimeout != 0) | ||
94 | { | ||
95 | CleanerThread = new Thread(CleanCache); | ||
96 | CleanerThread.Name = "J2KCleanerThread"; | ||
97 | CleanerThread.IsBackground = true; | ||
98 | CleanerThread.Start(); | ||
99 | } | ||
100 | } | 76 | } |
101 | 77 | ||
102 | public void PostInitialise() | 78 | public void PostInitialise() |
103 | { | 79 | { |
104 | AssetService = m_Scene.AssetService; | 80 | m_cache = m_scene.RequestModuleInterface<IImprovedAssetCache>(); |
105 | } | 81 | } |
106 | 82 | ||
107 | public void Close() | 83 | public void Close() |
108 | { | 84 | { |
109 | |||
110 | } | ||
111 | |||
112 | public string Name | ||
113 | { | ||
114 | get { return "J2KDecoderModule"; } | ||
115 | } | ||
116 | |||
117 | public bool IsSharedModule | ||
118 | { | ||
119 | get { return true; } | ||
120 | } | 85 | } |
121 | 86 | ||
122 | #endregion | 87 | #endregion IRegionModule |
123 | |||
124 | #region IJ2KDecoder Members | ||
125 | 88 | ||
89 | #region IJ2KDecoder | ||
126 | 90 | ||
127 | public void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn) | 91 | public void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback) |
128 | { | 92 | { |
129 | // Dummy for if decoding fails. | 93 | OpenJPEG.J2KLayerInfo[] result; |
130 | OpenJPEG.J2KLayerInfo[] result = new OpenJPEG.J2KLayerInfo[0]; | ||
131 | |||
132 | // Check if it's cached | ||
133 | bool cached = false; | ||
134 | lock (m_cacheddecode) | ||
135 | { | ||
136 | if (m_cacheddecode.ContainsKey(AssetId)) | ||
137 | { | ||
138 | cached = true; | ||
139 | result = m_cacheddecode[AssetId]; | ||
140 | } | ||
141 | } | ||
142 | 94 | ||
143 | // If it's cached, return the cached results | 95 | // If it's cached, return the cached results |
144 | if (cached) | 96 | if (m_decodedCache.TryGetValue(assetID, out result)) |
145 | { | 97 | { |
146 | decodedReturn(AssetId, result); | 98 | callback(assetID, result); |
147 | } | 99 | } |
148 | else | 100 | else |
149 | { | 101 | { |
150 | // not cached, so we need to decode it | 102 | // Not cached, we need to decode it. |
151 | // Add to notify list and start decoding. | 103 | // Add to notify list and start decoding. |
152 | // Next request for this asset while it's decoding will only be added to the notify list | 104 | // Next request for this asset while it's decoding will only be added to the notify list |
153 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated | 105 | // once this is decoded, requests will be served from the cache and all clients in the notifylist will be updated |
154 | bool decode = false; | 106 | bool decode = false; |
155 | lock (m_notifyList) | 107 | lock (m_notifyList) |
156 | { | 108 | { |
157 | if (m_notifyList.ContainsKey(AssetId)) | 109 | if (m_notifyList.ContainsKey(assetID)) |
158 | { | 110 | { |
159 | m_notifyList[AssetId].Add(decodedReturn); | 111 | m_notifyList[assetID].Add(callback); |
160 | } | 112 | } |
161 | else | 113 | else |
162 | { | 114 | { |
163 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); | 115 | List<DecodedCallback> notifylist = new List<DecodedCallback>(); |
164 | notifylist.Add(decodedReturn); | 116 | notifylist.Add(callback); |
165 | m_notifyList.Add(AssetId, notifylist); | 117 | m_notifyList.Add(assetID, notifylist); |
166 | decode = true; | 118 | decode = true; |
167 | } | 119 | } |
168 | } | 120 | } |
121 | |||
169 | // Do Decode! | 122 | // Do Decode! |
170 | if (decode) | 123 | if (decode) |
171 | { | 124 | DoJ2KDecode(assetID, j2kData); |
172 | doJ2kDecode(AssetId, assetData); | ||
173 | } | ||
174 | } | 125 | } |
175 | } | 126 | } |
176 | 127 | ||
177 | /// <summary> | 128 | /// <summary> |
178 | /// Provides a synchronous decode so that caller can be assured that this executes before the next line | 129 | /// Provides a synchronous decode so that caller can be assured that this executes before the next line |
179 | /// </summary> | 130 | /// </summary> |
180 | /// <param name="AssetId"></param> | 131 | /// <param name="assetID"></param> |
181 | /// <param name="j2kdata"></param> | 132 | /// <param name="j2kData"></param> |
182 | public void syncdecode(UUID AssetId, byte[] j2kdata) | 133 | public void Decode(UUID assetID, byte[] j2kData) |
183 | { | 134 | { |
184 | doJ2kDecode(AssetId, j2kdata); | 135 | DoJ2KDecode(assetID, j2kData); |
185 | } | 136 | } |
186 | 137 | ||
187 | #endregion | 138 | #endregion IJ2KDecoder |
188 | 139 | ||
189 | /// <summary> | 140 | /// <summary> |
190 | /// Decode Jpeg2000 Asset Data | 141 | /// Decode Jpeg2000 Asset Data |
191 | /// </summary> | 142 | /// </summary> |
192 | /// <param name="AssetId">UUID of Asset</param> | 143 | /// <param name="assetID">UUID of Asset</param> |
193 | /// <param name="j2kdata">Byte Array Asset Data </param> | 144 | /// <param name="j2kData">JPEG2000 data</param> |
194 | private void doJ2kDecode(UUID AssetId, byte[] j2kdata) | 145 | private void DoJ2KDecode(UUID assetID, byte[] j2kData) |
195 | { | 146 | { |
196 | int DecodeTime = 0; | 147 | int DecodeTime = 0; |
197 | DecodeTime = Environment.TickCount; | 148 | DecodeTime = Environment.TickCount; |
198 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[0]; // Dummy result for if it fails. Informs that there's only full quality | 149 | OpenJPEG.J2KLayerInfo[] layers; |
199 | 150 | ||
200 | if (!OpenJpegFail) | 151 | if (!TryLoadCacheForAsset(assetID, out layers)) |
201 | { | 152 | { |
202 | if (!fCache.TryLoadCacheForAsset(AssetId, out layers)) | 153 | try |
203 | { | 154 | { |
204 | try | 155 | List<int> layerStarts = CSJ2K.J2kImage.GetLayerBoundaries(new MemoryStream(j2kData)); |
156 | |||
157 | if (layerStarts != null && layerStarts.Count > 0) | ||
205 | { | 158 | { |
159 | layers = new OpenJPEG.J2KLayerInfo[layerStarts.Count]; | ||
206 | 160 | ||
207 | AssetTexture texture = new AssetTexture(AssetId, j2kdata); | 161 | for (int i = 0; i < layerStarts.Count; i++) |
208 | if (texture.DecodeLayerBoundaries()) | ||
209 | { | 162 | { |
210 | bool sane = true; | 163 | OpenJPEG.J2KLayerInfo layer = new OpenJPEG.J2KLayerInfo(); |
211 | 164 | ||
212 | // Sanity check all of the layers | 165 | if (i == 0) |
213 | for (int i = 0; i < texture.LayerInfo.Length; i++) | 166 | layer.Start = 0; |
214 | { | ||
215 | if (texture.LayerInfo[i].End > texture.AssetData.Length) | ||
216 | { | ||
217 | sane = false; | ||
218 | break; | ||
219 | } | ||
220 | } | ||
221 | |||
222 | if (sane) | ||
223 | { | ||
224 | layers = texture.LayerInfo; | ||
225 | fCache.SaveFileCacheForAsset(AssetId, layers); | ||
226 | |||
227 | |||
228 | // Write out decode time | ||
229 | m_log.InfoFormat("[J2KDecoderModule]: {0} Decode Time: {1}", Environment.TickCount - DecodeTime, | ||
230 | AssetId); | ||
231 | |||
232 | } | ||
233 | else | 167 | else |
234 | { | 168 | layer.Start = layerStarts[i]; |
235 | m_log.WarnFormat( | ||
236 | "[J2KDecoderModule]: JPEG2000 texture decoding succeeded, but sanity check failed for {0}", | ||
237 | AssetId); | ||
238 | } | ||
239 | } | ||
240 | |||
241 | else | ||
242 | { | ||
243 | /* | ||
244 | Random rnd = new Random(); | ||
245 | // scramble ends for test | ||
246 | for (int i = 0; i < texture.LayerInfo.Length; i++) | ||
247 | { | ||
248 | texture.LayerInfo[i].End = rnd.Next(999999); | ||
249 | } | ||
250 | */ | ||
251 | |||
252 | // Try to do some heuristics error correction! Yeah. | ||
253 | bool sane2Heuristics = true; | ||
254 | |||
255 | |||
256 | if (texture.Image == null) | ||
257 | sane2Heuristics = false; | ||
258 | |||
259 | if (texture.LayerInfo == null) | ||
260 | sane2Heuristics = false; | ||
261 | |||
262 | if (sane2Heuristics) | ||
263 | { | ||
264 | |||
265 | 169 | ||
266 | if (texture.LayerInfo.Length == 0) | 170 | if (i == layerStarts.Count - 1) |
267 | sane2Heuristics = false; | 171 | layer.End = j2kData.Length; |
268 | } | ||
269 | |||
270 | if (sane2Heuristics) | ||
271 | { | ||
272 | // Last layer start is less then the end of the file and last layer start is greater then 0 | ||
273 | if (texture.LayerInfo[texture.LayerInfo.Length - 1].Start < texture.AssetData.Length && texture.LayerInfo[texture.LayerInfo.Length - 1].Start > 0) | ||
274 | { | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | sane2Heuristics = false; | ||
279 | } | ||
280 | |||
281 | } | ||
282 | |||
283 | if (sane2Heuristics) | ||
284 | { | ||
285 | int start = 0; | ||
286 | |||
287 | // try to fix it by using consistant data in the start field | ||
288 | for (int i = 0; i < texture.LayerInfo.Length; i++) | ||
289 | { | ||
290 | if (i == 0) | ||
291 | start = 0; | ||
292 | |||
293 | if (i == texture.LayerInfo.Length - 1) | ||
294 | texture.LayerInfo[i].End = texture.AssetData.Length; | ||
295 | else | ||
296 | texture.LayerInfo[i].End = texture.LayerInfo[i + 1].Start - 1; | ||
297 | |||
298 | // in this case, the end of the next packet is less then the start of the last packet | ||
299 | // after we've attempted to fix it which means the start of the last packet is borked | ||
300 | // there's no recovery from this | ||
301 | if (texture.LayerInfo[i].End < start) | ||
302 | { | ||
303 | sane2Heuristics = false; | ||
304 | break; | ||
305 | } | ||
306 | |||
307 | if (texture.LayerInfo[i].End < 0 || texture.LayerInfo[i].End > texture.AssetData.Length) | ||
308 | { | ||
309 | sane2Heuristics = false; | ||
310 | break; | ||
311 | } | ||
312 | |||
313 | if (texture.LayerInfo[i].Start < 0 || texture.LayerInfo[i].Start > texture.AssetData.Length) | ||
314 | { | ||
315 | sane2Heuristics = false; | ||
316 | break; | ||
317 | } | ||
318 | |||
319 | start = texture.LayerInfo[i].Start; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | if (sane2Heuristics) | ||
324 | { | ||
325 | layers = texture.LayerInfo; | ||
326 | fCache.SaveFileCacheForAsset(AssetId, layers); | ||
327 | |||
328 | |||
329 | // Write out decode time | ||
330 | m_log.InfoFormat("[J2KDecoderModule]: HEURISTICS SUCCEEDED {0} Decode Time: {1}", Environment.TickCount - DecodeTime, | ||
331 | AssetId); | ||
332 | |||
333 | } | ||
334 | else | 172 | else |
335 | { | 173 | layer.End = layerStarts[i + 1] - 1; |
336 | m_log.WarnFormat("[J2KDecoderModule]: JPEG2000 texture decoding failed for {0}. Is this a texture? is it J2K?", AssetId); | 174 | |
337 | } | 175 | layers[i] = layer; |
338 | } | 176 | } |
339 | texture = null; // dereference and dispose of ManagedImage | ||
340 | } | ||
341 | catch (DllNotFoundException) | ||
342 | { | ||
343 | m_log.Error( | ||
344 | "[J2KDecoderModule]: OpenJpeg is not installed properly. Decoding disabled! This will slow down texture performance! Often times this is because of an old version of GLIBC. You must have version 2.4 or above!"); | ||
345 | OpenJpegFail = true; | ||
346 | } | ||
347 | catch (Exception ex) | ||
348 | { | ||
349 | m_log.WarnFormat( | ||
350 | "[J2KDecoderModule]: JPEG2000 texture decoding threw an exception for {0}, {1}", | ||
351 | AssetId, ex); | ||
352 | } | 177 | } |
353 | } | 178 | } |
354 | 179 | catch (Exception ex) | |
355 | } | 180 | { |
181 | m_log.Warn("[J2KDecoderModule]: CSJ2K threw an exception decoding texture " + assetID + ": " + ex.Message); | ||
182 | } | ||
356 | 183 | ||
357 | // Cache Decoded layers | 184 | if (layers == null || layers.Length == 0) |
358 | lock (m_cacheddecode) | 185 | { |
359 | { | 186 | m_log.Warn("[J2KDecoderModule]: Failed to decode layer data for texture " + assetID + ", guessing sane defaults"); |
360 | if (m_cacheddecode.ContainsKey(AssetId)) | 187 | // Layer decoding completely failed. Guess at sane defaults for the layer boundaries |
361 | m_cacheddecode.Remove(AssetId); | 188 | layers = CreateDefaultLayers(j2kData.Length); |
362 | m_cacheddecode.Add(AssetId, layers); | 189 | } |
363 | 190 | ||
191 | // Cache Decoded layers | ||
192 | SaveFileCacheForAsset(assetID, layers); | ||
364 | } | 193 | } |
365 | 194 | ||
366 | // Notify Interested Parties | 195 | // Notify Interested Parties |
367 | lock (m_notifyList) | 196 | lock (m_notifyList) |
368 | { | 197 | { |
369 | if (m_notifyList.ContainsKey(AssetId)) | 198 | if (m_notifyList.ContainsKey(assetID)) |
370 | { | 199 | { |
371 | foreach (DecodedCallback d in m_notifyList[AssetId]) | 200 | foreach (DecodedCallback d in m_notifyList[assetID]) |
372 | { | 201 | { |
373 | if (d != null) | 202 | if (d != null) |
374 | d.DynamicInvoke(AssetId, layers); | 203 | d.DynamicInvoke(assetID, layers); |
375 | } | 204 | } |
376 | m_notifyList.Remove(AssetId); | 205 | m_notifyList.Remove(assetID); |
377 | } | 206 | } |
378 | } | 207 | } |
379 | } | 208 | } |
380 | |||
381 | private void CleanCache() | ||
382 | { | ||
383 | m_log.Info("[J2KDecoderModule]: Cleaner thread started"); | ||
384 | |||
385 | while (true) | ||
386 | { | ||
387 | if (AssetService != null) | ||
388 | fCache.ScanCacheFiles(RedecodeTexture); | ||
389 | 209 | ||
390 | System.Threading.Thread.Sleep(600000); | 210 | private OpenJPEG.J2KLayerInfo[] CreateDefaultLayers(int j2kLength) |
391 | } | ||
392 | } | ||
393 | |||
394 | private void RedecodeTexture(UUID assetID) | ||
395 | { | 211 | { |
396 | AssetBase texture = AssetService.Get(assetID.ToString()); | 212 | OpenJPEG.J2KLayerInfo[] layers = new OpenJPEG.J2KLayerInfo[5]; |
397 | if (texture == null) | 213 | |
398 | return; | 214 | for (int i = 0; i < layers.Length; i++) |
399 | 215 | layers[i] = new OpenJPEG.J2KLayerInfo(); | |
400 | doJ2kDecode(assetID, texture.Data); | 216 | |
217 | // These default layer sizes are based on a small sampling of real-world texture data | ||
218 | // with extra padding thrown in for good measure. This is a worst case fallback plan | ||
219 | // and may not gracefully handle all real world data | ||
220 | layers[0].Start = 0; | ||
221 | layers[1].Start = (int)((float)j2kLength * 0.02f); | ||
222 | layers[2].Start = (int)((float)j2kLength * 0.05f); | ||
223 | layers[3].Start = (int)((float)j2kLength * 0.20f); | ||
224 | layers[4].Start = (int)((float)j2kLength * 0.50f); | ||
225 | |||
226 | layers[0].End = layers[1].Start - 1; | ||
227 | layers[1].End = layers[2].Start - 1; | ||
228 | layers[2].End = layers[3].Start - 1; | ||
229 | layers[3].End = layers[4].Start - 1; | ||
230 | layers[4].End = j2kLength; | ||
231 | |||
232 | return layers; | ||
401 | } | 233 | } |
402 | } | ||
403 | 234 | ||
404 | public class J2KDecodeFileCache | 235 | private void SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) |
405 | { | ||
406 | private readonly string m_cacheDecodeFolder; | ||
407 | private readonly int m_cacheTimeout; | ||
408 | private bool enabled = true; | ||
409 | |||
410 | private static readonly ILog m_log | ||
411 | = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
412 | |||
413 | /// <summary> | ||
414 | /// Creates a new instance of a file cache | ||
415 | /// </summary> | ||
416 | /// <param name="pFolder">base folder for the cache. Will be created if it doesn't exist</param> | ||
417 | public J2KDecodeFileCache(string pFolder, int timeout) | ||
418 | { | 236 | { |
419 | m_cacheDecodeFolder = pFolder; | 237 | m_decodedCache.AddOrUpdate(AssetId, Layers, TimeSpan.FromMinutes(10)); |
420 | m_cacheTimeout = timeout; | ||
421 | if (!Directory.Exists(pFolder)) | ||
422 | { | ||
423 | Createj2KCacheFolder(pFolder); | ||
424 | } | ||
425 | } | ||
426 | 238 | ||
427 | /// <summary> | 239 | if (m_cache != null) |
428 | /// Save Layers to Disk Cache | ||
429 | /// </summary> | ||
430 | /// <param name="AssetId">Asset to Save the layers. Used int he file name by default</param> | ||
431 | /// <param name="Layers">The Layer Data from OpenJpeg</param> | ||
432 | /// <returns></returns> | ||
433 | public bool SaveFileCacheForAsset(UUID AssetId, OpenJPEG.J2KLayerInfo[] Layers) | ||
434 | { | ||
435 | if (Layers.Length > 0 && enabled) | ||
436 | { | 240 | { |
437 | FileStream fsCache = | 241 | AssetBase layerDecodeAsset = new AssetBase(); |
438 | new FileStream(String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)), | 242 | layerDecodeAsset.ID = "j2kCache_" + AssetId.ToString(); |
439 | FileMode.Create); | 243 | layerDecodeAsset.Local = true; |
440 | StreamWriter fsSWCache = new StreamWriter(fsCache); | 244 | layerDecodeAsset.Name = layerDecodeAsset.ID; |
245 | layerDecodeAsset.Temporary = true; | ||
246 | layerDecodeAsset.Type = (sbyte)AssetType.Notecard; | ||
247 | |||
248 | #region Serialize Layer Data | ||
249 | |||
441 | StringBuilder stringResult = new StringBuilder(); | 250 | StringBuilder stringResult = new StringBuilder(); |
442 | string strEnd = "\n"; | 251 | string strEnd = "\n"; |
443 | for (int i = 0; i < Layers.Length; i++) | 252 | for (int i = 0; i < Layers.Length; i++) |
444 | { | 253 | { |
445 | if (i == (Layers.Length - 1)) | 254 | if (i == Layers.Length - 1) |
446 | strEnd = ""; | 255 | strEnd = String.Empty; |
447 | 256 | ||
448 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); | 257 | stringResult.AppendFormat("{0}|{1}|{2}{3}", Layers[i].Start, Layers[i].End, Layers[i].End - Layers[i].Start, strEnd); |
449 | } | 258 | } |
450 | fsSWCache.Write(stringResult.ToString()); | ||
451 | fsSWCache.Close(); | ||
452 | fsSWCache.Dispose(); | ||
453 | fsCache.Dispose(); | ||
454 | return true; | ||
455 | } | ||
456 | |||
457 | 259 | ||
458 | return false; | 260 | layerDecodeAsset.Data = Encoding.UTF8.GetBytes(stringResult.ToString()); |
459 | } | ||
460 | 261 | ||
461 | 262 | #endregion Serialize Layer Data | |
462 | /// <summary> | ||
463 | /// Loads the Layer data from the disk cache | ||
464 | /// Returns true if load succeeded | ||
465 | /// </summary> | ||
466 | /// <param name="AssetId">AssetId that we're checking the cache for</param> | ||
467 | /// <param name="Layers">out layers to save to</param> | ||
468 | /// <returns>true if load succeeded</returns> | ||
469 | public bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) | ||
470 | { | ||
471 | string filename = String.Format("{0}/{1}", m_cacheDecodeFolder, FileNameFromAssetId(AssetId)); | ||
472 | Layers = new OpenJPEG.J2KLayerInfo[0]; | ||
473 | 263 | ||
474 | if (!File.Exists(filename)) | 264 | m_cache.Cache(layerDecodeAsset); |
475 | return false; | ||
476 | |||
477 | if (!enabled) | ||
478 | { | ||
479 | return false; | ||
480 | } | 265 | } |
266 | } | ||
481 | 267 | ||
482 | string readResult = string.Empty; | 268 | bool TryLoadCacheForAsset(UUID AssetId, out OpenJPEG.J2KLayerInfo[] Layers) |
483 | 269 | { | |
484 | try | 270 | if (m_decodedCache.TryGetValue(AssetId, out Layers)) |
485 | { | 271 | { |
486 | FileStream fsCachefile = | 272 | return true; |
487 | new FileStream(filename, | ||
488 | FileMode.Open); | ||
489 | |||
490 | StreamReader sr = new StreamReader(fsCachefile); | ||
491 | readResult = sr.ReadToEnd(); | ||
492 | |||
493 | sr.Close(); | ||
494 | sr.Dispose(); | ||
495 | fsCachefile.Dispose(); | ||
496 | |||
497 | } | 273 | } |
498 | catch (IOException ioe) | 274 | else if (m_cache != null) |
499 | { | 275 | { |
500 | if (ioe is PathTooLongException) | 276 | string assetName = "j2kCache_" + AssetId.ToString(); |
501 | { | 277 | AssetBase layerDecodeAsset = m_cache.Get(assetName); |
502 | m_log.Error( | ||
503 | "[J2KDecodeCache]: Cache Read failed. Path is too long."); | ||
504 | } | ||
505 | else if (ioe is DirectoryNotFoundException) | ||
506 | { | ||
507 | m_log.Error( | ||
508 | "[J2KDecodeCache]: Cache Read failed. Cache Directory does not exist!"); | ||
509 | enabled = false; | ||
510 | } | ||
511 | else | ||
512 | { | ||
513 | m_log.Error( | ||
514 | "[J2KDecodeCache]: Cache Read failed. IO Exception."); | ||
515 | } | ||
516 | return false; | ||
517 | 278 | ||
518 | } | 279 | if (layerDecodeAsset != null) |
519 | catch (UnauthorizedAccessException) | ||
520 | { | ||
521 | m_log.Error( | ||
522 | "[J2KDecodeCache]: Cache Read failed. UnauthorizedAccessException Exception. Do you have the proper permissions on this file?"); | ||
523 | return false; | ||
524 | } | ||
525 | catch (ArgumentException ae) | ||
526 | { | ||
527 | if (ae is ArgumentNullException) | ||
528 | { | ||
529 | m_log.Error( | ||
530 | "[J2KDecodeCache]: Cache Read failed. No Filename provided"); | ||
531 | } | ||
532 | else | ||
533 | { | 280 | { |
534 | m_log.Error( | 281 | #region Deserialize Layer Data |
535 | "[J2KDecodeCache]: Cache Read failed. Filname was invalid"); | ||
536 | } | ||
537 | return false; | ||
538 | } | ||
539 | catch (NotSupportedException) | ||
540 | { | ||
541 | m_log.Error( | ||
542 | "[J2KDecodeCache]: Cache Read failed, not supported. Cache disabled!"); | ||
543 | enabled = false; | ||
544 | 282 | ||
545 | return false; | 283 | string readResult = Encoding.UTF8.GetString(layerDecodeAsset.Data); |
546 | } | 284 | string[] lines = readResult.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); |
547 | catch (Exception e) | ||
548 | { | ||
549 | m_log.ErrorFormat( | ||
550 | "[J2KDecodeCache]: Cache Read failed, unknown exception. Error: {0}", | ||
551 | e.ToString()); | ||
552 | return false; | ||
553 | } | ||
554 | |||
555 | string[] lines = readResult.Split('\n'); | ||
556 | 285 | ||
557 | if (lines.Length <= 0) | 286 | if (lines.Length == 0) |
558 | return false; | ||
559 | |||
560 | Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; | ||
561 | |||
562 | for (int i = 0; i < lines.Length; i++) | ||
563 | { | ||
564 | string[] elements = lines[i].Split('|'); | ||
565 | if (elements.Length == 3) | ||
566 | { | ||
567 | int element1, element2; | ||
568 | |||
569 | try | ||
570 | { | ||
571 | element1 = Convert.ToInt32(elements[0]); | ||
572 | element2 = Convert.ToInt32(elements[1]); | ||
573 | } | ||
574 | catch (FormatException) | ||
575 | { | 287 | { |
576 | m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed with ErrorConvert for {0}", AssetId); | 288 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (empty) " + assetName); |
577 | Layers = new OpenJPEG.J2KLayerInfo[0]; | 289 | m_cache.Expire(assetName); |
578 | return false; | 290 | return false; |
579 | } | 291 | } |
580 | 292 | ||
581 | Layers[i] = new OpenJPEG.J2KLayerInfo(); | 293 | Layers = new OpenJPEG.J2KLayerInfo[lines.Length]; |
582 | Layers[i].Start = element1; | ||
583 | Layers[i].End = element2; | ||
584 | |||
585 | } | ||
586 | else | ||
587 | { | ||
588 | // reading failed | ||
589 | m_log.WarnFormat("[J2KDecodeCache]: Cache Read failed for {0}", AssetId); | ||
590 | Layers = new OpenJPEG.J2KLayerInfo[0]; | ||
591 | return false; | ||
592 | } | ||
593 | } | ||
594 | |||
595 | |||
596 | |||
597 | |||
598 | return true; | ||
599 | } | ||
600 | 294 | ||
601 | /// <summary> | 295 | for (int i = 0; i < lines.Length; i++) |
602 | /// Routine which converts assetid to file name | 296 | { |
603 | /// </summary> | 297 | string[] elements = lines[i].Split('|'); |
604 | /// <param name="AssetId">asset id of the image</param> | 298 | if (elements.Length == 3) |
605 | /// <returns>string filename</returns> | 299 | { |
606 | public string FileNameFromAssetId(UUID AssetId) | 300 | int element1, element2; |
607 | { | ||
608 | return String.Format("j2kCache_{0}.cache", AssetId); | ||
609 | } | ||
610 | 301 | ||
611 | public UUID AssetIdFromFileName(string fileName) | 302 | try |
612 | { | 303 | { |
613 | string rawId = fileName.Replace("j2kCache_", "").Replace(".cache", ""); | 304 | element1 = Convert.ToInt32(elements[0]); |
614 | UUID asset; | 305 | element2 = Convert.ToInt32(elements[1]); |
615 | if (!UUID.TryParse(rawId, out asset)) | 306 | } |
616 | return UUID.Zero; | 307 | catch (FormatException) |
308 | { | ||
309 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (format) " + assetName); | ||
310 | m_cache.Expire(assetName); | ||
311 | return false; | ||
312 | } | ||
617 | 313 | ||
618 | return asset; | 314 | Layers[i] = new OpenJPEG.J2KLayerInfo(); |
619 | } | 315 | Layers[i].Start = element1; |
316 | Layers[i].End = element2; | ||
317 | } | ||
318 | else | ||
319 | { | ||
320 | m_log.Warn("[J2KDecodeCache]: Expiring corrupted layer data (layout) " + assetName); | ||
321 | m_cache.Expire(assetName); | ||
322 | return false; | ||
323 | } | ||
324 | } | ||
620 | 325 | ||
621 | /// <summary> | 326 | #endregion Deserialize Layer Data |
622 | /// Creates the Cache Folder | ||
623 | /// </summary> | ||
624 | /// <param name="pFolder">Folder to Create</param> | ||
625 | public void Createj2KCacheFolder(string pFolder) | ||
626 | { | ||
627 | try | ||
628 | { | ||
629 | Directory.CreateDirectory(pFolder); | ||
630 | } | ||
631 | catch (IOException ioe) | ||
632 | { | ||
633 | if (ioe is PathTooLongException) | ||
634 | { | ||
635 | m_log.Error( | ||
636 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because the path to the cache folder is too long. Cache disabled!"); | ||
637 | } | ||
638 | else if (ioe is DirectoryNotFoundException) | ||
639 | { | ||
640 | m_log.Error( | ||
641 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because the supplied base of the directory folder does not exist. Cache disabled!"); | ||
642 | } | ||
643 | else | ||
644 | { | ||
645 | m_log.Error( | ||
646 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an IO Exception. Cache disabled!"); | ||
647 | } | ||
648 | enabled = false; | ||
649 | 327 | ||
650 | } | 328 | return true; |
651 | catch (UnauthorizedAccessException) | ||
652 | { | ||
653 | m_log.Error( | ||
654 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an UnauthorizedAccessException Exception. Cache disabled!"); | ||
655 | enabled = false; | ||
656 | } | ||
657 | catch (ArgumentException ae) | ||
658 | { | ||
659 | if (ae is ArgumentNullException) | ||
660 | { | ||
661 | m_log.Error( | ||
662 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because the folder provided is invalid! Cache disabled!"); | ||
663 | } | 329 | } |
664 | else | ||
665 | { | ||
666 | m_log.Error( | ||
667 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because no cache folder was provided! Cache disabled!"); | ||
668 | } | ||
669 | enabled = false; | ||
670 | } | 330 | } |
671 | catch (NotSupportedException) | ||
672 | { | ||
673 | m_log.Error( | ||
674 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because it's not supported. Cache disabled!"); | ||
675 | enabled = false; | ||
676 | } | ||
677 | catch (Exception e) | ||
678 | { | ||
679 | m_log.ErrorFormat( | ||
680 | "[J2KDecodeCache]: Cache Directory does not exist and create failed because of an unknown exception. Cache disabled! Error: {0}", | ||
681 | e.ToString()); | ||
682 | enabled = false; | ||
683 | } | ||
684 | } | ||
685 | |||
686 | public void ScanCacheFiles(J2KDecodeDelegate decode) | ||
687 | { | ||
688 | DirectoryInfo dir = new DirectoryInfo(m_cacheDecodeFolder); | ||
689 | FileInfo[] files = dir.GetFiles("j2kCache_*.cache"); | ||
690 | 331 | ||
691 | foreach (FileInfo f in files) | 332 | return false; |
692 | { | ||
693 | TimeSpan fileAge = DateTime.Now - f.CreationTime; | ||
694 | |||
695 | if (m_cacheTimeout != 0 && fileAge >= TimeSpan.FromMinutes(m_cacheTimeout)) | ||
696 | { | ||
697 | File.Delete(f.Name); | ||
698 | decode(AssetIdFromFileName(f.Name)); | ||
699 | System.Threading.Thread.Sleep(5000); | ||
700 | } | ||
701 | } | ||
702 | } | 333 | } |
703 | } | 334 | } |
704 | } | 335 | } |
diff --git a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs index 14eb9a2..9a6c49a 100644 --- a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs | |||
@@ -325,7 +325,7 @@ namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture | |||
325 | IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>(); | 325 | IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface<IJ2KDecoder>(); |
326 | if (cacheLayerDecode != null) | 326 | if (cacheLayerDecode != null) |
327 | { | 327 | { |
328 | cacheLayerDecode.syncdecode(asset.FullID, asset.Data); | 328 | cacheLayerDecode.Decode(asset.FullID, asset.Data); |
329 | cacheLayerDecode = null; | 329 | cacheLayerDecode = null; |
330 | LastAssetID = asset.FullID; | 330 | LastAssetID = asset.FullID; |
331 | } | 331 | } |
diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index f4526ae..8ad4844 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs | |||
@@ -1102,5 +1102,9 @@ namespace OpenSim.Region.Examples.SimpleModule | |||
1102 | { | 1102 | { |
1103 | } | 1103 | } |
1104 | #endregion | 1104 | #endregion |
1105 | |||
1106 | public void SendRebakeAvatarTextures(UUID textureID) | ||
1107 | { | ||
1108 | } | ||
1105 | } | 1109 | } |
1106 | } | 1110 | } |
diff --git a/OpenSim/Region/Framework/Interfaces/IJ2KDecoder.cs b/OpenSim/Region/Framework/Interfaces/IJ2KDecoder.cs index b153997..856eb11 100644 --- a/OpenSim/Region/Framework/Interfaces/IJ2KDecoder.cs +++ b/OpenSim/Region/Framework/Interfaces/IJ2KDecoder.cs | |||
@@ -30,12 +30,11 @@ using OpenMetaverse.Imaging; | |||
30 | 30 | ||
31 | namespace OpenSim.Region.Framework.Interfaces | 31 | namespace OpenSim.Region.Framework.Interfaces |
32 | { | 32 | { |
33 | |||
34 | public delegate void DecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers); | 33 | public delegate void DecodedCallback(UUID AssetId, OpenJPEG.J2KLayerInfo[] layers); |
35 | 34 | ||
36 | public interface IJ2KDecoder | 35 | public interface IJ2KDecoder |
37 | { | 36 | { |
38 | void decode(UUID AssetId, byte[] assetData, DecodedCallback decodedReturn); | 37 | void BeginDecode(UUID assetID, byte[] j2kData, DecodedCallback callback); |
39 | void syncdecode(UUID AssetId, byte[] j2kdata); | 38 | void Decode(UUID assetID, byte[] j2kData); |
40 | } | 39 | } |
41 | } | 40 | } |
diff --git a/OpenSim/Region/Framework/Scenes/SceneManager.cs b/OpenSim/Region/Framework/Scenes/SceneManager.cs index 1d4efd0..3097929 100644 --- a/OpenSim/Region/Framework/Scenes/SceneManager.cs +++ b/OpenSim/Region/Framework/Scenes/SceneManager.cs | |||
@@ -540,127 +540,5 @@ namespace OpenSim.Region.Framework.Scenes | |||
540 | { | 540 | { |
541 | m_localScenes.ForEach(action); | 541 | m_localScenes.ForEach(action); |
542 | } | 542 | } |
543 | |||
544 | public void CacheJ2kDecode(int threads) | ||
545 | { | ||
546 | if (threads < 1) threads = 1; | ||
547 | |||
548 | IJ2KDecoder m_decoder = m_localScenes[0].RequestModuleInterface<IJ2KDecoder>(); | ||
549 | |||
550 | List<UUID> assetRequestList = new List<UUID>(); | ||
551 | |||
552 | #region AssetGathering! | ||
553 | foreach (Scene scene in m_localScenes) | ||
554 | { | ||
555 | List<EntityBase> entitles = scene.GetEntities(); | ||
556 | foreach (EntityBase entity in entitles) | ||
557 | { | ||
558 | if (entity is SceneObjectGroup) | ||
559 | { | ||
560 | SceneObjectGroup sog = (SceneObjectGroup) entity; | ||
561 | foreach (SceneObjectPart part in sog.Children.Values) | ||
562 | { | ||
563 | if (part.Shape != null) | ||
564 | { | ||
565 | if (part.Shape.TextureEntry.Length > 0) | ||
566 | { | ||
567 | OpenMetaverse.Primitive.TextureEntry te = | ||
568 | new Primitive.TextureEntry(part.Shape.TextureEntry, 0, | ||
569 | part.Shape.TextureEntry.Length); | ||
570 | if (te.DefaultTexture != null) // this has been null for some reason... | ||
571 | { | ||
572 | if (te.DefaultTexture.TextureID != UUID.Zero) | ||
573 | assetRequestList.Add(te.DefaultTexture.TextureID); | ||
574 | } | ||
575 | for (int i=0; i<te.FaceTextures.Length; i++) | ||
576 | { | ||
577 | if (te.FaceTextures[i] != null) | ||
578 | { | ||
579 | if (te.FaceTextures[i].TextureID != UUID.Zero) | ||
580 | { | ||
581 | assetRequestList.Add(te.FaceTextures[i].TextureID); | ||
582 | } | ||
583 | } | ||
584 | } | ||
585 | } | ||
586 | if (part.Shape.SculptTexture != UUID.Zero) | ||
587 | { | ||
588 | assetRequestList.Add(part.Shape.SculptTexture); | ||
589 | } | ||
590 | |||
591 | } | ||
592 | } | ||
593 | } | ||
594 | } | ||
595 | } | ||
596 | #endregion | ||
597 | |||
598 | int entries_per_thread = (assetRequestList.Count / threads) + 1; | ||
599 | |||
600 | UUID[] arrAssetRequestList = assetRequestList.ToArray(); | ||
601 | |||
602 | List<UUID[]> arrvalus = new List<UUID[]>(); | ||
603 | |||
604 | //split into separate arrays | ||
605 | for (int j = 0; j < threads; j++) | ||
606 | { | ||
607 | List<UUID> val = new List<UUID>(); | ||
608 | |||
609 | for (int k = j * entries_per_thread; k < ((j + 1) * entries_per_thread); k++) | ||
610 | { | ||
611 | if (k < arrAssetRequestList.Length) | ||
612 | { | ||
613 | val.Add(arrAssetRequestList[k]); | ||
614 | } | ||
615 | |||
616 | } | ||
617 | arrvalus.Add(val.ToArray()); | ||
618 | } | ||
619 | |||
620 | for (int l = 0; l < arrvalus.Count; l++) | ||
621 | { | ||
622 | DecodeThreadContents threadworkItem = new DecodeThreadContents(); | ||
623 | threadworkItem.sn = m_localScenes[0]; | ||
624 | threadworkItem.j2kdecode = m_decoder; | ||
625 | threadworkItem.arrassets = arrvalus[l]; | ||
626 | |||
627 | System.Threading.Thread decodethread = | ||
628 | new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(threadworkItem.run)); | ||
629 | |||
630 | threadworkItem.SetThread(decodethread); | ||
631 | |||
632 | decodethread.Priority = System.Threading.ThreadPriority.Lowest; | ||
633 | decodethread.Name = "J2kCacheDecodeThread_" + l + 1; | ||
634 | ThreadTracker.Add(decodethread); | ||
635 | decodethread.Start(); | ||
636 | |||
637 | } | ||
638 | } | ||
639 | } | ||
640 | |||
641 | public class DecodeThreadContents | ||
642 | { | ||
643 | public Scene sn; | ||
644 | public UUID[] arrassets; | ||
645 | public IJ2KDecoder j2kdecode; | ||
646 | private System.Threading.Thread thisthread; | ||
647 | |||
648 | public void run(object o) | ||
649 | { | ||
650 | for (int i=0;i<arrassets.Length;i++) | ||
651 | { | ||
652 | AssetBase ab = sn.AssetService.Get(arrassets[i].ToString()); | ||
653 | if (ab != null && ab.Data != null) | ||
654 | { | ||
655 | j2kdecode.syncdecode(arrassets[i], ab.Data); | ||
656 | } | ||
657 | } | ||
658 | ThreadTracker.Remove(thisthread); | ||
659 | } | ||
660 | |||
661 | public void SetThread(System.Threading.Thread thr) | ||
662 | { | ||
663 | thisthread = thr; | ||
664 | } | ||
665 | } | 543 | } |
666 | } | 544 | } |
diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 66fefa3..3996cdc 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs | |||
@@ -74,6 +74,8 @@ namespace OpenSim.Region.Framework.Scenes | |||
74 | 74 | ||
75 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 75 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
76 | 76 | ||
77 | private static readonly byte[] BAKE_INDICES = new byte[] { 8, 9, 10, 11, 19, 20 }; | ||
78 | |||
77 | public static byte[] DefaultTexture; | 79 | public static byte[] DefaultTexture; |
78 | 80 | ||
79 | public UUID currentParcelUUID = UUID.Zero; | 81 | public UUID currentParcelUUID = UUID.Zero; |
@@ -2686,7 +2688,7 @@ namespace OpenSim.Region.Framework.Scenes | |||
2686 | /// </summary> | 2688 | /// </summary> |
2687 | /// <param name="texture"></param> | 2689 | /// <param name="texture"></param> |
2688 | /// <param name="visualParam"></param> | 2690 | /// <param name="visualParam"></param> |
2689 | public void SetAppearance(byte[] texture, List<byte> visualParam) | 2691 | public void SetAppearance(Primitive.TextureEntry textureEntry, byte[] visualParams) |
2690 | { | 2692 | { |
2691 | if (m_physicsActor != null) | 2693 | if (m_physicsActor != null) |
2692 | { | 2694 | { |
@@ -2704,7 +2706,30 @@ namespace OpenSim.Region.Framework.Scenes | |||
2704 | AddToPhysicalScene(flyingTemp); | 2706 | AddToPhysicalScene(flyingTemp); |
2705 | } | 2707 | } |
2706 | } | 2708 | } |
2707 | m_appearance.SetAppearance(texture, visualParam); | 2709 | |
2710 | #region Bake Cache Check | ||
2711 | |||
2712 | if (textureEntry != null) | ||
2713 | { | ||
2714 | for (int i = 0; i < BAKE_INDICES.Length; i++) | ||
2715 | { | ||
2716 | int j = BAKE_INDICES[i]; | ||
2717 | Primitive.TextureEntryFace face = textureEntry.FaceTextures[j]; | ||
2718 | |||
2719 | if (face != null && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE) | ||
2720 | { | ||
2721 | if (m_scene.AssetService.Get(face.TextureID.ToString()) == null) | ||
2722 | { | ||
2723 | m_log.Warn("[APPEARANCE]: Missing baked texture " + face.TextureID + " (" + (AppearanceManager.TextureIndex)j + ") for avatar " + this.Name); | ||
2724 | this.ControllingClient.SendRebakeAvatarTextures(face.TextureID); | ||
2725 | } | ||
2726 | } | ||
2727 | } | ||
2728 | } | ||
2729 | |||
2730 | #endregion Bake Cache Check | ||
2731 | |||
2732 | m_appearance.SetAppearance(textureEntry, visualParams); | ||
2708 | if (m_appearance.AvatarHeight > 0) | 2733 | if (m_appearance.AvatarHeight > 0) |
2709 | SetHeight(m_appearance.AvatarHeight); | 2734 | SetHeight(m_appearance.AvatarHeight); |
2710 | m_scene.CommsManager.AvatarService.UpdateUserAppearance(m_controllingClient.AgentId, m_appearance); | 2735 | m_scene.CommsManager.AvatarService.UpdateUserAppearance(m_controllingClient.AgentId, m_appearance); |
@@ -3255,14 +3280,14 @@ namespace OpenSim.Region.Framework.Scenes | |||
3255 | wears[i++] = new AvatarWearable(itemId, assetId); | 3280 | wears[i++] = new AvatarWearable(itemId, assetId); |
3256 | } | 3281 | } |
3257 | m_appearance.Wearables = wears; | 3282 | m_appearance.Wearables = wears; |
3258 | byte[] te = null; | 3283 | Primitive.TextureEntry te; |
3259 | if (cAgent.AgentTextures != null) | 3284 | if (cAgent.AgentTextures != null && cAgent.AgentTextures.Length > 1) |
3260 | te = cAgent.AgentTextures; | 3285 | te = new Primitive.TextureEntry(cAgent.AgentTextures, 0, cAgent.AgentTextures.Length); |
3261 | else | 3286 | else |
3262 | te = AvatarAppearance.GetDefaultTexture().GetBytes(); | 3287 | te = AvatarAppearance.GetDefaultTexture(); |
3263 | if ((cAgent.VisualParams == null) || (cAgent.VisualParams.Length < AvatarAppearance.VISUALPARAM_COUNT)) | 3288 | if ((cAgent.VisualParams == null) || (cAgent.VisualParams.Length < AvatarAppearance.VISUALPARAM_COUNT)) |
3264 | cAgent.VisualParams = AvatarAppearance.GetDefaultVisualParams(); | 3289 | cAgent.VisualParams = AvatarAppearance.GetDefaultVisualParams(); |
3265 | m_appearance.SetAppearance(te, new List<byte>(cAgent.VisualParams)); | 3290 | m_appearance.SetAppearance(te, (byte[])cAgent.VisualParams.Clone()); |
3266 | } | 3291 | } |
3267 | catch (Exception e) | 3292 | catch (Exception e) |
3268 | { | 3293 | { |
diff --git a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs index 605645b..ee2d2db 100644 --- a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs +++ b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs | |||
@@ -861,12 +861,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server | |||
861 | Scene scene = (Scene)Scene; | 861 | Scene scene = (Scene)Scene; |
862 | AvatarAppearance appearance; | 862 | AvatarAppearance appearance; |
863 | scene.GetAvatarAppearance(this, out appearance); | 863 | scene.GetAvatarAppearance(this, out appearance); |
864 | List<byte> visualParams = new List<byte>(); | 864 | OnSetAppearance(appearance.Texture, (byte[])appearance.VisualParams.Clone()); |
865 | foreach (byte visualParam in appearance.VisualParams) | ||
866 | { | ||
867 | visualParams.Add(visualParam); | ||
868 | } | ||
869 | OnSetAppearance(appearance.Texture.GetBytes(), visualParams); | ||
870 | } | 865 | } |
871 | 866 | ||
872 | public void SendRegionHandshake(RegionInfo regionInfo, RegionHandshakeArgs args) | 867 | public void SendRegionHandshake(RegionInfo regionInfo, RegionHandshakeArgs args) |
@@ -1609,5 +1604,9 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server | |||
1609 | } | 1604 | } |
1610 | 1605 | ||
1611 | #endregion | 1606 | #endregion |
1607 | |||
1608 | public void SendRebakeAvatarTextures(UUID textureID) | ||
1609 | { | ||
1610 | } | ||
1612 | } | 1611 | } |
1613 | } | 1612 | } |
diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs index f0bdf3b..ac8b98c 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs | |||
@@ -1101,5 +1101,9 @@ namespace OpenSim.Region.OptionalModules.World.NPC | |||
1101 | { | 1101 | { |
1102 | } | 1102 | } |
1103 | #endregion | 1103 | #endregion |
1104 | |||
1105 | public void SendRebakeAvatarTextures(UUID textureID) | ||
1106 | { | ||
1107 | } | ||
1104 | } | 1108 | } |
1105 | } | 1109 | } |
diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs index b3bfe07..30a2675 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCModule.cs | |||
@@ -163,13 +163,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC | |||
163 | { | 163 | { |
164 | AvatarAppearance x = GetAppearance(p_cloneAppearanceFrom, p_scene); | 164 | AvatarAppearance x = GetAppearance(p_cloneAppearanceFrom, p_scene); |
165 | 165 | ||
166 | List<byte> wearbyte = new List<byte>(); | 166 | sp.SetAppearance(x.Texture, (byte[])x.VisualParams.Clone()); |
167 | for (int i = 0; i < x.VisualParams.Length; i++) | ||
168 | { | ||
169 | wearbyte.Add(x.VisualParams[i]); | ||
170 | } | ||
171 | |||
172 | sp.SetAppearance(x.Texture.GetBytes(), wearbyte); | ||
173 | } | 167 | } |
174 | 168 | ||
175 | m_avatars.Add(npcAvatar.AgentId, npcAvatar); | 169 | m_avatars.Add(npcAvatar.AgentId, npcAvatar); |
diff --git a/OpenSim/Region/Physics/Meshing/Meshmerizer.cs b/OpenSim/Region/Physics/Meshing/Meshmerizer.cs index 56eb359..d56ddc8 100644 --- a/OpenSim/Region/Physics/Meshing/Meshmerizer.cs +++ b/OpenSim/Region/Physics/Meshing/Meshmerizer.cs | |||
@@ -78,6 +78,20 @@ namespace OpenSim.Region.Physics.Meshing | |||
78 | 78 | ||
79 | private Dictionary<ulong, Mesh> m_uniqueMeshes = new Dictionary<ulong, Mesh>(); | 79 | private Dictionary<ulong, Mesh> m_uniqueMeshes = new Dictionary<ulong, Mesh>(); |
80 | 80 | ||
81 | public Meshmerizer() | ||
82 | { | ||
83 | try | ||
84 | { | ||
85 | if (!Directory.Exists(decodedScultMapPath)) | ||
86 | Directory.CreateDirectory(decodedScultMapPath); | ||
87 | } | ||
88 | catch (Exception e) | ||
89 | { | ||
90 | m_log.WarnFormat("[SCULPT]: Unable to create {0} directory: ", decodedScultMapPath, e.Message); | ||
91 | } | ||
92 | |||
93 | } | ||
94 | |||
81 | /// <summary> | 95 | /// <summary> |
82 | /// creates a simple box mesh of the specified size. This mesh is of very low vertex count and may | 96 | /// creates a simple box mesh of the specified size. This mesh is of very low vertex count and may |
83 | /// be useful as a backup proxy when level of detail is not needed or when more complex meshes fail | 97 | /// be useful as a backup proxy when level of detail is not needed or when more complex meshes fail |