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

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

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

Первый небольшой фикс мы начнем с комментария в LyraInventoryManagerComponent.cpp на строке 136: TODO: Would prefer to not deal with this here and hide it further? Он указывает на то, что валидация (nullptr и другие проверки состояния Entry.Instance) выполняется прямо в компоненте. Это нарушает инкапсуляцию и засоряет логику компонента инвентаря.

Где будем искать решение? Спрячем логику валидации в подсистему, где ей и место, а компоненту оставим только вызов готовых методов. Такой подход делает код чище, надёжнее и легче для поддержки.

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

В своих потугах использую UE5.5.0.

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

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

Добавляем методы для:

  • Переносим логику проверки валидности и очистки в ULyraInventorySubsystem.
  • Добавляем методы для:Валидации экземпляров предметов (например, на nullptr).Фильтрации списка предметов — возвращает только валидные.Очистки невалидных предметов — автоматически убирает мусор.
  • Обновляем FLyraInventoryList в компоненте, чтобы он делегировал эту работу подсистеме.

Обновляем 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.
  • Добавление новой логики (например, асинхронной проверки или логирования ошибок) теперь проще.

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

  • Централизованная валидация — всё сосредоточено в подсистеме.
  • Чистый и модульный код — компонент больше не занимается чужими заботами.
  • Автоматическая очистка — подсистема убирает невалидные предметы, предотвращая ошибки и утечки.
  • Гибкость — в будущем можно добавить расширенные проверки или асинхронное обновление списка.

Вывод

Мы спрятали логику валидации туда, где ей место — в ULyraInventorySubsystem. Компонент инвентаря стал проще, а код — чище и надёжнее. Как говорится: "Меньше проверок руками — больше времени на крутые фичи!"

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

Молодость… Вечно что-то unimplemented оставляют, а потом чешут репу: "Так и должно быть или кто-то заснул на клавише?" Ну давайте разберём этот вопрос как опытный старик, который видел все — и баги, и багфиксы, и бессонные ночи программистов.

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

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

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

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

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

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

Потому что жизнь сложнее, чем кажется.

Первый метод (AddEntry с ItemClass) — для создания нового экземпляра. Это как роддом для предметов: класс определили, вес (стек) назначили, и вот вам новый герой в системе инвентаря.

Второй метод (AddEntry с 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
Начать дискуссию