Switch to Microsoft's MultiValueDictionary
While we are here, clean up AppearanceManager usage of memory. * Modify some List operations to Linq * Copy through construction
This commit is contained in:
@@ -29,6 +29,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Linq;
|
||||
using LibreMetaverse;
|
||||
using Microsoft.Collections.Extensions;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenMetaverse.Imaging;
|
||||
using OpenMetaverse.Assets;
|
||||
@@ -664,7 +665,8 @@ namespace OpenMetaverse
|
||||
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
|
||||
|
||||
if (type == WearableType.Invalid) continue;
|
||||
hash = Wearables.GetValues(type, true).Aggregate(hash, (current, worn) => current ^ worn.AssetID);
|
||||
hash = Wearables.Where(e => e.Key == type)
|
||||
.SelectMany(e => e.Value).Aggregate(hash, (current, worn) => current ^ worn.AssetID);
|
||||
}
|
||||
|
||||
if (hash != UUID.Zero)
|
||||
@@ -714,16 +716,15 @@ namespace OpenMetaverse
|
||||
[Obsolete]
|
||||
public UUID GetWearableAsset(WearableType type)
|
||||
{
|
||||
IList<WearableData> wearableList;
|
||||
return Wearables.TryGetValue(type, out wearableList)
|
||||
return Wearables.TryGetValue(type, out var wearableList)
|
||||
? wearableList.First().AssetID
|
||||
: UUID.Zero;
|
||||
}
|
||||
|
||||
public IEnumerable<UUID> GetWearableAssets(WearableType type)
|
||||
{
|
||||
var wearables = Wearables.GetValues(type, true);
|
||||
return wearables.Select(wearable => wearable.AssetID).ToList();
|
||||
return Wearables.Where(e => e.Key == type).SelectMany(e => e.Value)
|
||||
.Select(wearable => wearable.AssetID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -765,23 +766,17 @@ namespace OpenMetaverse
|
||||
/// <param name="replace">Should existing item on the same point or of the same type be replaced</param>
|
||||
public void AddToOutfit(List<InventoryItem> wearableItems, bool replace)
|
||||
{
|
||||
var wearables = new List<InventoryWearable>();
|
||||
var attachments = new List<InventoryItem>();
|
||||
|
||||
foreach (InventoryItem item in wearableItems)
|
||||
{
|
||||
if (item is InventoryWearable wearable)
|
||||
wearables.Add(wearable);
|
||||
else if (item is InventoryAttachment || item is InventoryObject)
|
||||
attachments.Add(item);
|
||||
}
|
||||
var wearables = wearableItems.OfType<InventoryWearable>()
|
||||
.ToList();
|
||||
var attachments = wearableItems.Where(item => item is InventoryAttachment || item is InventoryObject)
|
||||
.ToList();
|
||||
|
||||
lock (Wearables)
|
||||
{
|
||||
// Add the given wearables to the wearables collection
|
||||
foreach (InventoryWearable wearableItem in wearables)
|
||||
{
|
||||
WearableData wd = new WearableData
|
||||
var wd = new WearableData
|
||||
{
|
||||
AssetID = wearableItem.AssetUUID,
|
||||
AssetType = wearableItem.AssetType,
|
||||
@@ -796,12 +791,12 @@ namespace OpenMetaverse
|
||||
}
|
||||
}
|
||||
|
||||
if (attachments.Count > 0)
|
||||
if (attachments.Any())
|
||||
{
|
||||
AddAttachments(attachments, false, replace);
|
||||
AddAttachments(attachments.ToList(), false, replace);
|
||||
}
|
||||
|
||||
if (wearables.Count > 0)
|
||||
if (wearables.Any())
|
||||
{
|
||||
SendAgentIsNowWearing();
|
||||
DelayedRequestSetAppearance();
|
||||
@@ -826,16 +821,10 @@ namespace OpenMetaverse
|
||||
/// be removed from the outfit</param>
|
||||
public void RemoveFromOutfit(List<InventoryItem> wearableItems)
|
||||
{
|
||||
var wearables = new List<InventoryWearable>();
|
||||
var attachments = new List<InventoryItem>();
|
||||
|
||||
foreach (var item in wearableItems)
|
||||
{
|
||||
if (item is InventoryWearable wearable)
|
||||
wearables.Add(wearable);
|
||||
else if (item is InventoryAttachment || item is InventoryObject)
|
||||
attachments.Add(item);
|
||||
}
|
||||
var wearables = wearableItems.OfType<InventoryWearable>()
|
||||
.ToList();
|
||||
var attachments = wearableItems.Where(item => item is InventoryAttachment || item is InventoryObject)
|
||||
.ToList();
|
||||
|
||||
bool needSetAppearance = false;
|
||||
lock (Wearables)
|
||||
@@ -846,7 +835,8 @@ namespace OpenMetaverse
|
||||
if (wearable.AssetType != AssetType.Bodypart // Remove if it's not a body part
|
||||
&& Wearables.ContainsKey(wearable.WearableType)) // And we have that wearable type
|
||||
{
|
||||
var worn = Wearables.GetValues(wearable.WearableType, true);
|
||||
var worn = Wearables.Where(e => e.Key == wearable.WearableType)
|
||||
.SelectMany(e => e.Value);
|
||||
WearableData wearableData = worn.FirstOrDefault(item => item.ItemID == wearable.UUID);
|
||||
if (wearableData == null) continue;
|
||||
|
||||
@@ -887,16 +877,10 @@ namespace OpenMetaverse
|
||||
/// if you know what you're doing</param>
|
||||
public void ReplaceOutfit(List<InventoryItem> wearableItems, bool safe)
|
||||
{
|
||||
var wearables = new List<InventoryWearable>();
|
||||
var attachments = new List<InventoryItem>();
|
||||
|
||||
foreach (InventoryItem item in wearableItems)
|
||||
{
|
||||
if (item is InventoryWearable wearable)
|
||||
wearables.Add(wearable);
|
||||
else if (item is InventoryAttachment || item is InventoryObject)
|
||||
attachments.Add(item);
|
||||
}
|
||||
var wearables = wearableItems.OfType<InventoryWearable>()
|
||||
.ToList();
|
||||
var attachments = wearableItems.Where(item => item is InventoryAttachment || item is InventoryObject)
|
||||
.ToList();
|
||||
|
||||
if (safe)
|
||||
{
|
||||
@@ -979,12 +963,8 @@ namespace OpenMetaverse
|
||||
{
|
||||
lock (Wearables)
|
||||
{
|
||||
var wearables = new List<WearableData>();
|
||||
foreach (var wearableType in Wearables.Values)
|
||||
{
|
||||
wearables.AddRange(wearableType);
|
||||
}
|
||||
return wearables;
|
||||
// ToList will copy the IEnumerable
|
||||
return Wearables.SelectMany(e => e.Value).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,9 +972,7 @@ namespace OpenMetaverse
|
||||
{
|
||||
lock (Wearables)
|
||||
{
|
||||
var wearables = new MultiValueDictionary<WearableType, WearableData>();
|
||||
wearables.Merge(Wearables);
|
||||
return wearables;
|
||||
return new MultiValueDictionary<WearableType, WearableData>(Wearables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,8 +987,7 @@ namespace OpenMetaverse
|
||||
/// new list of items, false to add these items to the existing outfit</param>
|
||||
public void WearOutfit(List<InventoryBase> wearables, bool replaceItems)
|
||||
{
|
||||
var wearableItems = new List<InventoryItem>(wearables.Count);
|
||||
wearableItems.AddRange(wearables.OfType<InventoryItem>());
|
||||
var wearableItems = wearables.OfType<InventoryItem>().ToList();
|
||||
|
||||
if (replaceItems)
|
||||
ReplaceOutfit(wearableItems);
|
||||
@@ -1022,17 +999,6 @@ namespace OpenMetaverse
|
||||
|
||||
#region Attachments
|
||||
|
||||
/// <summary>
|
||||
/// Adds a list of attachments to our agent
|
||||
/// </summary>
|
||||
/// <param name="attachments">A List containing the attachments to add</param>
|
||||
/// <param name="removeExistingFirst">If true, tells simulator to remove existing attachment
|
||||
/// first</param>
|
||||
public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst)
|
||||
{
|
||||
AddAttachments(attachments, removeExistingFirst, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a list of attachments to our agent
|
||||
/// </summary>
|
||||
@@ -1040,7 +1006,7 @@ namespace OpenMetaverse
|
||||
/// <param name="removeExistingFirst">If true, tells simulator to remove existing attachment
|
||||
/// <param name="replace">If true replace existing attachment on this attachment point, otherwise add to it (multi-attachments)</param>
|
||||
/// first</param>
|
||||
public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst, bool replace)
|
||||
public void AddAttachments(List<InventoryItem> attachments, bool removeExistingFirst, bool replace = true)
|
||||
{
|
||||
// Use RezMultipleAttachmentsFromInv to clear out current attachments, and attach new ones
|
||||
RezMultipleAttachmentsFromInvPacket attachmentsPacket = new RezMultipleAttachmentsFromInvPacket
|
||||
@@ -1205,15 +1171,14 @@ namespace OpenMetaverse
|
||||
/// <param name="itemID">The inventory itemID of the item to detach</param>
|
||||
public void Detach(UUID itemID)
|
||||
{
|
||||
DetachAttachmentIntoInvPacket detach =
|
||||
new DetachAttachmentIntoInvPacket
|
||||
var detach = new DetachAttachmentIntoInvPacket
|
||||
{
|
||||
ObjectData =
|
||||
{
|
||||
ObjectData =
|
||||
{
|
||||
AgentID = Client.Self.AgentID,
|
||||
ItemID = itemID
|
||||
}
|
||||
};
|
||||
AgentID = Client.Self.AgentID,
|
||||
ItemID = itemID
|
||||
}
|
||||
};
|
||||
|
||||
Client.Network.SendPacket(detach);
|
||||
}
|
||||
@@ -1246,10 +1211,8 @@ namespace OpenMetaverse
|
||||
{
|
||||
WearableType = (byte) i,
|
||||
// This appears to be hacked on SL server side to support multi-layers
|
||||
ItemID = Wearables.ContainsKey(type) ?
|
||||
(Wearables[type].First() != null ?
|
||||
Wearables[type].First().ItemID
|
||||
: UUID.Zero)
|
||||
ItemID = Wearables.ContainsKey(type) ?
|
||||
(Wearables[type].First()?.ItemID ?? UUID.Zero)
|
||||
: UUID.Zero
|
||||
};
|
||||
}
|
||||
@@ -1701,27 +1664,16 @@ namespace OpenMetaverse
|
||||
if (index == AvatarTextureIndex.Skirt && !Wearables.ContainsKey(WearableType.Skirt))
|
||||
continue;
|
||||
|
||||
AddTextureDownload(index, textures);
|
||||
TextureData textureData = Textures[(int)index];
|
||||
// Add the textureID to the list if this layer has a valid textureID set, it has not already
|
||||
// been downloaded, and it is not already in the download list
|
||||
if (textureData.TextureID != UUID.Zero && textureData.Texture == null && !textures.Contains(textureData.TextureID))
|
||||
textures.Add(textureData.TextureID);
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to lookup the TextureID for a single layer and add it
|
||||
/// to a list if it is not already present
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="textures"></param>
|
||||
private void AddTextureDownload(AvatarTextureIndex index, List<UUID> textures)
|
||||
{
|
||||
TextureData textureData = Textures[(int)index];
|
||||
// Add the textureID to the list if this layer has a valid textureID set, it has not already
|
||||
// been downloaded, and it is not already in the download list
|
||||
if (textureData.TextureID != UUID.Zero && textureData.Texture == null && !textures.Contains(textureData.TextureID))
|
||||
textures.Add(textureData.TextureID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocking method to download all of the textures needed for baking
|
||||
/// the given bake layers
|
||||
@@ -1813,7 +1765,7 @@ namespace OpenMetaverse
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingBakes.Count > 0)
|
||||
if (pendingBakes.Any())
|
||||
{
|
||||
DownloadTextures(pendingBakes);
|
||||
|
||||
@@ -1831,11 +1783,7 @@ namespace OpenMetaverse
|
||||
{
|
||||
Textures[i].Texture = null;
|
||||
}
|
||||
|
||||
// We just allocated and freed a ridiculous amount of memory while
|
||||
// baking. Signal to the GC to clean up
|
||||
GC.Collect();
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -2185,7 +2133,8 @@ namespace OpenMetaverse
|
||||
WearableType type = WEARABLE_BAKE_MAP[bakedIndex][wearableIndex];
|
||||
|
||||
if (type == WearableType.Invalid) continue;
|
||||
hash = Wearables.GetValues(type, true).Aggregate(hash, (current, worn) => current ^ worn.AssetID);
|
||||
hash = Wearables.Where(e => e.Key == type)
|
||||
.SelectMany(e => e.Value).Aggregate(hash, (current, worn) => current ^ worn.AssetID);
|
||||
}
|
||||
|
||||
if (hash != UUID.Zero)
|
||||
@@ -2328,7 +2277,7 @@ namespace OpenMetaverse
|
||||
{
|
||||
// HACK: I'm so tired and this is so bad.
|
||||
bool match = false;
|
||||
foreach (var wearable in Wearables.GetValues(type, true))
|
||||
foreach (var wearable in Wearables.Where(w => w.Key == type).SelectMany(w => w.Value))
|
||||
{
|
||||
if (wearable.AssetID == block.AssetID || wearable.ItemID == block.ItemID)
|
||||
{
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.Experimental.Collections" Version="1.0.6-e190117-3" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="4.6.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright(c) 2008 Solutions Design.
|
||||
* Copyright(c) 2017 openmetaverse.co.
|
||||
* All rights reserved.
|
||||
*
|
||||
* - Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright notice, this
|
||||
|
||||
* list of conditions and the following disclaimer.
|
||||
* - Neither the name of the openmetaverse.co nor the names
|
||||
* of its contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibreMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to the normal Dictionary. This class can store more than one value for every key. It keeps a HashSet for every Key value.
|
||||
/// Calling Add with the same Key and multiple values will store each value under the same Key in the Dictionary. Obtaining the values
|
||||
/// for a Key will return the HashSet with the Values of the Key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
public class MultiValueDictionary<TKey, TValue> : Dictionary<TKey, IList<TValue>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the specified value under the specified key
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
IList<TValue> container;
|
||||
if (!TryGetValue(key, out container))
|
||||
{
|
||||
container = new List<TValue>();
|
||||
base.Add(key, container);
|
||||
}
|
||||
container.Add(value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this dictionary contains the specified value for the specified key
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>true if the value is stored for the specified key in this dictionary, false otherwise</returns>
|
||||
public bool ContainsValue(TKey key, TValue value)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
bool toReturn = false;
|
||||
IList<TValue> values;
|
||||
if (TryGetValue(key, out values))
|
||||
{
|
||||
toReturn = values.Contains(value);
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified value for the specified key. It will leave the key in the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public void Remove(TKey key, TValue value)
|
||||
{
|
||||
if (key == null) throw new ArgumentNullException(nameof(key));
|
||||
IList<TValue> container;
|
||||
if (TryGetValue(key, out container))
|
||||
{
|
||||
container.Remove(value);
|
||||
if (container.Count <= 0)
|
||||
{
|
||||
Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Merges the specified multivaluedictionary into this instance.
|
||||
/// </summary>
|
||||
/// <param name="toMergeWith">To merge with.</param>
|
||||
public void Merge(MultiValueDictionary<TKey, TValue> toMergeWith)
|
||||
{
|
||||
if (toMergeWith == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in toMergeWith)
|
||||
{
|
||||
foreach (TValue value in pair.Value)
|
||||
{
|
||||
Add(pair.Key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values for the key specified. This method is useful if you want to avoid an exception for key value retrieval and you can't use TryGetValue
|
||||
/// (e.g. in lambdas)
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="returnEmptySet">if set to true and the key isn't found, an empty hashset is returned, otherwise, if the key isn't found, null is returned</param>
|
||||
/// <returns>
|
||||
/// This method will return null (or an empty set if returnEmptySet is true) if the key wasn't found, or
|
||||
/// the values if key was found.
|
||||
/// </returns>
|
||||
public IList<TValue> GetValues(TKey key, bool returnEmptySet)
|
||||
{
|
||||
IList<TValue> toReturn;
|
||||
if (!TryGetValue(key, out toReturn) && returnEmptySet)
|
||||
{
|
||||
toReturn = new List<TValue>();
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user