using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using PhoenixLib.Caching; using PhoenixLib.Logging; using WingsAPI.Communication; using WingsAPI.Communication.DbServer.CharacterService; using WingsAPI.Communication.Player; using WingsAPI.Data.Character; using WingsEmu.Game.Managers; using WingsEmu.Game.Networking; using WingsEmu.Game.Networking.Broadcasting; using WingsEmu.Packets; namespace WingsEmu.Plugins.BasicImplementations.Managers; public class SessionManager : ISessionManager { private static readonly IPacketSerializer Serializer = new PacketSerializer(); private readonly ILongKeyCachedRepository _characterCache; private readonly ConcurrentDictionary _characterCacheByName = new(); private readonly ICharacterService _characterService; private readonly ConcurrentDictionary _isOnlineByCharacterId = new(); private readonly ConcurrentDictionary _isOnlineByCharacterName = new(); private readonly ReaderWriterLockSlim _lock = new(); private readonly ConcurrentQueue _queue = new(); private readonly IServerManager _serverManager; private readonly List _sessionsAll = new(); private readonly ConcurrentDictionary _sessionsByCharacterId = new(); private readonly ConcurrentDictionary _sessionsByCharacterName = new(); public SessionManager(ILongKeyCachedRepository characterCache, IServerManager serverManager, ICharacterService characterService) { _characterCache = characterCache; _serverManager = serverManager; _characterService = characterService; } private IReadOnlyDictionary SessionsByCharacterId => _sessionsByCharacterId; private IReadOnlyDictionary SessionsByCharacterName => _sessionsByCharacterName; public IReadOnlyList Sessions { get { _lock.EnterReadLock(); try { return _sessionsAll.ToArray(); } finally { _lock.ExitReadLock(); } } } public int SessionsCount => _sessionsByCharacterId.Count; public async ValueTask GetOnlineCharacterById(long characterId) { return await _characterCache.GetOrSetAsync(characterId, () => FetchDelegate(characterId), TimeSpan.FromHours(2)); } public ClusterCharacterInfo GetOnlineCharacterByName(string characterName) => _characterCacheByName.TryGetValue(characterName, out ClusterCharacterInfo characterInfo) ? characterInfo : null; public bool IsOnline(string charName) => _isOnlineByCharacterName.ContainsKey(charName); public bool IsOnline(long characterId) => _isOnlineByCharacterId.ContainsKey(characterId); public void AddOnline(ClusterCharacterInfo clusterCharacterInfo) { long characterId = clusterCharacterInfo.Id; string charName = clusterCharacterInfo.Name; _isOnlineByCharacterId[characterId] = true; _isOnlineByCharacterName[charName] = true; _characterCache.Set(clusterCharacterInfo.Id, clusterCharacterInfo, TimeSpan.FromHours(2)); _characterCacheByName[charName] = clusterCharacterInfo; } public void RemoveOnline(string charName, long characterId) { _isOnlineByCharacterId.Remove(characterId, out _); _isOnlineByCharacterName.Remove(charName, out _); _characterCache.Remove(characterId); _characterCacheByName.Remove(charName, out _); } public async Task DisconnectAllAsync() { foreach (IClientSession session in Sessions.ToArray()) { if (session == null) { continue; } if (session.HasSelectedCharacter) { if (session.PlayerEntity.Hp < 1) { session.PlayerEntity.Hp = 1; } } Log.Info($"[SESSION_DISCONNECT] {session.SessionId}:{session.PlayerEntity?.Name}"); session.ForceDisconnect(); } } public IClientSession GetSessionByCharacterName(string name) => SessionsByCharacterName.TryGetValue(name, out IClientSession session) ? session : null; public async Task KickAsync(string characterName) { IClientSession session = GetSessionByCharacterName(characterName); session?.ForceDisconnect(); } public async Task KickAsync(long accountId) { IClientSession session = Sessions.FirstOrDefault(s => s.Account != null && s.Account.Id == accountId); session?.ForceDisconnect(); } public IClientSession GetSessionByCharacterId(long characterId) => SessionsByCharacterId.TryGetValue(characterId, out IClientSession session) ? session : null; public void Broadcast(string packet) { _queue.Enqueue(packet); } public void Broadcast(T packet, params IBroadcastRule[] rules) where T : IServerPacket { _lock.EnterReadLock(); try { foreach (IClientSession session in _sessionsAll) { if (session is null) { continue; } bool all = true; foreach (IBroadcastRule x in rules) { if (!x.Match(session)) { all = false; break; } } if (!rules.Any() || all) { session.SendPacket(packet); } } } finally { _lock.ExitReadLock(); } } public void Broadcast(string packet, params IBroadcastRule[] rules) { _lock.EnterReadLock(); try { foreach (IClientSession session in _sessionsAll) { if (session is null) { continue; } bool all = true; foreach (IBroadcastRule x in rules) { if (!x.Match(session)) { all = false; break; } } if (!rules.Any() || all) { session.SendPacket(packet); } } } finally { _lock.ExitReadLock(); } } public void Broadcast(IEnumerable packets) { foreach (string packet in packets) { _queue.Enqueue(packet); } } public void Broadcast(IEnumerable packets, params IBroadcastRule[] rules) { _lock.EnterReadLock(); try { foreach (IClientSession session in _sessionsAll) { if (session is null) { continue; } bool all = true; foreach (IBroadcastRule x in rules) { if (!x.Match(session)) { all = false; break; } } if (!rules.Any() || all) { session.SendPackets(packets); } } } finally { _lock.ExitReadLock(); } } public void Broadcast(Func generatePacketCallback) { _lock.EnterReadLock(); try { foreach (IClientSession session in _sessionsAll) { if (session is null) { continue; } session.SendPacket(generatePacketCallback(session)); } } finally { _lock.ExitReadLock(); } } public void Broadcast(Func generatePacketCallback, params IBroadcastRule[] rules) { _lock.EnterReadLock(); try { foreach (IClientSession session in _sessionsAll) { if (session is null) { continue; } bool all = true; foreach (IBroadcastRule x in rules) { if (x.Match(session)) { continue; } all = false; break; } if (!rules.Any() || all) { session.SendPacket(generatePacketCallback(session)); } } } finally { _lock.ExitReadLock(); } } public async Task BroadcastAsync(Func> lambdaAsync) { foreach (IClientSession session in Sessions) { if (session is null) { continue; } session.SendPacket(await lambdaAsync(session)); } } public async Task BroadcastAsync(Func> generatePacketCallback, params IBroadcastRule[] rules) { foreach (IClientSession session in Sessions) { if (session is null) { continue; } bool all = true; foreach (IBroadcastRule x in rules) { if (!x.Match(session)) { all = false; break; } } if (!rules.Any() || all) { session.SendPacket(await generatePacketCallback(session)); } } } public void Broadcast(T packet) where T : IPacket { Broadcast(Serializer.Serialize(packet)); } public virtual void RegisterSession(IClientSession session) { if (!session.HasSelectedCharacter) { return; } long id = session.PlayerEntity.Id; string name = session.PlayerEntity.Name; if (!_sessionsByCharacterId.TryGetValue(id, out _)) { _sessionsByCharacterId.TryAdd(session.PlayerEntity.Id, session); } else { _sessionsByCharacterId.TryRemove(session.PlayerEntity.Id, out _); _sessionsByCharacterId.TryAdd(session.PlayerEntity.Id, session); } if (!_sessionsByCharacterName.TryGetValue(name, out _)) { _sessionsByCharacterName.TryAdd(session.PlayerEntity.Name, session); } else { _sessionsByCharacterName.TryRemove(session.PlayerEntity.Name, out _); _sessionsByCharacterName.TryAdd(session.PlayerEntity.Name, session); } _lock.EnterWriteLock(); try { _sessionsAll.Add(session); } finally { _lock.ExitWriteLock(); } _serverManager.TryStart(); } public virtual void UnregisterSession(IClientSession session) { _sessionsByCharacterId.TryRemove(session.PlayerEntity.Id, out _); _sessionsByCharacterName.TryRemove(session.PlayerEntity.Name, out _); _lock.EnterWriteLock(); try { _sessionsAll.Remove(session); } finally { _lock.ExitWriteLock(); } if (_sessionsByCharacterId.IsEmpty) { _serverManager.PutIdle(); } } private async Task FetchDelegate(long characterId) { DbServerGetCharacterResponse response = null; try { response = await _characterService.GetCharacterById(new DbServerGetCharacterByIdRequest { CharacterId = characterId }); } catch (Exception e) { Log.Error("[SESSION_MANAGER] Unexpected error: ", e); } if (response?.RpcResponseType == RpcResponseType.GENERIC_SERVER_ERROR) { Log.Error("[SESSION_MANAGER] FamilyManager", new InvalidOperationException($"Database corrupted: {characterId.ToString()} seems to have been removed but still used somewhere")); } if (response?.RpcResponseType != RpcResponseType.SUCCESS) { return null; } CharacterDTO characterDto = response.CharacterDto; return new ClusterCharacterInfo { Id = characterId, Name = characterDto.Name, Gender = characterDto.Gender, Class = characterDto.Class, Level = characterDto.Level, HeroLevel = characterDto.HeroLevel, MorphId = null, ChannelId = null }; } }