using System.Collections.Concurrent; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; namespace SPTarkov.Server.Core.Services; [Injectable(InjectionType.Singleton)] public class RagfairRequiredItemsService(RagfairOfferService ragfairOfferService, PaymentHelper paymentHelper) { private readonly Lock _createCacheLock = new(); private volatile bool _cacheIsStale = true; /// /// Key = tpl, Value = offerIds /// private ConcurrentDictionary> _requiredItemsCache = new(); /// /// Empty hashset to be returned when no keys found by GetRequiredOffersById (reduces memory allocations) /// private readonly IReadOnlySet _emptyOfferIdSet = new HashSet(); /// /// Get the offerId of offers that require the supplied tpl /// /// Tpl to find offers ids for /// Set of OfferIds public IReadOnlySet GetRequiredOffersById(MongoId tpl) { if (_cacheIsStale) { // Lock to prevent 2 threads building table lock (_createCacheLock) { // Second check in the event another thread just built table if (_cacheIsStale) { BuildRequiredItemTable(); } } } return _requiredItemsCache.TryGetValue(tpl, out var offerIds) ? offerIds : _emptyOfferIdSet; } /// /// Create a cache of offer Ids keyed against the item tpl they require /// public void BuildRequiredItemTable() { ConcurrentDictionary> newCache = new(); foreach (var offer in ragfairOfferService.GetOffers()) { if (offer.Requirements is null) { continue; } foreach (var requirement in offer.Requirements) { // Skip offers for currency, we only need barter offers as this cache is used by `GetOffersThatRequireItem` if (paymentHelper.IsMoneyTpl(requirement.TemplateId)) { continue; } // Ensure cache has Hashset init for this tpl var offerIds = newCache.GetOrAdd(requirement.TemplateId, _ => []); // Add offer id against the tpl key offerIds.Add(offer.Id); } } // Replace cache in one go _requiredItemsCache = newCache; // Cache is now fresh _cacheIsStale = false; } /// /// Flag the cache as stale /// public void InvalidateCache() { _cacheIsStale = true; } }