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:
nopjmp
2019-10-25 10:49:04 -05:00
parent 44feb64f46
commit 88a05fbb3a
3 changed files with 49 additions and 240 deletions

View File

@@ -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)
{

View File

@@ -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" />

View File

@@ -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;
}
}
}