Merge pull request #110 from nooperation/messagepack_serialization

Replace all file serilization with MessagePack
This commit is contained in:
¡Cinder! ㊝
2025-05-27 12:57:58 -05:00
committed by GitHub
15 changed files with 338 additions and 605 deletions

View File

@@ -127,7 +127,6 @@ namespace OpenMetaverse
#endregion Enums
[Serializable]
public class AppearanceManagerException : Exception
{
public AppearanceManagerException(string message)

View File

@@ -0,0 +1,31 @@
using MessagePack;
using MessagePack.Formatters;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
namespace OpenMetaverse.Formatters
{
public class UUIDFormatter : IMessagePackFormatter<UUID>
{
public static readonly UUIDFormatter Instance = new UUIDFormatter();
public void Serialize(ref MessagePackWriter writer, UUID value, MessagePackSerializerOptions options)
{
writer.Write(value.GetBytes());
}
public UUID Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var sequence = reader.ReadBytes();
if (sequence == null)
{
throw new MessagePackSerializationException("Invalid UUID");
}
var bytes = sequence.Value.ToArray();
return new UUID(bytes, 0);
}
}
}

View File

@@ -28,14 +28,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if NET7_0_OR_GREATER
using MemoryPack;
#else
using System.Runtime.Serialization.Formatters.Binary;
#endif
namespace OpenMetaverse
{
@@ -43,7 +36,6 @@ namespace OpenMetaverse
/// <summary>
/// Exception class to identify inventory exceptions
/// </summary>
[Serializable]
public class InventoryException : Exception
{
public InventoryException() { }
@@ -357,156 +349,24 @@ namespace OpenMetaverse
Items.Clear();
}
/// <summary>
/// Saves the current inventory structure to a cache file
/// </summary>
/// <param name="filename">Name of the cache file to save to</param>
public void SaveToDisk(string filename)
{
try
{
using (Stream stream = File.Open(filename, FileMode.Create))
{
#if !NET7_0_OR_GREATER
var bformatter = new BinaryFormatter();
#endif
lock (Items)
{
Logger.Log($"Caching {Items.Count} inventory items to {filename}", Helpers.LogLevel.Info);
foreach (var kvp in Items)
{
#if NET7_0_OR_GREATER
MemoryPackSerializer.SerializeAsync(stream, kvp.Value);
#else
bformatter.Serialize(stream, kvp.Value);
#endif
}
}
}
}
catch (Exception e)
{
Logger.Log("Error saving inventory cache to disk", Helpers.LogLevel.Error, e);
}
InventoryCache.SaveToDisk(filename, Items);
}
/// <summary>
/// Loads in inventory cache file into the inventory structure. Note only valid to call after login has been successful.
/// </summary>
/// <param name="filename">Name of the cache file to load</param>
/// <returns>The number of inventory items successfully reconstructed into the inventory node tree</returns>
/// <returns>The number of inventory items successfully reconstructed into the inventory node tree, or -1 on error</returns>
public int RestoreFromDisk(string filename)
{
var cacheNodes = new List<InventoryNode>();
try
{
if (!File.Exists(filename))
{
return -1;
}
using (var stream = File.Open(filename, FileMode.Open))
{
#if !NET7_0_OR_GREATER
var bformatter = new BinaryFormatter();
#endif
while (stream.Position < stream.Length)
{
#if NET7_0_OR_GREATER
var node = MemoryPackSerializer.DeserializeAsync<InventoryNode>(stream);
cacheNodes.Add(node.Result);
#else
var node = (InventoryNode)bformatter.Deserialize(stream);
cacheNodes.Add(node);
#endif
}
}
}
catch (Exception e)
{
Logger.Log("Error accessing inventory cache file", Helpers.LogLevel.Error, e);
return -1;
}
Logger.Log($"Read {cacheNodes.Count} items from inventory cache file", Helpers.LogLevel.Info);
var dirtyFolders = new HashSet<UUID>();
// First pass: process InventoryFolders
foreach (var cacheNode in cacheNodes)
{
if(!(cacheNode.Data is InventoryFolder cacheFolder))
{
continue;
}
if (cacheNode.ParentID == UUID.Zero)
{
//We don't need the root nodes "My Inventory" etc as they will already exist for the correct
// user of this cache.
continue;
}
if (!Items.TryGetValue(cacheNode.Data.UUID, out var serverNode))
{
// This is an orphaned folder that no longer exists on the server.
continue;
}
var serverFolder = (InventoryFolder)serverNode.Data;
serverNode.NeedsUpdate = cacheFolder.Version != serverFolder.Version;
if (serverNode.NeedsUpdate)
{
Logger.DebugLog($"Inventory Cache/Server version mismatch on {cacheNode.Data.Name} {cacheFolder.Version} vs {serverFolder.Version}");
dirtyFolders.Add(cacheNode.Data.UUID);
}
}
// Second pass: process InventoryItems
var itemCount = 0;
foreach (var cacheNode in cacheNodes)
{
if (!(cacheNode.Data is InventoryItem cacheItem))
{
// Only process InventoryItems
continue;
}
if (!Items.TryGetValue(cacheNode.ParentID, out var serverParentNode))
{
// This item does not have a parent in our known inventory. The folder was probably deleted on the server
// and our cache is old
continue;
}
if(dirtyFolders.Contains(serverParentNode.Data.UUID))
{
// This item belongs to a folder that has been marked as dirty, so it too is dirty and must be skipped
continue;
}
if(Items.ContainsKey(cacheItem.UUID))
{
// This item was already added to our Items store, likely added from previous server requests during this session
continue;
}
if (!Items.TryAdd(cacheItem.UUID, cacheNode))
{
Logger.Log($"Failed to add cache item node {cacheItem.Name} with parent {serverParentNode.Data.Name}", Helpers.LogLevel.Info);
continue;
}
// Add this cached InventoryItem node to the parent
cacheNode.Parent = serverParentNode;
serverParentNode.Nodes.Add(cacheItem.UUID, cacheNode);
itemCount++;
}
Logger.Log($"Reassembled {itemCount} items from inventory cache file", Helpers.LogLevel.Info);
return itemCount;
return InventoryCache.RestoreFromDisk(filename, Items);
}
#region Operators

View File

@@ -26,56 +26,51 @@
*/
using System;
using System.Runtime.Serialization;
using OpenMetaverse.StructuredData;
#if NET7_0_OR_GREATER
using MemoryPack;
#endif
using MessagePack;
namespace OpenMetaverse
{
/// <summary>
/// Base Class for Inventory Items
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
[MemoryPackUnion(0, typeof(InventoryFolder))]
[MemoryPackUnion(1, typeof(InventoryItem))]
[MemoryPackUnion(2, typeof(InventoryAnimation))]
[MemoryPackUnion(3, typeof(InventoryAttachment))]
[MemoryPackUnion(4, typeof(InventoryCallingCard))]
[MemoryPackUnion(5, typeof(InventoryCategory))]
[MemoryPackUnion(6, typeof(InventoryGesture))]
[MemoryPackUnion(7, typeof(InventoryLSL))]
[MemoryPackUnion(8, typeof(InventoryLandmark))]
[MemoryPackUnion(9, typeof(InventoryMaterial))]
[MemoryPackUnion(10, typeof(InventoryNotecard))]
[MemoryPackUnion(11, typeof(InventoryObject))]
[MemoryPackUnion(12, typeof(InventorySettings))]
[MemoryPackUnion(13, typeof(InventorySnapshot))]
[MemoryPackUnion(14, typeof(InventorySound))]
[MemoryPackUnion(15, typeof(InventoryTexture))]
[MemoryPackUnion(16, typeof(InventoryWearable))]
#endif
public abstract partial class InventoryBase : ISerializable
[MessagePackObject]
[Union(0, typeof(InventoryFolder))]
[Union(1, typeof(InventoryItem))]
[Union(2, typeof(InventoryAnimation))]
[Union(3, typeof(InventoryAttachment))]
[Union(4, typeof(InventoryCallingCard))]
[Union(5, typeof(InventoryCategory))]
[Union(6, typeof(InventoryGesture))]
[Union(7, typeof(InventoryLSL))]
[Union(8, typeof(InventoryLandmark))]
[Union(9, typeof(InventoryMaterial))]
[Union(10, typeof(InventoryNotecard))]
[Union(11, typeof(InventoryObject))]
[Union(12, typeof(InventorySettings))]
[Union(13, typeof(InventorySnapshot))]
[Union(14, typeof(InventorySound))]
[Union(15, typeof(InventoryTexture))]
[Union(16, typeof(InventoryWearable))]
public abstract partial class InventoryBase
{
/// <summary><see cref="OpenMetaverse.UUID"/> of item/folder</summary>
[Key("UUID")]
public UUID UUID;
/// <summary><see cref="OpenMetaverse.UUID"/> of parent folder</summary>
[Key("ParentUUID")]
public UUID ParentUUID;
/// <summary>Name of item/folder</summary>
[Key("Name")]
public string Name;
/// <summary>Item/Folder Owners <see cref="OpenMetaverse.UUID"/></summary>
[Key("OwnerID")]
public UUID OwnerID;
/// <summary>
/// Constructor, takes an itemID as a parameter
/// </summary>
/// <param name="UUID">The <see cref="OpenMetaverse.UUID"/> of the item</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
protected InventoryBase(UUID UUID)
{
if (UUID == UUID.Zero)
@@ -83,32 +78,6 @@ namespace OpenMetaverse
this.UUID = UUID;
}
/// <summary>
/// Get object data
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("UUID", UUID);
info.AddValue("ParentUUID", ParentUUID);
info.AddValue("Name", Name);
info.AddValue("OwnerID", OwnerID);
}
/// <summary>
/// Inventory base ctor
/// </summary>
/// <param name="info"></param>
/// <param name="ctxt"></param>
protected InventoryBase(SerializationInfo info, StreamingContext ctxt)
{
UUID = (UUID)info.GetValue("UUID", typeof(UUID));
ParentUUID = (UUID)info.GetValue("ParentUUID", typeof(UUID));
Name = (string)info.GetValue("Name", typeof(string));
OwnerID = (UUID)info.GetValue("OwnerID", typeof(UUID));
}
/// <summary>
/// Generates a number corresponding to the value of the object to support the use of a hash table,
/// suitable for use in hashing algorithms and data structures such as a hash table
@@ -152,10 +121,7 @@ namespace OpenMetaverse
/// <summary>
/// An Item in Inventory
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryItem : InventoryBase
{
public override string ToString()
@@ -163,45 +129,58 @@ namespace OpenMetaverse
return $"{AssetType} {AssetUUID} ({InventoryType} {UUID}) '{Name}'/'{Description}' {Permissions}";
}
/// <summary><see cref="UUID"/> of the underlying asset</summary>
[Key("AssetUUID")]
public UUID AssetUUID;
/// <summary>Combined <see cref="OpenMetaverse.Permissions"/> of the item</summary>
[Key("Permissions")]
public Permissions Permissions;
/// <summary><see cref="OpenMetaverse.AssetType"/> of the underlying asset</summary>
[Key("AssetType")]
public AssetType AssetType;
/// <summary><see cref="OpenMetaverse.InventoryType"/> of the item</summary>
[Key("InventoryType")]
public InventoryType InventoryType;
/// <summary><see cref="UUID"/> of the creator of the item</summary>
[Key("CreatorID")]
public UUID CreatorID;
/// <summary>Description of the item</summary>
[Key("Description")]
public string Description;
/// <summary><see cref="Group"/>s <see cref="UUID"/> the item is owned by</summary>
[Key("GroupID")]
public UUID GroupID;
/// <summary>If true, item is owned by a group</summary>
[Key("GroupOwned")]
public bool GroupOwned;
/// <summary>Price the item can be purchased for</summary>
[Key("SalePrice")]
public int SalePrice;
/// <summary><see cref="OpenMetaverse.SaleType"/> of the item</summary>
[Key("SaleType")]
public SaleType SaleType;
/// <summary>Combined flags from <see cref="InventoryItemFlags"/></summary>
[Key("Flags")]
public uint Flags;
/// <summary>Time and date the inventory item was created, stored as
/// UTC (Coordinated Universal Time)</summary>
[Key("CreationDate")]
public DateTime CreationDate;
/// <summary>Used to update the AssetID in requests sent to the server</summary>
[Key("TransactionID")]
public UUID TransactionID;
/// <summary><see cref="UUID"/> of the previous owner of the item</summary>
[Key("LastOwnerID")]
public UUID LastOwnerID;
/// <summary>inventoryID that this item points to, else this item's inventoryID</summary>
[IgnoreMember]
public UUID ActualUUID => IsLink() ? AssetUUID : UUID;
/// <summary>
/// Construct a new InventoryItem object
/// </summary>
/// <param name="UUID">The <see cref="UUID"/> of the item</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryItem(UUID UUID)
: base(UUID) { }
@@ -221,53 +200,6 @@ namespace OpenMetaverse
return AssetType == AssetType.Link || AssetType == AssetType.LinkFolder;
}
/// <inheritdoc />
/// <summary>
/// Get object data
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("AssetUUID", AssetUUID, typeof(UUID));
info.AddValue("Permissions", Permissions, typeof(Permissions));
info.AddValue("AssetType", AssetType);
info.AddValue("InventoryType", InventoryType);
info.AddValue("CreatorID", CreatorID);
info.AddValue("Description", Description);
info.AddValue("GroupID", GroupID);
info.AddValue("GroupOwned", GroupOwned);
info.AddValue("SalePrice", SalePrice);
info.AddValue("SaleType", SaleType);
info.AddValue("Flags", Flags);
info.AddValue("CreationDate", CreationDate);
info.AddValue("LastOwnerID", LastOwnerID);
}
/// <summary>
/// Inventory item ctor
/// </summary>
/// <param name="info"></param>
/// <param name="ctxt"></param>
public InventoryItem(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
AssetUUID = (UUID)info.GetValue("AssetUUID", typeof(UUID));
Permissions = (Permissions)info.GetValue("Permissions", typeof(Permissions));
AssetType = (AssetType)info.GetValue("AssetType", typeof(AssetType));
InventoryType = (InventoryType)info.GetValue("InventoryType", typeof(InventoryType));
CreatorID = (UUID)info.GetValue("CreatorID", typeof(UUID));
Description = (string)info.GetValue("Description", typeof(string));
GroupID = (UUID)info.GetValue("GroupID", typeof(UUID));
GroupOwned = (bool)info.GetValue("GroupOwned", typeof(bool));
SalePrice = (int)info.GetValue("SalePrice", typeof(int));
SaleType = (SaleType)info.GetValue("SaleType", typeof(SaleType));
Flags = (uint)info.GetValue("Flags", typeof(uint));
CreationDate = (DateTime)info.GetValue("CreationDate", typeof(DateTime));
LastOwnerID = (UUID)info.GetValue("LastOwnerID", typeof(UUID));
}
/// <summary>
/// Generates a number corresponding to the value of the object to support the use of a hash table.
/// Suitable for use in hashing algorithms and data structures such as a hash table
@@ -508,10 +440,7 @@ namespace OpenMetaverse
/// InventoryTexture Class representing a graphical image
/// </summary>
/// <seealso cref="T:OpenMetaverse.Imaging.ManagedImage" />
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryTexture : InventoryItem
{
/// <summary>
@@ -519,33 +448,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryTexture(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Texture;
}
/// <summary>
/// Construct an InventoryTexture object from a serialization stream
/// </summary>
public InventoryTexture(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Texture;
}
}
/// <inheritdoc />
/// <summary>
/// InventorySound Class representing a playable sound
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventorySound : InventoryItem
{
/// <summary>
@@ -553,33 +467,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventorySound(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Sound;
}
/// <summary>
/// Construct an InventorySound object from a serialization stream
/// </summary>
public InventorySound(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Sound;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryCallingCard Class, contains information on another avatar
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryCallingCard : InventoryItem
{
/// <summary>
@@ -587,33 +486,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryCallingCard(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.CallingCard;
}
/// <summary>
/// Construct an InventoryCallingCard object from a serialization stream
/// </summary>
public InventoryCallingCard(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.CallingCard;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryLandmark Class, contains details on a specific location
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryLandmark : InventoryItem
{
/// <summary>
@@ -621,28 +505,16 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryLandmark(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Landmark;
}
/// <summary>
/// Construct an InventoryLandmark object from a serialization stream
/// </summary>
public InventoryLandmark(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Landmark;
}
/// <summary>
/// Landmarks use the InventoryItemFlags struct and will have a flag of 1 set if they have been visited
/// </summary>
[IgnoreMember]
public bool LandmarkVisited
{
get => (Flags & 1) != 0;
@@ -658,10 +530,7 @@ namespace OpenMetaverse
/// <summary>
/// InventoryObject Class contains details on a primitive or coalesced set of primitives
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryObject : InventoryItem
{
/// <summary>
@@ -669,27 +538,16 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryObject(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Object;
}
/// <summary>
/// Construct an InventoryObject object from a serialization stream
/// </summary>
public InventoryObject(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Object;
}
/// <summary>
/// Gets or sets the upper byte of the Flags value
/// </summary>
[IgnoreMember]
public InventoryItemFlags ItemFlags
{
get => (InventoryItemFlags)(Flags & ~0xFF);
@@ -699,6 +557,7 @@ namespace OpenMetaverse
/// <summary>
/// Gets or sets the object attachment point, the lower byte of the Flags value
/// </summary>
[IgnoreMember]
public AttachmentPoint AttachPoint
{
get => (AttachmentPoint)(Flags & 0xFF);
@@ -710,10 +569,7 @@ namespace OpenMetaverse
/// <summary>
/// InventoryNotecard Class, contains details on an encoded text document
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryNotecard : InventoryItem
{
/// <summary>
@@ -721,33 +577,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryNotecard(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Notecard;
}
/// <summary>
/// Construct an InventoryNotecard object from a serialization stream
/// </summary>
public InventoryNotecard(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Notecard;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryCategory Class
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryCategory : InventoryItem
{
/// <summary>
@@ -755,33 +596,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryCategory(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Category;
}
/// <summary>
/// Construct an InventoryCategory object from a serialization stream
/// </summary>
public InventoryCategory(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Category;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryLSL Class, represents a Linden Scripting Language object
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryLSL : InventoryItem
{
/// <summary>
@@ -789,33 +615,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryLSL(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.LSL;
}
/// <summary>
/// Construct an InventoryLSL object from a serialization stream
/// </summary>
public InventoryLSL(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.LSL;
}
}
/// <inheritdoc />
/// <summary>
/// InventorySnapshot Class, an image taken with the viewer
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventorySnapshot : InventoryItem
{
/// <inheritdoc />
@@ -824,32 +635,17 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="T:OpenMetaverse.UUID" /> which becomes the
/// <seealso cref="T:OpenMetaverse.InventoryItem" /> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventorySnapshot(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Snapshot;
}
/// <summary>
/// Construct an InventorySnapshot object from a serialization stream
/// </summary>
public InventorySnapshot(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Snapshot;
}
}
/// <summary>
/// InventoryAttachment Class, contains details on an attachable object
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryAttachment : InventoryItem
{
/// <summary>
@@ -857,27 +653,16 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryAttachment(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Attachment;
}
/// <summary>
/// Construct an InventoryAttachment object from a serialization stream
/// </summary>
public InventoryAttachment(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Attachment;
}
/// <summary>
/// Get the last AttachmentPoint this object was attached to
/// </summary>
[IgnoreMember]
public AttachmentPoint AttachmentPoint
{
get => (AttachmentPoint)Flags;
@@ -889,10 +674,7 @@ namespace OpenMetaverse
/// <summary>
/// InventoryWearable Class, details on a clothing item or body part
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryWearable : InventoryItem
{
/// <summary>
@@ -900,23 +682,12 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryWearable(UUID UUID) : base(UUID) { InventoryType = InventoryType.Wearable; }
/// <summary>
/// Construct an InventoryWearable object from a serialization stream
/// </summary>
public InventoryWearable(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Wearable;
}
/// <summary>
/// The <see cref="OpenMetaverse.WearableType"/>, Skin, Shape, Skirt, Etc
/// </summary>
[IgnoreMember]
public WearableType WearableType
{
get => (WearableType)Flags;
@@ -928,10 +699,7 @@ namespace OpenMetaverse
/// <summary>
/// InventoryAnimation Class, A bvh encoded object which animates an avatar
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryAnimation : InventoryItem
{
/// <summary>
@@ -939,33 +707,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryAnimation(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Animation;
}
/// <summary>
/// Construct an InventoryAnimation object from a serialization stream
/// </summary>
public InventoryAnimation(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Animation;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryGesture Class, details on a series of animations, sounds, and actions
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryGesture : InventoryItem
{
/// <summary>
@@ -973,33 +726,18 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryGesture(UUID UUID)
: base(UUID)
{
InventoryType = InventoryType.Gesture;
}
/// <summary>
/// Construct an InventoryGesture object from a serialization stream
/// </summary>
public InventoryGesture(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
InventoryType = InventoryType.Gesture;
}
}
/// <inheritdoc />
/// <summary>
/// InventorySettings, LLSD settings blob as an asset
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventorySettings : InventoryItem
{
/// <summary>
@@ -1007,28 +745,17 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventorySettings(UUID UUID) : base(UUID)
{
InventoryType = InventoryType.Settings;
}
public InventorySettings(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt)
{
InventoryType = InventoryType.Settings;
}
}
/// <inheritdoc />
/// <summary>
/// InventoryMaterial, material as an asset
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryMaterial : InventoryItem
{
/// <summary>
@@ -1036,47 +763,38 @@ namespace OpenMetaverse
/// </summary>
/// <param name="UUID">A <see cref="UUID"/> which becomes the
/// <seealso cref="InventoryItem"/> objects UUID</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryMaterial(UUID UUID) : base(UUID)
{
InventoryType = InventoryType.Settings;
}
public InventoryMaterial(SerializationInfo info, StreamingContext ctxt) : base(info, ctxt)
{
InventoryType = InventoryType.Settings;
}
}
/// <inheritdoc />
/// <summary>
/// A folder contains <see cref="T:OpenMetaverse.InventoryItem" />s and has certain attributes specific
/// to itself
/// </summary>
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
[MessagePackObject]
public partial class InventoryFolder : InventoryBase
{
public const int VERSION_UNKNOWN = -1;
/// <summary>The Preferred <see cref="T:OpenMetaverse.FolderType"/> for a folder.</summary>
[Key("PreferredType")]
public FolderType PreferredType;
/// <summary>The Version of this folder</summary>
[Key("Version")]
public int Version;
/// <summary>Number of child items this folder contains.</summary>
[Key("DescendentCount")]
public int DescendentCount;
/// <summary>
/// Constructor
/// </summary>
/// <param name="UUID">UUID of the folder</param>
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryFolder(UUID UUID)
: base(UUID)
{
@@ -1094,30 +812,6 @@ namespace OpenMetaverse
return Name;
}
/// <summary>
/// Get Serialization data for this InventoryFolder object
/// </summary>
/// <param name="info"></param>
/// <param name="context"></param>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("PreferredType", PreferredType, typeof(FolderType));
info.AddValue("Version", Version);
info.AddValue("DescendentCount", DescendentCount);
}
/// <summary>
/// Construct an InventoryFolder object from a serialization stream
/// </summary>
public InventoryFolder(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
PreferredType = (FolderType)info.GetValue("PreferredType", typeof(FolderType));
Version = (int)info.GetValue("Version", typeof(int));
DescendentCount = (int)info.GetValue("DescendentCount", typeof(int));
}
/// <summary>
/// Return int hash code
/// </summary>

View File

@@ -0,0 +1,214 @@
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace OpenMetaverse
{
internal class InventoryCache
{
private static readonly string InventoryCacheMagic = "INVCACHE";
private static readonly int InventoryCacheVersion = 1;
/// <summary>
/// Creates MessagePack serializer options for use in inventory cache serializing and deserializing
/// </summary>
/// <returns>MessagePack serializer options</returns>
private static MessagePackSerializerOptions GetSerializerOptions()
{
var resolver = MessagePack.Resolvers.CompositeResolver.Create(
new List<IMessagePackFormatter>()
{
Formatters.UUIDFormatter.Instance,
},
new List<IFormatterResolver>
{
StandardResolver.Instance,
}
);
return MessagePackSerializerOptions
.Standard
.WithResolver(resolver);
}
/// <summary>
/// Saves the current inventory structure to a cache file
/// </summary>
/// <param name="filename">Name of the cache file to save to</param>
public static void SaveToDisk(string filename, ConcurrentDictionary<UUID, InventoryNode> Items)
{
try
{
using (var bw = new BinaryWriter(File.Open(filename, FileMode.Create)))
{
lock (Items)
{
Logger.Log($"Caching {Items.Count} inventory items to {filename}", Helpers.LogLevel.Info);
var options = GetSerializerOptions();
var items = Items.Values.ToList();
bw.Write(Encoding.ASCII.GetBytes(InventoryCacheMagic));
bw.Write(InventoryCacheVersion);
MessagePackSerializer.Serialize(bw.BaseStream, items, options);
}
}
}
catch (Exception e)
{
Logger.Log("Error saving inventory cache to disk", Helpers.LogLevel.Error, e);
}
}
/// <summary>
/// Loads in inventory cache file into the inventory structure. Note only valid to call after login has been successful.
/// </summary>
/// <param name="filename">Name of the cache file to load</param>
/// <returns>The number of inventory items successfully reconstructed into the inventory node tree, or -1 on error</returns>
public static int RestoreFromDisk(string filename, ConcurrentDictionary<UUID, InventoryNode> Items)
{
List<InventoryNode> cacheNodes;
try
{
if (!File.Exists(filename))
{
return -1;
}
using (var br = new BinaryReader(File.Open(filename, FileMode.Open)))
{
var options = GetSerializerOptions();
if (br.BaseStream.Length < InventoryCacheMagic.Length + sizeof(int))
{
Logger.Log($"Invalid inventory cache file. Missing header.", Helpers.LogLevel.Warning);
return -1;
}
var magic = br.ReadBytes(InventoryCacheMagic.Length);
if (Encoding.ASCII.GetString(magic) != InventoryCacheMagic)
{
Logger.Log($"Invalid inventory cache file. Missing magic header.", Helpers.LogLevel.Warning);
return -1;
}
var version = br.ReadInt32();
if (version != InventoryCacheVersion)
{
Logger.Log($"Invalid inventory cache file. Expected version {InventoryCacheVersion}, got {version}.", Helpers.LogLevel.Warning);
return -1;
}
cacheNodes = MessagePackSerializer.Deserialize<List<InventoryNode>>(br.BaseStream, options);
if (cacheNodes == null)
{
Logger.Log($"Invalid inventory cache file. Failed to deserialize contents.", Helpers.LogLevel.Warning);
return -1;
}
}
}
catch (Exception e)
{
Logger.Log("Error accessing inventory cache file", Helpers.LogLevel.Error, e);
return -1;
}
Logger.Log($"Read {cacheNodes.Count} items from inventory cache file", Helpers.LogLevel.Info);
var dirtyFolders = new HashSet<UUID>();
// First pass: process InventoryFolders
foreach (var cacheNode in cacheNodes)
{
if (!(cacheNode.Data is InventoryFolder cacheFolder))
{
continue;
}
if (cacheNode.Data.ParentUUID == UUID.Zero)
{
//We don't need the root nodes "My Inventory" etc as they will already exist for the correct
// user of this cache.
continue;
}
if (!Items.TryGetValue(cacheNode.Data.UUID, out var serverNode))
{
// This is an orphaned folder that no longer exists on the server.
continue;
}
if (!(serverNode.Data is InventoryFolder serverFolder))
{
Logger.Log($"Cached inventory node folder has a parent that is not an InventoryFolder", Helpers.LogLevel.Warning);
continue;
}
serverNode.NeedsUpdate = cacheFolder.Version != serverFolder.Version;
if (serverNode.NeedsUpdate)
{
Logger.DebugLog($"Inventory Cache/Server version mismatch on {cacheNode.Data.Name} {cacheFolder.Version} vs {serverFolder.Version}");
dirtyFolders.Add(cacheNode.Data.UUID);
}
}
// Second pass: process InventoryItems
var itemCount = 0;
foreach (var cacheNode in cacheNodes)
{
if (!(cacheNode.Data is InventoryItem cacheItem))
{
// Only process InventoryItems
continue;
}
if (!Items.TryGetValue(cacheNode.Data.ParentUUID, out var serverParentNode))
{
// This item does not have a parent in our known inventory. The folder was probably deleted on the server
// and our cache is old
continue;
}
if (!(serverParentNode.Data is InventoryFolder serverParentFolder))
{
Logger.Log($"Cached inventory node item {cacheItem.Name} has a parent {serverParentNode.Data.Name} that is not an InventoryFolder", Helpers.LogLevel.Warning);
continue;
}
if (dirtyFolders.Contains(serverParentFolder.UUID))
{
// This item belongs to a folder that has been marked as dirty, so it too is dirty and must be skipped
continue;
}
if (Items.ContainsKey(cacheItem.UUID))
{
// This item was already added to our Items store, likely added from previous server requests during this session
continue;
}
if (!Items.TryAdd(cacheItem.UUID, cacheNode))
{
Logger.Log($"Failed to add cache item node {cacheItem.Name} with parent {serverParentFolder.Name}", Helpers.LogLevel.Info);
continue;
}
// Add this cached InventoryItem node to the parent
cacheNode.Parent = serverParentNode;
serverParentNode.Nodes.Add(cacheItem.UUID, cacheNode);
itemCount++;
}
Logger.Log($"Reassembled {itemCount} items from inventory cache file", Helpers.LogLevel.Info);
return itemCount;
}
}
}

View File

@@ -26,30 +26,21 @@
*/
using System;
using System.Runtime.Serialization;
#if NET7_0_OR_GREATER
using MemoryPack;
#endif
using MessagePack;
namespace OpenMetaverse
{
[Serializable]
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
public partial class InventoryNode : ISerializable
[MessagePackObject]
public partial class InventoryNode
{
private InventoryBase data;
private InventoryNode parent;
private UUID parentID; //used for deseralization
private InventoryNodeDictionary nodes;
private bool needsUpdate = true;
[NonSerialized]
#if NET7_0_OR_GREATER
[MemoryPackIgnore]
#endif
private object tag;
[Key("Data")]
public InventoryBase Data
{
get => data;
@@ -57,27 +48,21 @@ namespace OpenMetaverse
}
/// <summary>User data</summary>
#if NET7_0_OR_GREATER
[MemoryPackIgnore]
#endif
[IgnoreMember]
public object Tag
{
get => tag;
set => tag = value;
}
[IgnoreMember]
public InventoryNode Parent
{
get => parent;
set => parent = value;
}
public UUID ParentID
{
get => parentID;
private set => parentID = value;
}
[IgnoreMember]
public InventoryNodeDictionary Nodes
{
get => nodes ?? (nodes = new InventoryNodeDictionary(this));
@@ -88,12 +73,14 @@ namespace OpenMetaverse
/// For inventory folder nodes specifies weather the folder needs to be
/// refreshed from the server
/// </summary>
[IgnoreMember]
public bool NeedsUpdate
{
get => needsUpdate;
set => needsUpdate = value;
}
[IgnoreMember]
public DateTime ModifyTime
{
get
@@ -120,9 +107,6 @@ namespace OpenMetaverse
Nodes.Sort();
}
#if NET7_0_OR_GREATER
[MemoryPackConstructor]
#endif
public InventoryNode()
{
}
@@ -148,29 +132,6 @@ namespace OpenMetaverse
}
}
/// <summary>
/// Serialization handler for the InventoryNode Class
/// </summary>
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("Parent", parent?.Data.UUID ?? UUID.Zero, typeof(UUID));
info.AddValue("Type", data.GetType(), typeof(Type));
data.GetObjectData(info, ctxt);
}
/// <summary>
/// De-serialization handler for the InventoryNode Class
/// </summary>
public InventoryNode(SerializationInfo info, StreamingContext ctxt)
{
parentID = (UUID)info.GetValue("Parent", typeof(UUID));
Type type = (Type)info.GetValue("Type", typeof(Type));
// Construct a new inventory object based on the Type stored in Type
System.Reflection.ConstructorInfo ctr = type.GetConstructor(new[] {typeof(SerializationInfo),typeof(StreamingContext)});
if (ctr != null) data = (InventoryBase)ctr.Invoke(new object[] { info, ctxt });
}
public override string ToString()
{
return this.Data == null ? "[Empty Node]" : this.Data.ToString();

View File

@@ -27,23 +27,15 @@
using System;
using System.Collections.Generic;
#if NET7_0_OR_GREATER
using MemoryPack;
#endif
namespace OpenMetaverse
{
#if NET7_0_OR_GREATER
[MemoryPackable]
#endif
public partial class InventoryNodeDictionary: IComparer<UUID>
{
protected readonly SortedDictionary<UUID, InventoryNode> SDictionary;
protected readonly Dictionary<UUID, InventoryNode> Dictionary = new Dictionary<UUID, InventoryNode>();
protected InventoryNode parent;
#if NET7_0_OR_GREATER
[MemoryPackIgnore]
#endif
protected readonly object syncRoot = new object();
public int Compare(UUID x, UUID y)
{
@@ -91,9 +83,6 @@ namespace OpenMetaverse
set => parent = value;
}
#if NET7_0_OR_GREATER
[MemoryPackIgnore]
#endif
public object SyncRoot => syncRoot;
public int Count => Dictionary.Count;

View File

@@ -28,7 +28,7 @@
<ItemGroup>
<PackageReference Include="CoreJ2K.Skia" Version="1.1.1.48" />
<PackageReference Include="log4net" Version="3.0.4" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="MessagePack" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.4" />
<PackageReference Include="OggVorbisEncoder" Version="1.2.2" />
<PackageReference Include="Pfim" Version="0.11.3" />

View File

@@ -29,7 +29,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace OpenMetaverse
{
@@ -90,13 +89,6 @@ namespace OpenMetaverse
}
[Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.")]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
lock (Dictionary)
Dictionary.GetObjectData(info, context);
}
public void OnDeserialization(object sender)
{
lock( Dictionary)

View File

@@ -1061,7 +1061,6 @@ namespace OpenMetaverse
#endregion Structs
[Serializable]
public class LoginException : Exception
{
public LoginException(string message)

View File

@@ -603,7 +603,6 @@ namespace OpenMetaverse.Messages.Linden
/// <summary>
/// The details of a single parcel in a region, also contains some regionwide globals
/// </summary>
[Serializable]
public class ParcelPropertiesMessage : IMessage
{
/// <summary>Simulator-local ID of this parcel</summary>
@@ -1086,7 +1085,6 @@ namespace OpenMetaverse.Messages.Linden
}
/// <summary>Base class used for the RemoteParcelRequest message</summary>
[Serializable]
public abstract class RemoteParcelRequestBlock
{
public abstract OSDMap Serialize();
@@ -1137,7 +1135,6 @@ namespace OpenMetaverse.Messages.Linden
/// A message sent from the simulator to the viewer in response to a <see cref="RemoteParcelRequestRequest"/>
/// which will contain parcel information
/// </summary>
[Serializable]
public class RemoteParcelRequestReply : RemoteParcelRequestBlock
{
/// <summary>The grid-wide unique parcel ID</summary>
@@ -1170,7 +1167,6 @@ namespace OpenMetaverse.Messages.Linden
/// A message containing a request for a remote parcel from a viewer, or a response
/// from the simulator to that request
/// </summary>
[Serializable]
public class RemoteParcelRequestMessage : IMessage
{
/// <summary>The request or response details block</summary>
@@ -4102,13 +4098,11 @@ namespace OpenMetaverse.Messages.Linden
}
}
[Serializable]
public class DirLandReplyMessage : IMessage
{
public UUID AgentID;
public UUID QueryID;
[Serializable]
public class QueryReply
{
public int ActualArea;
@@ -4739,7 +4733,6 @@ namespace OpenMetaverse.Messages.Linden
/// <summary>Base class used for the ObjectMedia message</summary>
[Serializable]
public abstract class ObjectMediaBlock
{
public abstract OSDMap Serialize();
@@ -4941,7 +4934,6 @@ namespace OpenMetaverse.Messages.Linden
/// <summary>
/// Message for setting or getting per face MediaEntry
/// </summary>
[Serializable]
public class ObjectMediaMessage : IMessage
{
/// <summary>The request or response details block</summary>
@@ -5489,7 +5481,6 @@ namespace OpenMetaverse.Messages.Linden
/// <summary>
/// Message received when requesting AgentProfile for an avatar
/// </summary>
[Serializable]
public class AgentProfileMessage : IMessage
{
public class GroupData

View File

@@ -24,8 +24,9 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using MessagePack;
using OpenMetaverse.StructuredData;
using System;
namespace OpenMetaverse
{
@@ -61,13 +62,18 @@ namespace OpenMetaverse
All = 0x1F
}
[Serializable]
[MessagePackObject]
public struct Permissions
{
[Key("BaseMask")]
public PermissionMask BaseMask;
[Key("EveryoneMask")]
public PermissionMask EveryoneMask;
[Key("GroupMask")]
public PermissionMask GroupMask;
[Key("NextOwnerMask")]
public PermissionMask NextOwnerMask;
[Key("OwnerMask")]
public PermissionMask OwnerMask;
public Permissions(uint baseMask, uint everyoneMask, uint groupMask, uint nextOwnerMask, uint ownerMask)

View File

@@ -194,7 +194,6 @@ namespace OpenMetaverse.Rendering
#region Exceptions
[Serializable]
public class RenderingException : Exception
{
public RenderingException(string message)

View File

@@ -29,7 +29,6 @@ using OpenMetaverse.Packets;
namespace OpenMetaverse
{
[Serializable]
public class TerrainPatch
{
#region Enums and Structs

View File

@@ -49,7 +49,6 @@ namespace OpenMetaverse.Packets
/// <summary>
/// Thrown when a packet could not be successfully deserialized
/// </summary>
[Serializable]
public class MalformedDataException : ApplicationException
{
/// <summary>