Lyra Inventory Fix 04: Решаем проблему с валидацией, инкапсуляцией и имплементацией.

Lyra Inventory Fix 04: Решаем проблему с валидацией, инкапсуляцией и имплементацией.

Фикс 1: Валидация, инкапсуляция

Нашли TODO, зарытый на 136-й строкеLyraInventoryManagerComponent.cpp.

"Would prefer to not deal with this here and hide it further?"

Перевод: "Не хочу возиться с этим здесь, спрячьте куда-нибудь подальше."

Суть проблемы: Валидация (nullptr, проверка состояния Entry.Instance) выполняется прямо в компоненте. Логика смешивается, инкапсуляция нарушается, код превращается в свалку проверок и условий.

Решение

Скрываем валидацию там, где её место — в подсистеме.

  • Логика проверок уходит из компонента.
  • Подсистема берёт на себя всю грязную работу.
  • Компонент вызывает только готовые методы.

Что это даст?

  • Чистота кода: компонент будет заниматься своим делом, не влезая в проверку данных.
  • Надёжность: валидатор в подсистеме будет унифицирован и не допустит пропущенных проверок.
  • Лёгкость поддержки: захочешь изменить валидацию? Меняй в одном месте — подсистема всё обработает.

В компоненте инвентаря не будет никакого мусора. Только вызовы, которые работают.

Спойлер: Для выполнения дальнейших телодвижений необходимо завершить предыдущие гайды:

В своих потугах будем использовать UE5.5.0.

Что мы делаем?

Мы берём грязную работу и отдаём её подсистеме, чтобы компонент наконец-то мог дышать спокойно, не забивая голову бесполезными проверками.

Переносим логику проверки валидности и очистки в ULyraInventorySubsystem

  • Валидация: Проверка на nullptr и другие ошибки состояния больше не беспокоят компонент.
  • Фильтрация: Подсистема фильтрует только валидные экземпляры.
  • Очистка: Убираем невалидные предметы автоматически. Мусор не задерживается.

Обновляем FLyraInventoryList

Теперь компонент не несёт ответственность за эти проверки. Все они делегированы подсистеме. Компонент лишь запрашивает, а подсистема делает.

"Пусть каждый делает то, для чего он предназначен."

LyraInventorySubsystem.h:

public: /** Validate if an inventory item instance is still valid */ UFUNCTION(BlueprintCallable, Category="Lyra|Inventory") bool IsValidItemInstance(const ULyraInventoryItemInstance* Instance) const; /** Get all valid items from an inventory entry array */ UFUNCTION(BlueprintCallable, Category="Lyra|Inventory") TArray<ULyraInventoryItemInstance*> GetValidItemsFromList(const TArray<struct FLyraInventoryEntry>& Entries) const; private: /** Remove invalid items from management */ void CleanupInvalidItems();

LyraInventorySubsystem.cpp:

void ULyraInventorySubsystem::CleanupInvalidItems() { ManagedItems.RemoveAll([](const ULyraInventoryItemInstance* Item) { return !IsValid(Item); }); } bool ULyraInventorySubsystem::IsValidItemInstance(const ULyraInventoryItemInstance* Instance) const { return IsValid(Instance) && Instance->GetItemDef() != nullptr && ManagedItems.Contains(Instance); } TArray<ULyraInventoryItemInstance*> ULyraInventorySubsystem::GetValidItemsFromList(const TArray<FLyraInventoryEntry>& Entries) const { TArray<ULyraInventoryItemInstance*> Results; Results.Reserve(Entries.Num()); for (const FLyraInventoryEntry& Entry : Entries) { if (IsValidItemInstance(Entry.Instance)) { Results.Add(Entry.Instance); } } return Results; }

LyraInventoryManagerComponent.h:

// Добавляем после #include "LyraInventoryManagerComponent.generated.h" class ULyraInventorySubsystem; // В FLyraInventoryEntry после строки friend ULyraInventoryManagerComponent; friend ULyraInventorySubsystem;

Обновляем FLyraInventoryList

Теперь компонент инвентаря делегирует проверку валидности и очисткуподсистеме.

LyraInventoryManagerComponent.cpp:

// Полностью заменяем реализацию функции FLyraInventoryList::GetAllItems() TArray<ULyraInventoryItemInstance*> FLyraInventoryList::GetAllItems() const { if (UGameInstance* GameInstance = OwnerComponent->GetWorld()->GetGameInstance()) { if (ULyraInventorySubsystem* InventorySubsystem = GameInstance->GetSubsystem<ULyraInventorySubsystem>()) { return InventorySubsystem->GetValidItemsFromList(Entries); } } return TArray<ULyraInventoryItemInstance*>(); }

Что мы сделали?

Lyra Inventory Fix 04: Решаем проблему с валидацией, инкапсуляцией и имплементацией.

Добавили методы в подсистему

  • IsValidItemInstance: Проверяет валидность экземпляра.
  • GetValidItemsFromList: Оставляет только валидные предметы, весь мусор вон из списка.
  • CleanupInvalidItems: Убирает всё, что не прошло проверку. Грязные предметы — не наша забота.

Перенесли логику валидации из компонента в подсистему:

  • FLyraInventoryList теперь передаёт задачи подсистеме. Ни nullptr, ни другие тупые проверки не тревожат компонент.

Улучшили инкапсуляцию и масштабируемость:

  • Вся логика валидации теперь под контролем ULyraInventorySubsystem. Любая новая логика — логирование, асинхронная проверка, кастомные ошибки — теперь не проблема.

Преимущества нового подхода

  • Централизованная валидация: Никаких раскиданных проверок по всему коду. Вся грязная работа — в подсистеме.
  • Чистый и модульный код: Компоненты теперь не врут про проверки. Они просто делают своё дело.
  • Автоматическая очистка: Подсистема удаляет все невалидные предметы. Никаких ошибок, никаких утечек.
  • Гибкость: Расширить систему — добавить новые проверки или асинхронное обновление? Пожалуйста. Всё есть.

Вывод

Мы не просто перенесли логику. Мы её закапываем там, где ей и место. Компонент инвентаря стал легче, а код — чище и надежнее.

Как говорится: "Меньше проверок руками — больше времени на крутые фичи!"

Фикс 2: Имплементация

Молодёжь, конечно… Какое-то постоянное состояние неопределённости. Как этот unimplemented(), который каждый раз шепчет: "Начну с понедельника." А потом ты такой — раз, и забываешь, а код остаётся как подгузник, который ты не сменил вовремя. Всё вроде работает, но разгоняется — и бах!

Два метода AddEntry — зачем они нужны?

ULyraInventoryItemInstance* AddEntry(TSubclassOf<ULyraInventoryItemDefinition> ItemClass, int32 StackCount)Это наш молодой и амбициозный метод. Он создаёт новый предмет с нуля:

  • Берёт определение предмета (ItemClass).
  • Создаёт экземпляр через подсистему.
  • Устанавливает размер стека (потому что не всегда нужен одиночный батончик хлеба — иногда их надо 20).
  • Используется при создании новых предметов: игрок нашёл лопату, квест наградил булкой, или админ выдал баночку зелья.

void AddEntry(ULyraInventoryItemInstance* Instance) А вот это старый волк в системе — работает с уже существующими экземплярамипредметов:

  • Экземпляр уже есть — неважно, откуда он: репликация, загрузка сохранёнки или переложили из другого инвентаря.
  • Нужна только проверка валидности (а то мало ли, пока предмет полз к нам, превратился в тыкву).
  • Подсистема может его дополнительно инициализировать, если что-то не так.

Зачем вообще два метода?

Потому что жизнь никогда не бывает линейной. Всё сложнее.

Первый метод — для новых экземпляров. Мы создаём что-то с нуля, задаём параметры и отправляем в мир. Это как роддом для предметов.

Второй метод — для существующих предметов. Вроде как, пришёл, а тут тебе только нужно проверить, а не стал ли он тыквой по дороге. Если всё в порядке — подсистема его доделает, если надо.

Почему unimplemented() — это плохо?

Забыть про этот метод — как забыть прикрутить гайку. Всё будет работать, пока не разгонишься. А потом в самый неподходящий момент машина возьмёт и развалится на части. Так вот, этот метод — необходим. А если его нет, то весь процесс создаётся в голове как пустое место, как заброшенная деревня, куда никто не заходит.

Как его реализовать?

Не будем терять время на всякие отговорки. Заполняем пробел и двигаемся дальше:

LyraInventoryManagerComponent.cpp

void FLyraInventoryList::AddEntry(ULyraInventoryItemInstance* Instance) { // Добавляем реализацию вместо unimplemented(); if (!Instance || !OwnerComponent) { return; } AActor* OwningActor = OwnerComponent->GetOwner(); if (!OwningActor || !OwningActor->HasAuthority()) { return; } // Check if the instance is already managed by the inventory subsystem if (UGameInstance* GameInstance = OwnerComponent->GetWorld()->GetGameInstance()) { if (ULyraInventorySubsystem* InventorySubsystem = GameInstance->GetSubsystem<ULyraInventorySubsystem>()) { if (!InventorySubsystem->IsValidItemInstance(Instance)) { // If not managed, initialize it through the subsystem InventorySubsystem->InitializeItemInstance(Instance, Instance->GetItemDef()); } } } // Add to inventory FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef(); NewEntry.Instance = Instance; NewEntry.StackCount = 1; NewEntry.LastObservedCount = 1; BroadcastChangeMessage(NewEntry, /[I]OldCount=[/I]/ 0, /[I]NewCount=[/I]/ 1); }

Теперь всё на своих местах:

  • AddEntry(ItemClass, StackCount) — создаёт новый предмет.
  • AddEntry(Instance) — добавляет существующий экземпляр.

Вывод

Два метода — два подхода: создать или добавить. Оба играют свою роль. И если какой-то из них не был реализован, а ждет, как старик на утренней рыбалке — не откладывайте! Сразу займитесь этим, иначе потом будете искать баг, когда всё уже валится на куски. Лучше сразу доделать, чем потом искать баг, когда всё уже развалилось!

1
1
Начать дискуссию