Added CreateLinksAsync and AddLinks #158
Conversation
…inks in a single request when AIS3 is supported which drastically reduces the competition time when adding 5+ links, i.e. for adding/replacing outfits Fixes a minor bug in ReplaceOutfit that will create a folder link for folders that are not in children of "Outfits", where its not desired to do a folder link (folder Links are only used as indicator for the currently active appearance in SL Viewers Appearance tab)
There was a problem hiding this comment.
Pull request overview
This PR adds AIS3-based batched inventory/COF link creation to speed up outfit changes, and adjusts ReplaceOutfit so the special folder link is only created for folders under the Outfits system folder.
Changes:
- Added
InventoryManager.CreateLinksAsyncfor creating multiple inventory links in one AIS3 request. - Added
CurrentOutfitFolder.AddLinksand switched outfit operations to use batched link creation where possible. - Updated
ReplaceOutfitto avoid creating COF folder links for non-Outfits descendants.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
LibreMetaverse/Inventory/InventoryManager.cs |
Adds the new AIS3 batch link creation API. |
LibreMetaverse/Appearance/CurrentOutfitFolder.cs |
Uses batched COF link creation and tightens outfit-folder link behavior in outfit replacement/add flows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (client.AisClient.IsAvailable) | ||
| { | ||
| await client.Inventory.CreateLinksAsync(COF.UUID, items, success => | ||
| { | ||
| client.Inventory.RequestFolderContents( | ||
| COF.UUID, | ||
| COF.OwnerID, | ||
| fetchFolders: true, | ||
| fetchItems: true, | ||
| order: InventorySortOrder.ByName, | ||
| cancellationToken: cancellationToken | ||
| ).ConfigureAwait(false); | ||
| }, cancellationToken); | ||
| } | ||
| else | ||
| { | ||
| foreach (InventoryItem item in items) | ||
| { | ||
| await AddLink(item, cancellationToken); | ||
| } | ||
| } |
There was a problem hiding this comment.
The whole point of adding AddLinks is to update all the links in a single request, instead of a long sequence request/reply/request/reply which adds tons of latency, about half a second per link created.
| OSDArray links = new OSDArray(); | ||
| foreach (InventoryBase baseItem in items) | ||
| { | ||
| switch (baseItem) | ||
| { | ||
| case InventoryItem item: | ||
| { | ||
| OSDMap link = new OSDMap | ||
| { | ||
| ["linked_id"] = OSD.FromUUID(item.UUID), | ||
| ["type"] = OSD.FromInteger((int)AssetType.Link), | ||
| ["inv_type"] = OSD.FromInteger((int)item.InventoryType), | ||
| ["name"] = OSD.FromString(item.Name), | ||
| ["desc"] = OSD.FromString(item.Description) | ||
| }; | ||
|
|
||
| links.Add(link); | ||
| break; | ||
| } | ||
| case InventoryFolder folder: | ||
| { | ||
| OSDMap link = new OSDMap | ||
| { | ||
| ["linked_id"] = OSD.FromUUID(folder.UUID), | ||
| ["type"] = OSD.FromInteger((int)AssetType.LinkFolder), | ||
| ["inv_type"] = OSD.FromInteger((int)InventoryType.Folder), | ||
| ["name"] = OSD.FromString(folder.Name), | ||
| ["desc"] = OSD.FromString(string.Empty) | ||
| }; | ||
|
|
||
| links.Add(link); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| OSDMap newInventory = new OSDMap { { "links", links } }; | ||
|
|
||
| await Client.AisClient.CreateInventory( | ||
| folderID, | ||
| newInventory, | ||
| true, | ||
| (success, reply) => callback?.Invoke(success), | ||
| cancellationToken | ||
| ).ConfigureAwait(false); |
There was a problem hiding this comment.
Love it when bots butcher code with non-sense.
@cinderblocks
Would calling client.Inventory.RequestFetchInventoryAsync(newItem.UUID, newItem.OwnerID, cancellationToken); in the client.Inventory.CreateLinksAsync(COF.UUID, newLinks, callback be sufficient? I'm not sure it will fetch folders like RequestFolderContents does.
Though in my personal testing I didn't notice any misssing. The COF Folder and the folder containing the orignal items do update on my Radegast Viewer and show the proper attachment/worn labels
There was a problem hiding this comment.
I believe that would be sufficient. The only issue might be on non-AISv3 regions (none exist in Second Life for about ten years, of course). Not sure how modern the inventory API is in OpenSimulator.
There was a problem hiding this comment.
It's not modern at all. Core doesn't implement AISv3 and has no plans to. I'd like to add it to NGC but still trying to just get the basic architecture compliant with dotnet v8+. So if supporting OpenSim is a goal the code will have to handle cases where AIS isnt present at least for some time.
There was a problem hiding this comment.
The actual AddLinks in COF has a fallback to iterate over the list and call AddLink (current code), when AISv3 isn't supported. But the CreateLinksAsync itself doesn't throws if unsupported. I can't even remember where I found the specifications of the endpoint back then.
Tbh I don't know if the older APIs implement batch Link creation or not. I implemented it years ago (2017 or 2018) or so in my local fork, because back then libremetaverise/libopenmetaverse was rather unmaintained and dead.
But until few weeks ago it was still sync, but when rebasing my libremetavers and Radegast i noticing that replacing outfits took forever again (5-10+ seconds) and took me a while to figure out what changed/broke it.
My main movitation back then was to make changing outfits faster, as changing an outfit did freeze the client up to 15-30 seconds (on a Duo Core back in 2017),making it completley unresponsive and slow during this process (also when done via RLV). Today it doesn't freeze the client, but its still slow because of multiple sequential async operations.
| else | ||
| { | ||
| throw new InvalidOperationException("Creating of batch links only supported on AIS3"); |
| public async Task AddLinks(IEnumerable<InventoryItem> items, CancellationToken cancellationToken = default) | ||
| { | ||
| if (COF == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (items == null || !items.Any()) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (client.AisClient.IsAvailable) | ||
| { | ||
| await client.Inventory.CreateLinksAsync(COF.UUID, items, success => | ||
| { | ||
| client.Inventory.RequestFolderContents( | ||
| COF.UUID, | ||
| COF.OwnerID, | ||
| fetchFolders: true, | ||
| fetchItems: true, | ||
| order: InventorySortOrder.ByName, | ||
| cancellationToken: cancellationToken | ||
| ).ConfigureAwait(false); | ||
| }, cancellationToken); |
…Outfit folder when calling AddLinks
9b17943 to
0f97651
Compare
Added CreateLinksAsync and AddLinks which allows to create multiple Links in a single request when AIS3 is supported which drastically reduces the competition time when adding 5+ links, i.e. for adding/replacing outfits
Fixes a minor bug in ReplaceOutfit that will create a folder link for folders that are not in children of "Outfits", where its not desired to do a folder link (folder Links are only used as indicator for the currently active appearance in SL Viewers Appearance tab).
In my tests even moderately sized outfits of 17 items took 5-10 seconds until the outfit was fully work which did drive me insane.
With the batched AddLinks AIS3 Request, the outfit switchs and appears fully in-world (assuming work items existed in the Viewers Cache already) is less than 1 second now. It falls back to one request per link if AIS3 isn't supported. Dunno if the old API has similar batching capabilities.