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;
}
}