Update 0.1.4 - AutoDetect WIP Debug & Fix UI & Optimization

This commit is contained in:
2025-09-11 22:37:29 +02:00
parent 95d9f65068
commit b79a51748f
10 changed files with 211 additions and 34 deletions

View File

@@ -1,6 +1,9 @@
using Microsoft.Extensions.Logging;
using MareSynchronos.WebAPI.AutoDetect;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
namespace MareSynchronos.Services.AutoDetect;
@@ -10,13 +13,17 @@ public class AutoDetectRequestService
private readonly DiscoveryConfigProvider _configProvider;
private readonly DiscoveryApiClient _client;
private readonly MareConfigService _configService;
private readonly DalamudUtilService _dalamud;
private readonly MareMediator _mediator;
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService)
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService)
{
_logger = logger;
_configProvider = configProvider;
_client = client;
_configService = configService;
_mediator = mediator;
_dalamud = dalamudUtilService;
}
public async Task<bool> SendRequestAsync(string token, CancellationToken ct = default)
@@ -24,14 +31,34 @@ public class AutoDetectRequestService
if (!_configService.Current.AllowAutoDetectPairRequests)
{
_logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled");
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
return false;
}
var endpoint = _configProvider.RequestEndpoint;
if (string.IsNullOrEmpty(endpoint))
{
_logger.LogDebug("No request endpoint configured");
_mediator.Publish(new NotificationMessage("Nearby request failed", "Server does not expose request endpoint.", NotificationType.Error));
return false;
}
return await _client.SendRequestAsync(endpoint!, token, ct).ConfigureAwait(false);
string? displayName = null;
try
{
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
displayName = me?.Name.TextValue;
}
catch { }
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false);
if (ok)
{
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
}
else
{
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
}
return ok;
}
}

View File

@@ -50,7 +50,7 @@ public class DiscoveryConfigProvider
root.NearbyDiscovery?.Hydrate();
_config = root;
_lastLoad = DateTimeOffset.UtcNow;
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}", NearbyEnabled);
_logger.LogInformation("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
return true;
}
catch (Exception ex)

View File

@@ -25,6 +25,9 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
private bool _loggedLocalOnly;
private int _lastLocalCount = -1;
private int _lastMatchCount = -1;
private bool _loggedConfigReady;
private string? _lastSnapshotSig;
private volatile bool _isConnected;
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
@@ -44,7 +47,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
public Task StartAsync(CancellationToken cancellationToken)
{
_loopCts = new CancellationTokenSource();
_mediator.Subscribe<ConnectedMessage>(this, _ => _configProvider.TryLoadFromStapled());
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
_ = Task.Run(() => Loop(_loopCts.Token));
return Task.CompletedTask;
}
@@ -65,7 +69,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
{
try
{
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn)
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected)
{
await Task.Delay(1000, ct).ConfigureAwait(false);
continue;
@@ -79,6 +83,11 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
}
}
else if (!_loggedConfigReady && _configProvider.NearbyEnabled)
{
_loggedConfigReady = true;
_logger.LogInformation("Nearby: well-known loaded and enabled; refresh={refresh}s, expires={exp}", _configProvider.RefreshSec, _configProvider.SaltExpiresAt);
}
var entries = await GetLocalNearbyAsync().ConfigureAwait(false);
// Log when local count changes (including 0) to indicate activity
@@ -95,7 +104,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
try
{
var saltHex = Convert.ToHexString(_configProvider.Salt!);
// map hash->index for result matching and reuse for publish
// map hash->index for result matching
Dictionary<string, int> hashToIndex = new(StringComparer.Ordinal);
List<string> hashes = new(entries.Count);
foreach (var (entry, idx) in entries.Select((e, i) => (e, i)))
@@ -105,35 +114,63 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
hashes.Add(h);
}
// Publish local snapshot if endpoint is available (deduplicated)
// Debug snapshot once per change
try
{
var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256();
if (!string.Equals(snapSig, _lastSnapshotSig, StringComparison.Ordinal))
{
_lastSnapshotSig = snapSig;
var sample = entries.Take(5).Select(e =>
{
var hh = (saltHex + e.Name + e.WorldId.ToString()).GetHash256();
var shortH = hh.Length > 8 ? hh[..8] : hh;
return $"{e.Name}({e.WorldId})->{shortH}";
});
var saltShort = saltHex.Length > 8 ? saltHex[..8] : saltHex;
_logger.LogInformation("Nearby snapshot: {count} entries; salt={saltShort}…; samples=[{samples}]",
entries.Count, saltShort, string.Join(", ", sample));
}
}
catch { }
// Publish OUR presence (own hash) if endpoint is available (deduplicated)
if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint))
{
string? displayName = null;
string? selfHash = null;
try
{
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
if (me != null)
{
displayName = me.Name.TextValue;
ushort meWorld = 0;
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
meWorld = (ushort)mePc.HomeWorld.RowId;
_logger.LogInformation("Nearby self ident: {name} ({world})", displayName, meWorld);
selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
}
}
catch { /* ignore */ }
if (hashes.Count > 0)
if (!string.IsNullOrEmpty(selfHash))
{
var sig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256();
var sig = selfHash!;
if (!string.Equals(sig, _lastPublishedSignature, StringComparison.Ordinal))
{
_lastPublishedSignature = sig;
_logger.LogDebug("Nearby publish: {count} hashes (updated)", hashes.Count);
_ = _api.PublishAsync(_configProvider.PublishEndpoint!, hashes, displayName, ct);
var shortSelf = selfHash!.Length > 8 ? selfHash[..8] : selfHash;
_logger.LogInformation("Nearby publish: self presence updated (hash={hash})", shortSelf);
var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct).ConfigureAwait(false);
_logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed");
}
else
{
_logger.LogDebug("Nearby publish skipped (no changes)");
}
}
// else: no local entries; skip publish silently
// else: no self character available; skip publish silently
}
// Query for matches if endpoint is available
@@ -160,6 +197,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
}
}
}
_logger.LogInformation("Nearby: server returned {count} matches", allMatches.Count);
// Log change in number of Umbra matches
int matchCount = entries.Count(e => e.IsMatch);
@@ -206,7 +244,8 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
var localPos = local?.Position ?? Vector3.Zero;
int maxDist = Math.Clamp(_config.Current.AutoDetectMaxDistanceMeters, 5, 100);
for (int i = 0; i < 200; i += 2)
int limit = Math.Min(200, _objectTable.Length);
for (int i = 0; i < limit; i++)
{
var obj = await _dalamud.RunOnFrameworkThread(() => _objectTable[i]).ConfigureAwait(false);
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
@@ -215,7 +254,7 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
if (!float.IsNaN(dist) && dist > maxDist) continue;
string name = obj.Name.ToString();
string name = obj.Name.TextValue;
ushort worldId = 0;
if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc)
worldId = (ushort)pc.HomeWorld.RowId;

View File

@@ -0,0 +1,70 @@
using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using MareSynchronos.Services.Mediator;
using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services.AutoDetect;
public sealed class NearbyPendingService : IMediatorSubscriber
{
private readonly ILogger<NearbyPendingService> _logger;
private readonly MareMediator _mediator;
private readonly ApiController _api;
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api)
{
_logger = logger;
_mediator = mediator;
_api = api;
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
}
public MareMediator Mediator => _mediator;
public IReadOnlyDictionary<string, string> Pending => _pending;
private void OnNotification(NotificationMessage msg)
{
// Watch info messages for Nearby request pattern
if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return;
var m = ReqRegex.Match(msg.Message);
if (!m.Success) return;
var uid = m.Groups["uid"].Value;
if (string.IsNullOrEmpty(uid)) return;
// Try to extract name as everything before space and '['
var name = msg.Message;
try
{
var idx = msg.Message.IndexOf(':');
if (idx >= 0) name = msg.Message[(idx + 1)..].Trim();
var br = name.LastIndexOf('[');
if (br > 0) name = name[..br].Trim();
}
catch { name = uid; }
_pending[uid] = name;
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
}
public void Remove(string uid)
{
_pending.TryRemove(uid, out _);
}
public async Task<bool> AcceptAsync(string uid)
{
try
{
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
_pending.TryRemove(uid, out _);
return true;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "NearbyPending: accept failed for {uid}", uid);
return false;
}
}
}