namespace Supercell.Laser.Server.Database.Cache { using Supercell.Laser.Logic.Club; using System.Collections.Concurrent; using System.Threading.Tasks; public static class AllianceCache { private static ConcurrentDictionary CachedAlliances; private static Timer SaveTimer; private static volatile bool _isRunning; public static bool Started { get => _isRunning; set => _isRunning = value; } public static int Count => CachedAlliances.Count; public static void Init() { CachedAlliances = new ConcurrentDictionary(); _isRunning = true; SaveTimer = new Timer(SaveAllParallel, null, 90000, 90000); } public static void Shutdown() { _isRunning = false; SaveTimer?.Dispose(); SaveAllParallel(null); } public static void SaveAll() { SaveAllParallel(null); } private static void SaveAllParallel(object state) { if (!_isRunning || CachedAlliances.IsEmpty) return; try { var alliances = CachedAlliances.Values.ToList(); int total = alliances.Count; if (total == 0) return; int batchSize = Math.Max(1, total / Environment.ProcessorCount); var batches = alliances.Chunk(batchSize); Parallel.ForEach(batches, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, batch => { foreach (var alliance in batch) { try { Alliances.Save(alliance); } catch (Exception ex) { Logger.Print($"Save error for alliance {alliance.Id}: {ex.Message}"); } } }); } catch (Exception ex) { Logger.Print($"SaveAll error: {ex.Message}"); } } public static bool IsAllianceCached(long id) { return CachedAlliances.ContainsKey(id); } public static Alliance GetAlliance(long id) { CachedAlliances.TryGetValue(id, out var alliance); return alliance; } public static void RemoveAlliance(long id) { CachedAlliances.TryRemove(id, out _); } // Fix 9: SaveAndEvict по образцу AccountCache.SaveAndEvict. // Используется там, где альянс был загружен только для разового lookup-а // (например, GetRandomAlliances) и не должен оставаться в кеше бессрочно. // Без явного вытеснения таймер SaveAllParallel продолжает сериализовывать // и писать в БД все когда-либо затронутые альянсы, а не только «живые». public static void SaveAndEvict(long allianceId) { if (CachedAlliances.TryRemove(allianceId, out var alliance) && alliance != null) { try { Alliances.Save(alliance); } catch (Exception ex) { Logger.Print($"AllianceCache.SaveAndEvict error for {allianceId}: {ex.Message}"); } } } // Fix 9: Вытеснение без сохранения — для объектов, которые мы загрузили // только на чтение (например, показ в поиске) и не меняли. Сохранять нечего, // но держать в кеше тоже незачем. public static void EvictWithoutSave(long allianceId) { CachedAlliances.TryRemove(allianceId, out _); } public static void Cache(Alliance alliance) { CachedAlliances[alliance.Id] = alliance; } } }