All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
2.0.0 Changes: - Reworked shell finder UI with compact or list view with profile tags showing with the listing, allowing moderators to broadcast the syncshell as well to have it be used more. - Reworked user list in syncshell admin screen to have filter visible and moved away from table to its own thing, allowing to copy uid/note/alias when clicking on the name. - Reworked download bars and download box to make it look more modern, removed the jitter around, so it shouldn't vibrate around much. - Chat has been added to the top menu, working in Zone or in Syncshells to be used there. - Paired system has been revamped to make pausing and unpausing faster, and loading people should be faster as well. - Moved to the internal object table to have faster load times for users; people should load in faster - Compactor is running on a multi-threaded level instead of single-threaded; this should increase the speed of compacting files - Nameplate Service has been reworked so it wouldn't use the nameplate handler anymore. - Files can be resized when downloading to reduce load on users if they aren't compressed. (can be toggled to resize all). - Penumbra Collections are now only made when people are visible, reducing the load on boot-up when having many syncshells in your list. - Lightfinder plates have been moved away from using Nameplates, but will use an overlay. - Main UI has been changed a bit with a gradient, and on hover will glow up now. - Reworked Profile UI for Syncshell and Users to be more user-facing with more customizable items. - Reworked Settings UI to look more modern. - Performance should be better due to new systems that would dispose of the collections and better caching of items. Co-authored-by: defnotken <itsdefnotken@gmail.com> Co-authored-by: azyges <aaaaaa@aaa.aaa> Co-authored-by: choco <choco@patat.nl> Co-authored-by: cake <admin@cakeandbanana.nl> Co-authored-by: Minmoose <KennethBohr@outlook.com> Reviewed-on: #92
198 lines
7.8 KiB
C#
198 lines
7.8 KiB
C#
using System.Collections.Concurrent;
|
|
using Dalamud.Plugin;
|
|
using LightlessSync.Interop.Ipc.Framework;
|
|
using LightlessSync.Services;
|
|
using LightlessSync.Services.Mediator;
|
|
using Microsoft.Extensions.Logging;
|
|
using Penumbra.Api.Enums;
|
|
using Penumbra.Api.IpcSubscribers;
|
|
|
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
|
|
|
public sealed class PenumbraCollections : PenumbraBase
|
|
{
|
|
private readonly CreateTemporaryCollection _createNamedTemporaryCollection;
|
|
private readonly AssignTemporaryCollection _assignTemporaryCollection;
|
|
private readonly DeleteTemporaryCollection _removeTemporaryCollection;
|
|
private readonly AddTemporaryMod _addTemporaryMod;
|
|
private readonly RemoveTemporaryMod _removeTemporaryMod;
|
|
private readonly GetCollections _getCollections;
|
|
private readonly ConcurrentDictionary<Guid, string> _activeTemporaryCollections = new();
|
|
|
|
private int _cleanupScheduled;
|
|
|
|
public PenumbraCollections(
|
|
ILogger logger,
|
|
IDalamudPluginInterface pluginInterface,
|
|
DalamudUtilService dalamudUtil,
|
|
LightlessMediator mediator) : base(logger, pluginInterface, dalamudUtil, mediator)
|
|
{
|
|
_createNamedTemporaryCollection = new CreateTemporaryCollection(pluginInterface);
|
|
_assignTemporaryCollection = new AssignTemporaryCollection(pluginInterface);
|
|
_removeTemporaryCollection = new DeleteTemporaryCollection(pluginInterface);
|
|
_addTemporaryMod = new AddTemporaryMod(pluginInterface);
|
|
_removeTemporaryMod = new RemoveTemporaryMod(pluginInterface);
|
|
_getCollections = new GetCollections(pluginInterface);
|
|
}
|
|
|
|
public override string Name => "Penumbra.Collections";
|
|
|
|
public async Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var result = _assignTemporaryCollection.Invoke(collectionId, objectIndex, forceAssignment: true);
|
|
logger.LogTrace("Assigning Temp Collection {CollectionId} to index {ObjectIndex}, Success: {Result}", collectionId, objectIndex, result);
|
|
return result;
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
|
{
|
|
if (!IsAvailable)
|
|
{
|
|
return Guid.Empty;
|
|
}
|
|
|
|
var (collectionId, collectionName) = await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var name = $"Lightless_{uid}";
|
|
_createNamedTemporaryCollection.Invoke(name, name, out var tempCollectionId);
|
|
logger.LogTrace("Creating Temp Collection {CollectionName}, GUID: {CollectionId}", name, tempCollectionId);
|
|
return (tempCollectionId, name);
|
|
}).ConfigureAwait(false);
|
|
|
|
if (collectionId != Guid.Empty)
|
|
{
|
|
_activeTemporaryCollections[collectionId] = collectionName;
|
|
}
|
|
|
|
return collectionId;
|
|
}
|
|
|
|
public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Removing temp collection for {CollectionId}", applicationId, collectionId);
|
|
var result = _removeTemporaryCollection.Invoke(collectionId);
|
|
logger.LogTrace("[{ApplicationId}] RemoveTemporaryCollection: {Result}", applicationId, result);
|
|
}).ConfigureAwait(false);
|
|
|
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
|
}
|
|
|
|
public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, IReadOnlyDictionary<string, string> modPaths)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
foreach (var mod in modPaths)
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Change: {From} => {To}", applicationId, mod.Key, mod.Value);
|
|
}
|
|
|
|
var removeResult = _removeTemporaryMod.Invoke("LightlessChara_Files", collectionId, 0);
|
|
logger.LogTrace("[{ApplicationId}] Removing temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, removeResult);
|
|
|
|
var addResult = _addTemporaryMod.Invoke("LightlessChara_Files", collectionId, new Dictionary<string, string>(modPaths), string.Empty, 0);
|
|
logger.LogTrace("[{ApplicationId}] Setting temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, addResult);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData)
|
|
{
|
|
if (!IsAvailable || collectionId == Guid.Empty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
logger.LogTrace("[{ApplicationId}] Manip: {Data}", applicationId, manipulationData);
|
|
var result = _addTemporaryMod.Invoke("LightlessChara_Meta", collectionId, [], manipulationData, 0);
|
|
logger.LogTrace("[{ApplicationId}] Setting temp meta mod for {CollectionId}, Success: {Result}", applicationId, collectionId, result);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
|
{
|
|
if (current == IpcConnectionState.Available)
|
|
{
|
|
ScheduleCleanup();
|
|
}
|
|
else if (previous == IpcConnectionState.Available && current != IpcConnectionState.Available)
|
|
{
|
|
Interlocked.Exchange(ref _cleanupScheduled, 0);
|
|
}
|
|
}
|
|
|
|
private void ScheduleCleanup()
|
|
{
|
|
if (Interlocked.Exchange(ref _cleanupScheduled, 1) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_ = Task.Run(CleanupTemporaryCollectionsAsync);
|
|
}
|
|
|
|
private async Task CleanupTemporaryCollectionsAsync()
|
|
{
|
|
if (!IsAvailable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var collections = await DalamudUtil.RunOnFrameworkThread(() => _getCollections.Invoke()).ConfigureAwait(false);
|
|
foreach (var (collectionId, name) in collections)
|
|
{
|
|
if (!IsLightlessCollectionName(name) || _activeTemporaryCollections.ContainsKey(collectionId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Logger.LogDebug("Cleaning up stale temporary collection {CollectionName} ({CollectionId})", name, collectionId);
|
|
var deleteResult = await DalamudUtil.RunOnFrameworkThread(() =>
|
|
{
|
|
var result = (PenumbraApiEc)_removeTemporaryCollection.Invoke(collectionId);
|
|
Logger.LogTrace("Cleanup RemoveTemporaryCollection result for {CollectionName} ({CollectionId}): {Result}", name, collectionId, result);
|
|
return result;
|
|
}).ConfigureAwait(false);
|
|
|
|
if (deleteResult == PenumbraApiEc.Success)
|
|
{
|
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
|
}
|
|
else
|
|
{
|
|
Logger.LogDebug("Skipped removing temporary collection {CollectionName} ({CollectionId}). Result: {Result}", name, collectionId, deleteResult);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogWarning(ex, "Failed to clean up Penumbra temporary collections");
|
|
}
|
|
}
|
|
|
|
private static bool IsLightlessCollectionName(string? name)
|
|
=> !string.IsNullOrEmpty(name) && name.StartsWith("Lightless_", StringComparison.Ordinal);
|
|
}
|