diff --git a/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs b/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs index a11893b..cd62f98 100644 --- a/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs +++ b/LightlessSync/PlayerData/Pairs/IPairPerformanceSubject.cs @@ -2,6 +2,9 @@ using LightlessSync.API.Data; namespace LightlessSync.PlayerData.Pairs; +/// +/// performance metrics for each pair handler +/// public interface IPairPerformanceSubject { string Ident { get; } diff --git a/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs b/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs deleted file mode 100644 index a5c5eff..0000000 --- a/LightlessSync/PlayerData/Pairs/OptionalPluginWarning.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LightlessSync.PlayerData.Pairs; - -public record OptionalPluginWarning -{ - public bool ShownHeelsWarning { get; set; } = false; - public bool ShownCustomizePlusWarning { get; set; } = false; - public bool ShownHonorificWarning { get; set; } = false; - public bool ShownMoodlesWarning { get; set; } = false; - public bool ShowPetNicknamesWarning { get; set; } = false; -} \ No newline at end of file diff --git a/LightlessSync/PlayerData/Pairs/Pair.cs b/LightlessSync/PlayerData/Pairs/Pair.cs index 7709b06..87933ac 100644 --- a/LightlessSync/PlayerData/Pairs/Pair.cs +++ b/LightlessSync/PlayerData/Pairs/Pair.cs @@ -14,6 +14,9 @@ using LightlessSync.WebAPI; namespace LightlessSync.PlayerData.Pairs; +/// +/// ui wrapper around a pair connection +/// public class Pair { private readonly PairLedger _pairLedger; diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs new file mode 100644 index 0000000..7bdfc23 --- /dev/null +++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.Groups.cs @@ -0,0 +1,136 @@ +using LightlessSync.API.Dto.Group; +using Microsoft.Extensions.Logging; + +namespace LightlessSync.PlayerData.Pairs; + +/// +/// handles group related pair events +/// +public sealed partial class PairCoordinator +{ + public void HandleGroupChangePermissions(GroupPermissionDto dto) + { + var result = _pairManager.UpdateGroupPermissions(dto); + if (!result.Success) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update permissions for group {GroupId}: {Error}", dto.Group.GID, result.Error); + } + return; + } + + PublishPairDataChanged(groupChanged: true); + } + + public void HandleGroupFullInfo(GroupFullInfoDto dto) + { + var result = _pairManager.AddGroup(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to add group {GroupId}: {Error}", dto.Group.GID, result.Error); + return; + } + + PublishPairDataChanged(groupChanged: true); + } + + public void HandleGroupPairJoined(GroupPairFullInfoDto dto) + { + var result = _pairManager.AddOrUpdateGroupPair(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to add group pair {Uid}/{Group}: {Error}", dto.User.UID, dto.Group.GID, result.Error); + return; + } + + PublishPairDataChanged(groupChanged: true); + } + + public void HandleGroupPairLeft(GroupPairDto dto) + { + var deregistration = _pairManager.RemoveGroupPair(dto); + if (deregistration.Success && deregistration.Value is { } registration && registration.CharacterIdent is not null) + { + _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); + } + else if (!deregistration.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("RemoveGroupPair failed for {Uid}: {Error}", dto.User.UID, deregistration.Error); + } + + if (deregistration.Success) + { + PublishPairDataChanged(groupChanged: true); + } + } + + public void HandleGroupRemoved(GroupDto dto) + { + var removalResult = _pairManager.RemoveGroup(dto.Group.GID); + if (removalResult.Success) + { + foreach (var registration in removalResult.Value) + { + if (registration.CharacterIdent is not null) + { + _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); + } + } + } + else if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to remove group {Group}: {Error}", dto.Group.GID, removalResult.Error); + } + + if (removalResult.Success) + { + PublishPairDataChanged(groupChanged: true); + } + } + + public void HandleGroupInfoUpdate(GroupInfoDto dto) + { + var result = _pairManager.UpdateGroupInfo(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update group info for {Group}: {Error}", dto.Group.GID, result.Error); + return; + } + + PublishPairDataChanged(groupChanged: true); + } + + public void HandleGroupPairPermissions(GroupPairUserPermissionDto dto) + { + var result = _pairManager.UpdateGroupPairPermissions(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update group pair permissions for {Group}: {Error}", dto.Group.GID, result.Error); + return; + } + + PublishPairDataChanged(groupChanged: true); + } + + public void HandleGroupPairStatus(GroupPairUserInfoDto dto, bool isSelf) + { + PairOperationResult result; + if (isSelf) + { + result = _pairManager.UpdateGroupStatus(dto); + } + else + { + result = _pairManager.UpdateGroupPairStatus(dto); + } + + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update group status for {Group}:{Uid}: {Error}", dto.GID, dto.UID, result.Error); + return; + } + + PublishPairDataChanged(groupChanged: true); + } +} diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs new file mode 100644 index 0000000..5925663 --- /dev/null +++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.Users.cs @@ -0,0 +1,303 @@ +using LightlessSync.API.Data; +using LightlessSync.API.Data.Enum; +using LightlessSync.API.Data.Extensions; +using LightlessSync.API.Dto.User; +using LightlessSync.Services.Events; +using LightlessSync.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace LightlessSync.PlayerData.Pairs; + +/// +/// handles user pair events +/// +public sealed partial class PairCoordinator +{ + public void HandleUserAddPair(UserPairDto dto, bool addToLastAddedUser = true) + { + var result = _pairManager.AddOrUpdateIndividual(dto, addToLastAddedUser); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to add/update pair {Uid}: {Error}", dto.User.UID, result.Error); + return; + } + + PublishPairDataChanged(); + } + + public void HandleUserAddPair(UserFullPairDto dto) + { + var result = _pairManager.AddOrUpdateIndividual(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to add/update full pair {Uid}: {Error}", dto.User.UID, result.Error); + return; + } + + PublishPairDataChanged(); + } + + public void HandleUserRemovePair(UserDto dto) + { + var removal = _pairManager.RemoveIndividual(dto); + if (removal.Success && removal.Value is { } registration && registration.CharacterIdent is not null) + { + _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); + } + else if (!removal.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("RemoveIndividual failed for {Uid}: {Error}", dto.User.UID, removal.Error); + } + + if (removal.Success) + { + _pendingCharacterData.TryRemove(dto.User.UID, out _); + PublishPairDataChanged(); + } + } + + public void HandleUserStatus(UserIndividualPairStatusDto dto) + { + var result = _pairManager.SetIndividualStatus(dto); + if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update individual pair status for {Uid}: {Error}", dto.User.UID, result.Error); + return; + } + + PublishPairDataChanged(); + } + + public void HandleUserOnline(OnlineUserIdentDto dto, bool sendNotification) + { + var wasOnline = false; + PairConnection? previousConnection = null; + if (_pairManager.TryGetPair(dto.User.UID, out var existingConnection)) + { + previousConnection = existingConnection; + wasOnline = existingConnection.IsOnline; + } + + var registrationResult = _pairManager.MarkOnline(dto); + if (!registrationResult.Success) + { + _logger.LogDebug("MarkOnline failed for {Uid}: {Error}", dto.User.UID, registrationResult.Error); + return; + } + + var registration = registrationResult.Value; + if (registration.CharacterIdent is null) + { + _logger.LogDebug("Online registration for {Uid} missing ident.", dto.User.UID); + } + else + { + var handlerResult = _handlerRegistry.RegisterOnlinePair(registration); + if (!handlerResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("RegisterOnlinePair failed for {Uid}: {Error}", dto.User.UID, handlerResult.Error); + } + } + + var connectionResult = _pairManager.GetPair(dto.User.UID); + var connection = connectionResult.Success ? connectionResult.Value : previousConnection; + if (connection is not null) + { + _mediator.Publish(new ClearProfileUserDataMessage(connection.User)); + } + else + { + _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); + } + + if (!wasOnline) + { + NotifyUserOnline(connection, sendNotification); + } + + if (registration.CharacterIdent is not null && + _pendingCharacterData.TryRemove(dto.User.UID, out var pendingData)) + { + var pendingRegistration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), registration.CharacterIdent); + var pendingApply = _handlerRegistry.ApplyCharacterData(pendingRegistration, pendingData); + if (!pendingApply.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Applying pending character data for {Uid} failed: {Error}", dto.User.UID, pendingApply.Error); + } + } + + PublishPairDataChanged(); + } + + public void HandleUserOffline(UserData user) + { + var registrationResult = _pairManager.MarkOffline(user); + if (registrationResult.Success) + { + _pendingCharacterData.TryRemove(user.UID, out _); + if (registrationResult.Value.CharacterIdent is not null) + { + _ = _handlerRegistry.DeregisterOfflinePair(registrationResult.Value); + } + + _mediator.Publish(new ClearProfileUserDataMessage(user)); + PublishPairDataChanged(); + } + else if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("MarkOffline failed for {Uid}: {Error}", user.UID, registrationResult.Error); + } + } + + public void HandleUserPermissions(UserPermissionsDto dto) + { + var pairResult = _pairManager.GetPair(dto.User.UID); + if (!pairResult.Success) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Permission update received for unknown pair {Uid}", dto.User.UID); + } + return; + } + + var connection = pairResult.Value; + var previous = connection.OtherToSelfPermissions; + + var updateResult = _pairManager.UpdateOtherPermissions(dto); + if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error); + return; + } + + PublishPairDataChanged(); + + if (previous.IsPaused() != dto.Permissions.IsPaused()) + { + _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); + + if (connection.Ident is not null) + { + var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused()); + if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error); + } + } + } + + if (!connection.IsPaused && connection.Ident is not null) + { + ReapplyLastKnownData(dto.User.UID, connection.Ident); + } + } + + public void HandleSelfPermissions(UserPermissionsDto dto) + { + var pairResult = _pairManager.GetPair(dto.User.UID); + if (!pairResult.Success) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Self permission update received for unknown pair {Uid}", dto.User.UID); + } + return; + } + + var connection = pairResult.Value; + var previous = connection.SelfToOtherPermissions; + + var updateResult = _pairManager.UpdateSelfPermissions(dto); + if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update self permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error); + return; + } + + PublishPairDataChanged(); + + if (previous.IsPaused() != dto.Permissions.IsPaused()) + { + _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); + + if (connection.Ident is not null) + { + var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused()); + if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error); + } + } + } + + if (!connection.IsPaused && connection.Ident is not null) + { + ReapplyLastKnownData(dto.User.UID, connection.Ident); + } + } + + public void HandleUploadStatus(UserDto dto) + { + var pairResult = _pairManager.GetPair(dto.User.UID); + if (!pairResult.Success) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Upload status received for unknown pair {Uid}", dto.User.UID); + } + return; + } + + var connection = pairResult.Value; + if (connection.Ident is null) + { + return; + } + + var setResult = _handlerRegistry.SetUploading(new PairUniqueIdentifier(dto.User.UID), connection.Ident, true); + if (!setResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Failed to set uploading for {Uid}: {Error}", dto.User.UID, setResult.Error); + } + } + + public void HandleCharacterData(OnlineUserCharaDataDto dto) + { + var pairResult = _pairManager.GetPair(dto.User.UID); + if (!pairResult.Success) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Character data received for unknown pair {Uid}, queued for later.", dto.User.UID); + } + _pendingCharacterData[dto.User.UID] = dto; + return; + } + + var connection = pairResult.Value; + _mediator.Publish(new EventMessage(new Event(connection.User, nameof(PairCoordinator), EventSeverity.Informational, "Received Character Data"))); + if (connection.Ident is null) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Character data received for {Uid} without ident, queued for later.", dto.User.UID); + } + _pendingCharacterData[dto.User.UID] = dto; + return; + } + + _pendingCharacterData.TryRemove(dto.User.UID, out _); + var registration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident); + var applyResult = _handlerRegistry.ApplyCharacterData(registration, dto); + if (!applyResult.Success && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("ApplyCharacterData queued for {Uid}: {Error}", dto.User.UID, applyResult.Error); + } + } + + public void HandleProfile(UserDto dto) + { + _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); + } +} diff --git a/LightlessSync/PlayerData/Pairs/PairCoordinator.cs b/LightlessSync/PlayerData/Pairs/PairCoordinator.cs index ddc4adb..b7af0cd 100644 --- a/LightlessSync/PlayerData/Pairs/PairCoordinator.cs +++ b/LightlessSync/PlayerData/Pairs/PairCoordinator.cs @@ -1,21 +1,19 @@ using System; using System.Collections.Concurrent; -using LightlessSync.API.Data; -using LightlessSync.API.Data.Enum; -using LightlessSync.API.Data.Extensions; -using LightlessSync.API.Dto.CharaData; -using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.User; using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration.Models; -using LightlessSync.Services.Mediator; using LightlessSync.Services.Events; +using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; -public sealed class PairCoordinator : MediatorSubscriberBase +/// +/// wires mediator events into the pair system +/// +public sealed partial class PairCoordinator : MediatorSubscriberBase { private readonly ILogger _logger; private readonly LightlessConfigService _configService; @@ -107,45 +105,6 @@ public sealed class PairCoordinator : MediatorSubscriberBase } } - public void HandleGroupChangePermissions(GroupPermissionDto dto) - { - var result = _pairManager.UpdateGroupPermissions(dto); - if (!result.Success) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update permissions for group {GroupId}: {Error}", dto.Group.GID, result.Error); - } - return; - } - - PublishPairDataChanged(groupChanged: true); - } - - public void HandleGroupFullInfo(GroupFullInfoDto dto) - { - var result = _pairManager.AddGroup(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to add group {GroupId}: {Error}", dto.Group.GID, result.Error); - return; - } - - PublishPairDataChanged(groupChanged: true); - } - - public void HandleGroupPairJoined(GroupPairFullInfoDto dto) - { - var result = _pairManager.AddOrUpdateGroupPair(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to add group pair {Uid}/{Group}: {Error}", dto.User.UID, dto.Group.GID, result.Error); - return; - } - - PublishPairDataChanged(groupChanged: true); - } - private void HandleActiveServerChange(string serverUrl) { if (_logger.IsEnabled(LogLevel.Debug)) @@ -175,379 +134,4 @@ public sealed class PairCoordinator : MediatorSubscriberBase _mediator.Publish(new ClearProfileGroupDataMessage()); PublishPairDataChanged(groupChanged: true); } - - public void HandleGroupPairLeft(GroupPairDto dto) - { - var deregistration = _pairManager.RemoveGroupPair(dto); - if (deregistration.Success && deregistration.Value is { } registration && registration.CharacterIdent is not null) - { - _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); - } - else if (!deregistration.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("RemoveGroupPair failed for {Uid}: {Error}", dto.User.UID, deregistration.Error); - } - - if (deregistration.Success) - { - PublishPairDataChanged(groupChanged: true); - } - } - - public void HandleGroupRemoved(GroupDto dto) - { - var removalResult = _pairManager.RemoveGroup(dto.Group.GID); - if (removalResult.Success) - { - foreach (var registration in removalResult.Value) - { - if (registration.CharacterIdent is not null) - { - _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); - } - } - } - else if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to remove group {Group}: {Error}", dto.Group.GID, removalResult.Error); - } - - if (removalResult.Success) - { - PublishPairDataChanged(groupChanged: true); - } - } - - public void HandleGroupInfoUpdate(GroupInfoDto dto) - { - var result = _pairManager.UpdateGroupInfo(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update group info for {Group}: {Error}", dto.Group.GID, result.Error); - return; - } - - PublishPairDataChanged(groupChanged: true); - } - - public void HandleGroupPairPermissions(GroupPairUserPermissionDto dto) - { - var result = _pairManager.UpdateGroupPairPermissions(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update group pair permissions for {Group}: {Error}", dto.Group.GID, result.Error); - return; - } - - PublishPairDataChanged(groupChanged: true); - } - - public void HandleGroupPairStatus(GroupPairUserInfoDto dto, bool isSelf) - { - PairOperationResult result; - if (isSelf) - { - result = _pairManager.UpdateGroupStatus(dto); - } - else - { - result = _pairManager.UpdateGroupPairStatus(dto); - } - - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update group status for {Group}:{Uid}: {Error}", dto.GID, dto.UID, result.Error); - return; - } - - PublishPairDataChanged(groupChanged: true); - } - - public void HandleUserAddPair(UserPairDto dto, bool addToLastAddedUser = true) - { - var result = _pairManager.AddOrUpdateIndividual(dto, addToLastAddedUser); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to add/update pair {Uid}: {Error}", dto.User.UID, result.Error); - return; - } - - PublishPairDataChanged(); - } - - public void HandleUserAddPair(UserFullPairDto dto) - { - var result = _pairManager.AddOrUpdateIndividual(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to add/update full pair {Uid}: {Error}", dto.User.UID, result.Error); - return; - } - - PublishPairDataChanged(); - } - - public void HandleUserRemovePair(UserDto dto) - { - var removal = _pairManager.RemoveIndividual(dto); - if (removal.Success && removal.Value is { } registration && registration.CharacterIdent is not null) - { - _ = _handlerRegistry.DeregisterOfflinePair(registration, forceDisposal: true); - } - else if (!removal.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("RemoveIndividual failed for {Uid}: {Error}", dto.User.UID, removal.Error); - } - - if (removal.Success) - { - _pendingCharacterData.TryRemove(dto.User.UID, out _); - PublishPairDataChanged(); - } - } - - public void HandleUserStatus(UserIndividualPairStatusDto dto) - { - var result = _pairManager.SetIndividualStatus(dto); - if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update individual pair status for {Uid}: {Error}", dto.User.UID, result.Error); - return; - } - - PublishPairDataChanged(); - } - - public void HandleUserOnline(OnlineUserIdentDto dto, bool sendNotification) - { - var wasOnline = false; - PairConnection? previousConnection = null; - if (_pairManager.TryGetPair(dto.User.UID, out var existingConnection)) - { - previousConnection = existingConnection; - wasOnline = existingConnection.IsOnline; - } - - var registrationResult = _pairManager.MarkOnline(dto); - if (!registrationResult.Success) - { - _logger.LogDebug("MarkOnline failed for {Uid}: {Error}", dto.User.UID, registrationResult.Error); - return; - } - - var registration = registrationResult.Value; - if (registration.CharacterIdent is null) - { - _logger.LogDebug("Online registration for {Uid} missing ident.", dto.User.UID); - } - else - { - var handlerResult = _handlerRegistry.RegisterOnlinePair(registration); - if (!handlerResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("RegisterOnlinePair failed for {Uid}: {Error}", dto.User.UID, handlerResult.Error); - } - } - - var connectionResult = _pairManager.GetPair(dto.User.UID); - var connection = connectionResult.Success ? connectionResult.Value : previousConnection; - if (connection is not null) - { - _mediator.Publish(new ClearProfileUserDataMessage(connection.User)); - } - else - { - _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); - } - - if (!wasOnline) - { - NotifyUserOnline(connection, sendNotification); - } - - if (registration.CharacterIdent is not null && - _pendingCharacterData.TryRemove(dto.User.UID, out var pendingData)) - { - var pendingRegistration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), registration.CharacterIdent); - var pendingApply = _handlerRegistry.ApplyCharacterData(pendingRegistration, pendingData); - if (!pendingApply.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Applying pending character data for {Uid} failed: {Error}", dto.User.UID, pendingApply.Error); - } - } - - PublishPairDataChanged(); - } - - public void HandleUserOffline(UserData user) - { - var registrationResult = _pairManager.MarkOffline(user); - if (registrationResult.Success) - { - _pendingCharacterData.TryRemove(user.UID, out _); - if (registrationResult.Value.CharacterIdent is not null) - { - _ = _handlerRegistry.DeregisterOfflinePair(registrationResult.Value); - } - - _mediator.Publish(new ClearProfileUserDataMessage(user)); - PublishPairDataChanged(); - } - else if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("MarkOffline failed for {Uid}: {Error}", user.UID, registrationResult.Error); - } - } - - public void HandleUserPermissions(UserPermissionsDto dto) - { - var pairResult = _pairManager.GetPair(dto.User.UID); - if (!pairResult.Success) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Permission update received for unknown pair {Uid}", dto.User.UID); - } - return; - } - - var connection = pairResult.Value; - var previous = connection.OtherToSelfPermissions; - - var updateResult = _pairManager.UpdateOtherPermissions(dto); - if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error); - return; - } - - PublishPairDataChanged(); - - if (previous.IsPaused() != dto.Permissions.IsPaused()) - { - _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); - - if (connection.Ident is not null) - { - var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused()); - if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error); - } - } - } - - if (!connection.IsPaused && connection.Ident is not null) - { - ReapplyLastKnownData(dto.User.UID, connection.Ident); - } - } - - public void HandleSelfPermissions(UserPermissionsDto dto) - { - var pairResult = _pairManager.GetPair(dto.User.UID); - if (!pairResult.Success) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Self permission update received for unknown pair {Uid}", dto.User.UID); - } - return; - } - - var connection = pairResult.Value; - var previous = connection.SelfToOtherPermissions; - - var updateResult = _pairManager.UpdateSelfPermissions(dto); - if (!updateResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update self permissions for {Uid}: {Error}", dto.User.UID, updateResult.Error); - return; - } - - PublishPairDataChanged(); - - if (previous.IsPaused() != dto.Permissions.IsPaused()) - { - _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); - - if (connection.Ident is not null) - { - var pauseResult = _handlerRegistry.SetPausedState(new PairUniqueIdentifier(dto.User.UID), connection.Ident, dto.Permissions.IsPaused()); - if (!pauseResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to update pause state for {Uid}: {Error}", dto.User.UID, pauseResult.Error); - } - } - } - - if (!connection.IsPaused && connection.Ident is not null) - { - ReapplyLastKnownData(dto.User.UID, connection.Ident); - } - } - - public void HandleUploadStatus(UserDto dto) - { - var pairResult = _pairManager.GetPair(dto.User.UID); - if (!pairResult.Success) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Upload status received for unknown pair {Uid}", dto.User.UID); - } - return; - } - - var connection = pairResult.Value; - if (connection.Ident is null) - { - return; - } - - var setResult = _handlerRegistry.SetUploading(new PairUniqueIdentifier(dto.User.UID), connection.Ident, true); - if (!setResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Failed to set uploading for {Uid}: {Error}", dto.User.UID, setResult.Error); - } - } - - public void HandleCharacterData(OnlineUserCharaDataDto dto) - { - var pairResult = _pairManager.GetPair(dto.User.UID); - if (!pairResult.Success) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Character data received for unknown pair {Uid}, queued for later.", dto.User.UID); - } - _pendingCharacterData[dto.User.UID] = dto; - return; - } - - var connection = pairResult.Value; - _mediator.Publish(new EventMessage(new Event(connection.User, nameof(PairCoordinator), EventSeverity.Informational, "Received Character Data"))); - if (connection.Ident is null) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Character data received for {Uid} without ident, queued for later.", dto.User.UID); - } - _pendingCharacterData[dto.User.UID] = dto; - return; - } - - _pendingCharacterData.TryRemove(dto.User.UID, out _); - var registration = new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident); - var applyResult = _handlerRegistry.ApplyCharacterData(registration, dto); - if (!applyResult.Success && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("ApplyCharacterData queued for {Uid}: {Error}", dto.User.UID, applyResult.Error); - } - } - - public void HandleProfile(UserDto dto) - { - _mediator.Publish(new ClearProfileUserDataMessage(dto.User)); - } } diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index 2114c35..6950186 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -28,6 +28,9 @@ using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; namespace LightlessSync.PlayerData.Pairs; +/// +/// orchestrates the lifecycle of a paired character +/// public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject { string Ident { get; } diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs b/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs index 6c43119..48d3c9e 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerRegistry.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; @@ -11,12 +10,14 @@ using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; +/// +/// creates, tracks, and removes pair handlers +/// public sealed class PairHandlerRegistry : IDisposable { private readonly object _gate = new(); - private readonly Dictionary _identToHandler = new(StringComparer.Ordinal); - private readonly Dictionary> _handlerToPairs = new(); - private readonly Dictionary _waitingRequests = new(StringComparer.Ordinal); + private readonly Dictionary _entriesByIdent = new(StringComparer.Ordinal); + private readonly Dictionary _entriesByHandler = new(); private readonly IPairHandlerAdapterFactory _handlerFactory; private readonly PairManager _pairManager; @@ -24,7 +25,6 @@ public sealed class PairHandlerRegistry : IDisposable private readonly ILogger _logger; private readonly TimeSpan _deletionGracePeriod = TimeSpan.FromMinutes(5); - private readonly TimeSpan _waitForHandlerGracePeriod = TimeSpan.FromMinutes(2); public PairHandlerRegistry( IPairHandlerAdapterFactory handlerFactory, @@ -42,7 +42,7 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - return _handlerToPairs.Keys.Count(handler => handler.IsVisible); + return _entriesByHandler.Keys.Count(handler => handler.IsVisible); } } @@ -50,7 +50,7 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - return _identToHandler.TryGetValue(ident, out var handler) && handler.IsVisible; + return _entriesByIdent.TryGetValue(ident, out var entry) && entry.Handler.IsVisible; } } @@ -64,16 +64,10 @@ public sealed class PairHandlerRegistry : IDisposable IPairHandlerAdapter handler; lock (_gate) { - handler = GetOrAddHandler(registration.CharacterIdent); + var entry = GetOrCreateEntry(registration.CharacterIdent); + handler = entry.Handler; handler.ScheduledForDeletion = false; - - if (!_handlerToPairs.TryGetValue(handler, out var set)) - { - set = new HashSet(); - _handlerToPairs[handler] = set; - } - - set.Add(registration.PairIdent); + entry.AddPair(registration.PairIdent); } ApplyPauseStateForHandler(handler); @@ -109,25 +103,23 @@ public sealed class PairHandlerRegistry : IDisposable lock (_gate) { - if (!_identToHandler.TryGetValue(registration.CharacterIdent, out handler)) + if (!_entriesByIdent.TryGetValue(registration.CharacterIdent, out var entry)) { return PairOperationResult.Fail($"Ident {registration.CharacterIdent} not registered."); } - if (_handlerToPairs.TryGetValue(handler, out var set)) + handler = entry.Handler; + entry.RemovePair(registration.PairIdent); + if (entry.PairCount == 0) { - set.Remove(registration.PairIdent); - if (set.Count == 0) + if (forceDisposal) { - if (forceDisposal) - { - shouldDisposeImmediately = true; - } - else - { - shouldScheduleRemoval = true; - handler.ScheduledForDeletion = true; - } + shouldDisposeImmediately = true; + } + else + { + shouldScheduleRemoval = true; + handler.ScheduledForDeletion = true; } } } @@ -154,13 +146,7 @@ public sealed class PairHandlerRegistry : IDisposable return PairOperationResult.Fail($"Character data received without ident for {registration.PairIdent.UserId}."); } - IPairHandlerAdapter? handler; - lock (_gate) - { - _identToHandler.TryGetValue(registration.CharacterIdent, out handler); - } - - if (handler is null) + if (!TryGetHandler(registration.CharacterIdent, out var handler) || handler is null) { var registerResult = RegisterOnlinePair(registration); if (!registerResult.Success) @@ -168,30 +154,19 @@ public sealed class PairHandlerRegistry : IDisposable return PairOperationResult.Fail(registerResult.Error); } - lock (_gate) + if (!TryGetHandler(registration.CharacterIdent, out handler) || handler is null) { - _identToHandler.TryGetValue(registration.CharacterIdent, out handler); + return PairOperationResult.Fail($"Handler not ready for {registration.PairIdent.UserId}."); } } - if (handler is null) - { - return PairOperationResult.Fail($"Handler not ready for {registration.PairIdent.UserId}."); - } - handler.ApplyData(dto.CharaData); return PairOperationResult.Ok(); } public PairOperationResult ApplyLastReceivedData(PairUniqueIdentifier pairIdent, string ident, bool forced = false) { - IPairHandlerAdapter? handler; - lock (_gate) - { - _identToHandler.TryGetValue(ident, out handler); - } - - if (handler is null) + if (!TryGetHandler(ident, out var handler) || handler is null) { return PairOperationResult.Fail($"Cannot reapply data: handler for {pairIdent.UserId} not found."); } @@ -202,13 +177,7 @@ public sealed class PairHandlerRegistry : IDisposable public PairOperationResult SetUploading(PairUniqueIdentifier pairIdent, string ident, bool uploading) { - IPairHandlerAdapter? handler; - lock (_gate) - { - _identToHandler.TryGetValue(ident, out handler); - } - - if (handler is null) + if (!TryGetHandler(ident, out var handler) || handler is null) { return PairOperationResult.Fail($"Cannot set uploading for {pairIdent.UserId}: handler not found."); } @@ -219,44 +188,31 @@ public sealed class PairHandlerRegistry : IDisposable public PairOperationResult SetPausedState(PairUniqueIdentifier pairIdent, string ident, bool paused) { - IPairHandlerAdapter? handler; - lock (_gate) - { - _identToHandler.TryGetValue(ident, out handler); - } - - if (handler is null) + if (!TryGetHandler(ident, out var handler) || handler is null) { return PairOperationResult.Fail($"Cannot update pause state for {pairIdent.UserId}: handler not found."); } _ = paused; // value reflected in pair manager already - // Recalculate pause state against all registered pairs to ensure consistency across contexts. ApplyPauseStateForHandler(handler); return PairOperationResult.Ok(); } public PairOperationResult> GetPairConnections(string ident) { - IPairHandlerAdapter? handler; - HashSet? identifiers = null; - + PairHandlerEntry? entry; lock (_gate) { - _identToHandler.TryGetValue(ident, out handler); - if (handler is not null) - { - _handlerToPairs.TryGetValue(handler, out identifiers); - } + _entriesByIdent.TryGetValue(ident, out entry); } - if (handler is null || identifiers is null) + if (entry is null) { return PairOperationResult>.Fail($"No handler registered for {ident}."); } var list = new List<(PairUniqueIdentifier, PairConnection)>(); - foreach (var pairIdent in identifiers) + foreach (var pairIdent in entry.SnapshotPairs()) { var result = _pairManager.GetPair(pairIdent.UserId); if (result.Success) @@ -279,8 +235,8 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - var success = _identToHandler.TryGetValue(ident, out var resolved); - handler = resolved; + var success = _entriesByIdent.TryGetValue(ident, out var entry); + handler = entry?.Handler; return success; } } @@ -289,7 +245,7 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - return _identToHandler.Values.Distinct().ToList(); + return _entriesByHandler.Keys.ToList(); } } @@ -297,9 +253,9 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - if (_handlerToPairs.TryGetValue(handler, out var pairs)) + if (_entriesByHandler.TryGetValue(handler, out var entry)) { - return pairs.ToList(); + return entry.SnapshotPairs(); } } @@ -330,17 +286,9 @@ public sealed class PairHandlerRegistry : IDisposable List handlers; lock (_gate) { - handlers = _identToHandler.Values.Distinct().ToList(); - _identToHandler.Clear(); - _handlerToPairs.Clear(); - - foreach (var pending in _waitingRequests.Values) - { - pending.Cancel(); - pending.Dispose(); - } - - _waitingRequests.Clear(); + handlers = _entriesByHandler.Keys.ToList(); + _entriesByIdent.Clear(); + _entriesByHandler.Clear(); } foreach (var handler in handlers) @@ -364,14 +312,9 @@ public sealed class PairHandlerRegistry : IDisposable List handlers; lock (_gate) { - handlers = _identToHandler.Values.Distinct().ToList(); - _identToHandler.Clear(); - _handlerToPairs.Clear(); - foreach (var kv in _waitingRequests.Values) - { - kv.Cancel(); - } - _waitingRequests.Clear(); + handlers = _entriesByHandler.Keys.ToList(); + _entriesByIdent.Clear(); + _entriesByHandler.Clear(); } foreach (var handler in handlers) @@ -380,46 +323,23 @@ public sealed class PairHandlerRegistry : IDisposable } } - private IPairHandlerAdapter GetOrAddHandler(string ident) + private PairHandlerEntry GetOrCreateEntry(string ident) { - if (_identToHandler.TryGetValue(ident, out var handler)) + if (_entriesByIdent.TryGetValue(ident, out var entry)) { - return handler; + return entry; } - handler = _handlerFactory.Create(ident); - _identToHandler[ident] = handler; - _handlerToPairs[handler] = new HashSet(); - return handler; - } - - private void EnsureInitialized(IPairHandlerAdapter handler) - { - if (handler.Initialized) - { - return; - } - - try - { - handler.Initialize(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to initialize handler for {Ident}", handler.Ident); - } + var handler = _handlerFactory.Create(ident); + entry = new PairHandlerEntry(ident, handler); + _entriesByIdent[ident] = entry; + _entriesByHandler[handler] = entry; + return entry; } private async Task RemoveAfterGracePeriodAsync(IPairHandlerAdapter handler) { - try - { - await Task.Delay(_deletionGracePeriod).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - return; - } + await Task.Delay(_deletionGracePeriod).ConfigureAwait(false); if (TryFinalizeHandlerRemoval(handler)) { @@ -431,63 +351,15 @@ public sealed class PairHandlerRegistry : IDisposable { lock (_gate) { - if (!_handlerToPairs.TryGetValue(handler, out var set) || set.Count > 0) + if (!_entriesByHandler.TryGetValue(handler, out var entry) || entry.HasPairs) { handler.ScheduledForDeletion = false; return false; } - _handlerToPairs.Remove(handler); - _identToHandler.Remove(handler.Ident); - - if (_waitingRequests.TryGetValue(handler.Ident, out var cts)) - { - cts.Cancel(); - cts.Dispose(); - _waitingRequests.Remove(handler.Ident); - } - + _entriesByHandler.Remove(handler); + _entriesByIdent.Remove(entry.Ident); return true; } } - - private async Task WaitThenApplyDataAsync(PairRegistration registration, OnlineUserCharaDataDto dto, CancellationTokenSource cts) - { - var token = cts.Token; - try - { - while (!token.IsCancellationRequested) - { - IPairHandlerAdapter? handler; - lock (_gate) - { - _identToHandler.TryGetValue(registration.CharacterIdent!, out handler); - } - - if (handler is not null && handler.Initialized) - { - handler.ApplyData(dto.CharaData); - break; - } - - await Task.Delay(TimeSpan.FromMilliseconds(500), token).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - // expected - } - finally - { - lock (_gate) - { - if (_waitingRequests.TryGetValue(registration.CharacterIdent!, out var existing) && existing == cts) - { - _waitingRequests.Remove(registration.CharacterIdent!); - } - } - - cts.Dispose(); - } - } } diff --git a/LightlessSync/PlayerData/Pairs/PairLedger.cs b/LightlessSync/PlayerData/Pairs/PairLedger.cs index 1e0e359..1593a7c 100644 --- a/LightlessSync/PlayerData/Pairs/PairLedger.cs +++ b/LightlessSync/PlayerData/Pairs/PairLedger.cs @@ -12,6 +12,9 @@ using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; +/// +/// keeps pair info for ui and reapplication +/// public sealed class PairLedger : DisposableMediatorSubscriberBase { private readonly PairManager _pairManager; diff --git a/LightlessSync/PlayerData/Pairs/PairManager.cs b/LightlessSync/PlayerData/Pairs/PairManager.cs index adbe5b8..95525b3 100644 --- a/LightlessSync/PlayerData/Pairs/PairManager.cs +++ b/LightlessSync/PlayerData/Pairs/PairManager.cs @@ -9,6 +9,9 @@ using LightlessSync.API.Dto.User; namespace LightlessSync.PlayerData.Pairs; +/// +/// in memory state for pairs, groups, and syncshells +/// public sealed class PairManager { private readonly object _gate = new(); diff --git a/LightlessSync/PlayerData/Pairs/PairState.cs b/LightlessSync/PlayerData/Pairs/PairModels.cs similarity index 69% rename from LightlessSync/PlayerData/Pairs/PairState.cs rename to LightlessSync/PlayerData/Pairs/PairModels.cs index 0e2a508..015a2a8 100644 --- a/LightlessSync/PlayerData/Pairs/PairState.cs +++ b/LightlessSync/PlayerData/Pairs/PairModels.cs @@ -7,42 +7,27 @@ using LightlessSync.API.Dto.Group; namespace LightlessSync.PlayerData.Pairs; -public readonly struct PairOperationResult +/// +/// core models for the pair system +/// +public sealed class PairState { - private PairOperationResult(bool success, string? error) - { - Success = success; - Error = error; - } + public CharacterData? CharacterData { get; set; } + public Guid? TemporaryCollectionId { get; set; } - public bool Success { get; } - public string? Error { get; } - - public static PairOperationResult Ok() => new(true, null); - - public static PairOperationResult Fail(string error) => new(false, error); + public bool IsEmpty => CharacterData is null && (TemporaryCollectionId is null || TemporaryCollectionId == Guid.Empty); } -public readonly struct PairOperationResult -{ - private PairOperationResult(bool success, T value, string? error) - { - Success = success; - Value = value; - Error = error; - } - - public bool Success { get; } - public T Value { get; } - public string? Error { get; } - - public static PairOperationResult Ok(T value) => new(true, value, null); - - public static PairOperationResult Fail(string error) => new(false, default!, error); -} +public readonly record struct PairUniqueIdentifier(string UserId); +/// +/// link between a pair id and character ident +/// public sealed record PairRegistration(PairUniqueIdentifier PairIdent, string? CharacterIdent); +/// +/// per group membership info for a pair +/// public sealed class GroupPairRelationship { public GroupPairRelationship(string groupId, GroupPairUserInfo? info) @@ -60,6 +45,9 @@ public sealed class GroupPairRelationship } } +/// +/// runtime view of a single pair connection +/// public sealed class PairConnection { public PairConnection(UserData user) @@ -121,6 +109,9 @@ public sealed class PairConnection } } +/// +/// syncshell metadata plus member connections +/// public sealed class Syncshell { public Syncshell(GroupFullInfoDto dto) @@ -138,12 +129,94 @@ public sealed class Syncshell } } -public sealed class PairState +/// +/// simple success/failure result +/// +public readonly struct PairOperationResult { - public CharacterData? CharacterData { get; set; } - public Guid? TemporaryCollectionId { get; set; } + private PairOperationResult(bool success, string? error) + { + Success = success; + Error = error; + } - public bool IsEmpty => CharacterData is null && (TemporaryCollectionId is null || TemporaryCollectionId == Guid.Empty); + public bool Success { get; } + public string? Error { get; } + + public static PairOperationResult Ok() => new(true, null); + + public static PairOperationResult Fail(string error) => new(false, error); } -public readonly record struct PairUniqueIdentifier(string UserId); +/// +/// typed success/failure result +/// +public readonly struct PairOperationResult +{ + private PairOperationResult(bool success, T value, string? error) + { + Success = success; + Value = value; + Error = error; + } + + public bool Success { get; } + public T Value { get; } + public string? Error { get; } + + public static PairOperationResult Ok(T value) => new(true, value, null); + + public static PairOperationResult Fail(string error) => new(false, default!, error); +} + +/// +/// state of which optional plugin warnings were shown +/// +public record OptionalPluginWarning +{ + public bool ShownHeelsWarning { get; set; } = false; + public bool ShownCustomizePlusWarning { get; set; } = false; + public bool ShownHonorificWarning { get; set; } = false; + public bool ShownMoodlesWarning { get; set; } = false; + public bool ShowPetNicknamesWarning { get; set; } = false; +} + +/// +/// tracks the handler registered pairs for an ident +/// +internal sealed class PairHandlerEntry +{ + private readonly HashSet _pairs = new(); + + public PairHandlerEntry(string ident, IPairHandlerAdapter handler) + { + Ident = ident; + Handler = handler; + } + + public string Ident { get; } + public IPairHandlerAdapter Handler { get; } + + public bool HasPairs => _pairs.Count > 0; + public int PairCount => _pairs.Count; + + public void AddPair(PairUniqueIdentifier pair) + { + _pairs.Add(pair); + } + + public bool RemovePair(PairUniqueIdentifier pair) + { + return _pairs.Remove(pair); + } + + public IReadOnlyCollection SnapshotPairs() + { + if (_pairs.Count == 0) + { + return Array.Empty(); + } + + return _pairs.ToArray(); + } +} diff --git a/LightlessSync/PlayerData/Pairs/PairStateCache.cs b/LightlessSync/PlayerData/Pairs/PairStateCache.cs index 67e8c8c..b6a7872 100644 --- a/LightlessSync/PlayerData/Pairs/PairStateCache.cs +++ b/LightlessSync/PlayerData/Pairs/PairStateCache.cs @@ -6,6 +6,9 @@ using LightlessSync.Utils; namespace LightlessSync.PlayerData.Pairs; +/// +/// cache for character/pair data and penumbra collections +/// public sealed class PairStateCache { private readonly ConcurrentDictionary _cache = new(StringComparer.Ordinal); diff --git a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs index 1840813..805bc26 100644 --- a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs +++ b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs @@ -14,6 +14,9 @@ using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; +/// +/// pushes character data to visible pairs +/// public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase { private readonly ApiController _apiController;