using System.Collections.Concurrent; using ServerBase; using ServerCommon; using MetaAssets; public class BrokerMetaTable { public string Version { get; private set; } private readonly MetaTable m_meta_table = new MetaTable(); public ProductMetaTable ProductMetaTable => m_meta_table.ProductMetaTable; // TODO: PlanetItemExchangePolicyMetaTable를 PlanetItemExchangePolicyTable로 이름 변경 public PlanetItemExchangePolicyMetaTable PlanetItemExchangePolicyMetaTable => m_meta_table.PlanetItemExchangePolicyMetaTable; public SystemMailMetaTable SystemMailMetaTable => m_meta_table.SystemMailMetaTable; public ItemMetaTable ItemMetaTable => m_meta_table.ItemMetaTable; public TextStringMetaTable TextStringMetaTable => m_meta_table.TextStringMetaTable; public BrokerMetaTable(MetaTable? source = null, string? versionName = null) { if (source != null) { m_meta_table = source; Version = versionName ?? Guid.NewGuid().ToString(); } } } public class BrokerApiMetaLoader : IDisposable { readonly string m_input_path = "resource/meta"; // 스레드 세이프한 딕셔너리로 변경 private readonly ConcurrentDictionary m_meta_tables = new ConcurrentDictionary(); // 더 세밀한 제어가 필요한 경우를 위한 ReaderWriterLockSlim 추가 private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(); // 기본 메타테이블 캐싱 private BrokerMetaTable m_default_meta_table; public Result load(string? dataPath = null) { try { m_lock.EnterWriteLock(); // 기본 메타 테이블 로드 var (result, meta_table) = loadMetaData(dataPath); if (result.isFail() || meta_table == null) { return result; } if (m_meta_tables.TryAdd(meta_table.Version, meta_table)) { m_default_meta_table = meta_table; } return result; } finally { m_lock.ExitWriteLock(); } } private (Result, BrokerMetaTable?) loadMetaData(string? dataPath = null, string? versionName = null) { var result = new Result(); var input_path = dataPath ?? m_input_path; try { var full_path = Path.GetFullPath(input_path, Environment.CurrentDirectory); if (!Directory.Exists(full_path)) { full_path = Path.GetFullPath(input_path, AppContext.BaseDirectory); if (!Directory.Exists(full_path)) { result.setFail(ServerErrorCode.MetaDataLoadFailed, $"Meta data path not found: {input_path}"); return (result, null); } } var meta_table = new MetaTable(); meta_table.loadForBrokerApi(full_path); var errors = new ValidatorErrorCollection(); foreach (var data in meta_table.PlanetItemExchangePolicyMetaTable.PlanetItemExchangePolicyDataList) { validate(data, meta_table, errors); } if (errors.HasError) { errors.log(); } //TODO: ProductMetaTable, SystemMailMetaTable, ItemMetaTable 에 대한 검사도 필요하다. var broker_meta_table = new BrokerMetaTable(meta_table, versionName); return (result, broker_meta_table); } catch (Exception ex) { result.setFail(ServerErrorCode.MetaDataLoadFailed, $"Meta data load failed => {ex.Message}"); } return (result, null); } public BrokerMetaTable getMetaTable(string? versionName = null!) { try { m_lock.EnterReadLock(); if (string.IsNullOrEmpty(versionName) || versionName == "default") { return m_default_meta_table; } return m_meta_tables.GetValueOrDefault(versionName, m_default_meta_table); // 요청된 버전이 없으면 기본 메타테이블 반환 } finally { m_lock.ExitReadLock(); } } private ValidatorErrorCollection validate(PlanetItemExchangePolicyMetaData planetItemExchange, MetaTable metaTable, ValidatorErrorCollection errors) { checkValidCaliverseItem(planetItemExchange); checkValidPlanetItem(planetItemExchange); return errors; void checkValidPlanetItem(PlanetItemExchangePolicyMetaData data) { var planet_item_type = toEnum(data.PlanetItemType, PlanetItemType.None); switch (planet_item_type) { case PlanetItemType.Igm26Currency: { var currency_type = toEnum(data.PlanetItemId, PlanetCurrencyType.None); if (currency_type == PlanetCurrencyType.None) { addError(nameof(data.PlanetItemId), $"Planet 화폐 타입이 잘못되었습니다 => PlanetItemId:{data.PlanetItemId}"); } // if (data.PlanetItemAmount >= 0) // { // // 재화는 차감만 가능 // addError(nameof(data.PlanetItemAmount), // $"Planet 화폐 수량은 양수 합니다 => PlanetItemAmount:{data.PlanetItemAmount}"); // } break; } case PlanetItemType.Igm26Quest: break; case PlanetItemType.None: default: addError(nameof(data.PlanetItemId), $"Planet 화폐 타입이 잘못되었습니다 => PlanetItemId:{data.PlanetItemId}"); break; } } void checkValidCaliverseItem(PlanetItemExchangePolicyMetaData data) { var caliverse_item_type = toEnum(data.CaliverseItemType, CaliverseItemType.None); switch (caliverse_item_type) { case CaliverseItemType.Currency: { var currency_type = toEnum(data.CaliverseItemId, CurrencyType.None); if (currency_type == CurrencyType.None) { addError(nameof(data.CaliverseItemId), $"Caliverse 화폐 타입이 잘못되었습니다 => CaliverseItemId:{data.CaliverseItemId}"); } // if (data.CaliverseItemAmount >= 0) // { // // 재화는 차감만 가능 // addError(nameof(data.CaliverseItemAmount), // $"Caliverse 화폐 수량은 음수여야 합니다 => CaliverseItemAmount:{data.CaliverseItemAmount}"); // } break; } case CaliverseItemType.CaliverseProduct: { var product_id = Convert.ToInt32(data.CaliverseItemId); if (metaTable.ProductMetaTable.ProductMetaDataListbyId.ContainsKey(product_id) == false) { addError(nameof(data.CaliverseItemId), $"CaliverseItemId이 존재하지 않습니다 => CaliverseItemId: {data.CaliverseItemId}"); } if (data.CaliverseItemAmount < 0) { addError(nameof(data.CaliverseItemAmount), $"Product 수량은 0보다 커야합니다 => CaliverseItemAmount:{data.CaliverseItemAmount}"); } break; } case CaliverseItemType.None: default: { addError(nameof(data.CaliverseItemType), $"Caliverse 아이템 타입이 잘못되었습니다 => CaliverseItemType:{data.CaliverseItemType}"); break; } } } void addError(string variableName, string msg) { errors.add($"Invalid PlanetItemExchangePolicyMetaData Value: {variableName} - {msg}"); } TEnum toEnum(string enumTypeString, TEnum defaultValue) where TEnum : struct, Enum { var enum_type_string_result = enumTypeString.Replace("_", ""); return Enum.TryParse(enum_type_string_result, ignoreCase: true, out var enum_type) ? enum_type : defaultValue; } } // 리소스 해제를 위한 Dispose 패턴 구현 private bool m_disposed = false; public void Dispose() { if (!m_disposed) { m_lock.Dispose(); m_disposed = true; } } } // 최신 메타테이블을 참조하는 클래스 public class BrokerMetaTableRef { public BrokerMetaTable MetaTable => m_meta_table; private readonly BrokerMetaTable m_meta_table; public BrokerMetaTableRef(BrokerMetaTable metaTable) { m_meta_table = metaTable; } }