Czy weak_maps w Verse oczyszczają się automatycznie, gdy gracz opuszcza grę w UEFN?
W skrócie
Artykuł wyjaśnia, dlaczego `weak_map` w języku Verse w środowisku UEFN nie usuwa automatycznie wpisów graczy po ich rozłączeniu, co może prowadzić do błędów czasu uruchomienia i wycieków pamięci. Przedstawiono w nim mechanizm działania pamięci podręcznej i bazy danych Epic Cloud Save, a także zaprezentowano kompletny tutorial ręcznej rekonstrukcji map w celu bezpiecznego czyszczenia danych sesyjnych. Na koniec opisano najlepsze praktyki zarządzania stanem gry oraz korzyści z zewnętrznego backendu chmurowego, takiego jak horizOn.
Jeśli ufasz swojemu asystentowi programowania AI, który twierdzi, że Verse automatycznie czyści dane gracza z weak_map w milisekundzie, w której rozłączy się on z Twoją wyspą Fortnite, narażasz się na poważny runtime crash. Twierdzenie to brzmi logicznie: ponieważ jest to weak reference, silnik powinien usunąć klucz, gdy obiekt gracza zostanie objęty procesem Garbage Collection. Jednak w Unreal Editor for Fortnite (UEFN) rzeczywistość jest o wiele bardziej skomplikowana, a błędne zrozumienie tego, jak Verse memory manager przetwarza cykle życia gracza, prowadzi do cichych wycieków stanu (state leaks) i krytycznych wyjątków (exceptions).
Gdy gracz opuszcza grę, odwoływanie się do jego nieaktywnego obiektu w mapie może wywołać przerażający błąd ErrRuntime_WeakMapInvalidKey lub doprowadzić do awarii całej wyspy, zmuszając Cię do wdrożenia rygorystycznego protokołu UEFN server crash fix protocol, aby utrzymać stabilność serwerów. Aby tego uniknąć, deweloperzy muszą zrozumieć, jak Verse wewnętrznie zarządza pamięcią oraz jak implementować niezawodne procedury oczyszczania (cleanup).
Błędne przekonanie: porady generowane przez AI kontra rzeczywistość Verse
Wielu deweloperów pyta swoich asystentów AI, jak radzić sobie z danymi gracza w mapach, gdy ten opuszcza mecz. Często powtarzaną radą generowaną przez AI jest to, że silnik traktuje klucz gracza jako „weak reference” i automatycznie usuwa wpis gracza z mapy po jego odejściu. Jest to zasadnicze błędne przekonanie.
Choć weak_map(player, t) w Verse pod maską wykorzystuje weak references jako klucze, aby zapobiec cyklom silnych referencji (hard reference cycles), które blokowałyby Garbage Collection, nie wykonuje ono automatycznego, natychmiastowego czyszczenia samych wpisów w mapie. Wpis – zawierający zarówno slot klucza, jak i powiązane z nim dane – pozostaje zaalokowany w kontenerze mapy.
Jeśli Twój kod spróbuje uzyskać dostęp, odczytać lub zmodyfikować ten klucz po tym, jak gracz opuścił grę, runtime Verse spróbuje zdereferencjonować pusty (null) lub nieprawidłowy obiekt gracza. Zamiast bezpiecznego obsłużenia błędu (failing gracefully), środowisko uruchomieniowe wywoła crash lub zgłosi nieprzechwytywany wyjątek (exception). System oczekuje, że jawnie obsłużysz zmiany cyklu życia, zamiast polegać na automatycznym oczyszczaniu.
Dlaczego Weak Maps nie oczyszczają automatycznie wpisów graczy
Aby zrozumieć, dlaczego tak się dzieje, musimy przyjrzeć się przeznaczeniu weak_map w UEFN. W przeciwieństwie do standardowych środowisk programistycznych, gdzie weak maps służą jako tymczasowe cache w pamięci, Verse używa weak_map(player, t) głównie jako strażnika persistent player data (trwałych danych gracza).
Trwałość danych między sesjami gry
Kiedy używasz weak_map(player, t) zdeklarowanej w zasięgu modułu (module scope), silnik łączy wartości z trwałą bazą danych w chmurze Epic (persistent cloud database). Jeśli gracz opuści mecz i powróci trzy dni później, silnik dopasuje jego ID gracza do trwałego klucza w mapie, aby przywrócić jego postępy.
Gdyby silnik automatycznie usuwał wpis gracza z mapy w momencie, gdy opuszcza on grę, mapa utraciłaby wszystkie trwałe dane. Poziomy, niestandardowe waluty i odblokowane przedmioty resetowałyby się do zera za każdym razem, gdy gracz rozłączyłby się lub doświadczył limitu czasu sieci (network timeout). Dlatego baza danych została zaprojektowana tak, aby zachować te wpisy w nienaruszonym stanie – właśnie dlatego, że mają one przetrwać rozłączenia.
Ograniczony czas życia obiektów gracza
Kiedy gracz opuszcza mecz, jego aktywny obiekt sesji w playspace zostaje zniszczony. Fizyczna referencja player przechowywana w Twoim kodzie Verse staje się nieaktywnym uchwytem (dead handle).
Ponieważ klucz w mapie wskazuje teraz na nieprawidłowy, nieaktywny obiekt, odpytanie mapy przy użyciu tej nieaktywnej referencji zakończy się niepowodzeniem. Silnik nie skanuje i nie oczyszcza aktywnie martwych kluczy z mapy w czasie rzeczywistym. Zamiast tego pozostawia je bezczynne, dlatego ręczne zarządzanie jest obowiązkowe, aby zapobiec gromadzeniu się nieaktualnych referencji (stale references).
Konsekwencje: wycieki pamięci, nieaktualne dane i awarie serwera
Brak oczyszczania wpisów graczy prowadzi do trzech wyraźnych problemów, które pogarszają wydajność gry i stabilność serwera podczas długich meczów.
- Wyciek nieaktualnych danych (Stale Data Leakage): Jeśli gracz odejdzie, a inny dołączy, nowy gracz może odziedziczyć dane sesji poprzedniego gracza, jeśli silnik ponownie użyje wewnętrznych slotów graczy. Prowadzi to do błędów stanu (state bugs), takich jak odradzanie się nowych graczy z pełnym ekwipunkiem lub nieprawidłowymi statystykami meczu.
- Gromadzenie pamięci (Memory Accumulation): Chociaż pojedyncza zmienna logiczna (boolean) lub liczba całkowita (integer) zajmuje znikomą ilość miejsca, przechowywanie złożonych struktur dla nawet 50 graczy w lobby o dużej pojemności może zwiększyć zużycie pamięci. W trakcie 4-godzinnej sesji serwera to nagromadzenie może obniżyć tick rate serwera.
- Błędy wyszukiwania (Look-up Failures): Próba odpytania o status nieaktywnego gracza lub wywołanie funkcji na martwej referencji gracza powoduje natychmiastowe crashe w runtime.
Osiąganie limitów zapisu w chmurze Epic
UEFN nakłada surowe limity na persistent data. Jesteś ograniczony do maksymalnie 4 persistent weak_maps na wyspę, a rozmiar pojedynczego rekordu gracza nie może przekraczać 256 KB danych.
Jeśli używasz persistent weak_map do przechowywania tymczasowych stanów sesji, marnujesz tę cenną przestrzeń bazy danych. Każda aktualizacja wiąże się z zapisem do bazy danych Epic, co grozi nałożeniem kary ograniczenia przepustowości zapisu (write-throttling) lub przekroczeniem limitu 256 KB, co z kolei wywoła błąd uruchomieniowy (runtime error) przy próbie zapisu kolejnych danych.
Samouczek krok po kroku: bezpieczne zarządzanie stanem sesji gracza
Aby zarządzać stanem gracza bez ryzyka wycieków pamięci lub przepełnienia bazy danych, musisz oddzielić tymczasowe dane sesji (transient session data) od trwałych danych w chmurze (persistent cloud data). Tymczasowe dane powinny być przechowywane w standardowych, nietrwałych mapach (non-persistent maps), które musisz ręcznie czyścić przy rozłączeniu gracza.
Krok 1: Zdefiniuj strukturę stanu sesji (struct)
Zacznij od zdefiniowania nietrwałej struktury (non-persistable struct), która zawiera wszystkie zmienne potrzebne graczowi podczas pojedynczej rundy lub meczu. Nie oznaczaj tej klasy ani struktury jako <persistable>.
# Define the transient data structure for active gameplay tracking
player_session_state := struct:
IsMoneyBagFull : logic = false
CurrentGold : int = 0
SpawnTime : float = 0.0
Krok 2: Utworzenie urządzenia zarządzającego (creative_device)
Stwórz creative device, które będzie pełniło rolę koordynatora. Będzie ono przechowywać modyfikowalną, nietrwałą mapę (non-persistent map) aktywnych graczy. Ponieważ standardowe mapy w Verse są niemodyfikowalne (immutable), deklarujemy zmienną mapy jako var, aby móc ją nadpisać, gdy gracze dołączają lub opuszczają grę.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
# Device handling player lifecycle events and session state mapping
state_manager_device := class(creative_device):
# Non-persistent map for tracking active player sessions
var SessionStates : [player]player_session_state = map{}
Krok 3: Subskrypcja zdarzeń playspace
W funkcji OnBegin zasubskrybuj zdarzenia połączeń playspace. Dzięki temu uruchomisz kod inicjalizacyjny, gdy gracz dołączy do gry, oraz kod czyszczący, gdy ją opuści.
OnBegin<override>()<suspends>:void=
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
# Initialize any players already in the session (useful for UEFN hot-reloading)
for (Player : GetPlayspace().GetPlayers()):
OnPlayerAdded(Player)
Krok 4: Implementacja logiki rejestracji i oczyszczania (cleanup)
Gdy gracz dołącza do gry, uzupełnij mapę o jego domyślny stan sesji. Gdy opuszcza grę, musisz usunąć jego wpis z mapy. Ponieważ Verse nie posiada wbudowanej funkcji Map.Remove(), musisz zrekonstruować mapę, odfiltrowując odchodzącego gracza. Zapobiega to pozostawaniu nieaktualnych referencji (stale references) w pamięci.
# Triggered when a player connects to the server
OnPlayerAdded(Player: player):void=
if (not SessionStates[Player]):
InitialState := player_session_state{IsMoneyBagFull := false, CurrentGold := 0, SpawnTime := GetEngineTime()}
if (set SessionStates[Player] = InitialState):
Print("Initialized gameplay state for joining player.")
# Triggered when a player disconnects or leaves the game
OnPlayerRemoved(Player: player):void=
Print("Player disconnected. Initiating map cleanup.")
RemovePlayerSession(Player)
# Purges the player's entry by reconstructing the map
RemovePlayerSession(PlayerToRemove: player):void=
var CleanedStates : [player]player_session_state = map{}
for (ActivePlayer -> State : SessionStates):
# Copy all players except the one who left
if (ActivePlayer <> PlayerToRemove):
if (set CleanedStates[ActivePlayer] = State):
# Entry successfully migrated to the cleaned map
set SessionStates = CleanedStates
Print("Successfully removed player session entry from memory.")
Przebudowując mapę podczas usuwania gracza, całkowicie usuwasz klucz referencyjny. Garbage collector może wtedy odzyskać zasoby gracza, nie pozostawiając nieaktualnych wpisów w pętli gry (game loop).
Jeśli chcesz śledzić niestandardową telemetry podczas tych przejść cyklu życia, musisz również pamiętać o limitach, takich jak 32-character analytics event name limit in Verse, podczas raportowania długości sesji lub statystyk waluty do zewnętrznych backendów.
Najlepsze praktyki zarządzania stanem w Verse
Aby upewnić się, że Twoje serwery UEFN pozostaną stabilne i wydajne, postępuj zgodnie z poniższymi wytycznymi dotyczącymi zarządzania danymi graczy:
- Rozróżniaj dane sesyjne od trwałych (Session vs. Persistent Data): Nigdy nie przechowuj zmiennych o krótkim czasie życia (takich jak aktualne zdrowie w meczu, wynik rundy czy tymczasowe pozycje) w persistent
weak_map. Przechowuj stany przejściowe (transient states) w standardowej modyfikowalnej mapie opakowanej w klasę menedżera. - Weryfikuj aktywność gracza za pomocą
IsActive: Przed pobraniem lub modyfikacją danych gracza w jakiejkolwiek mapie, sprawdź, czy nadal jest on obecny w playspace, korzystając z zapytaniaIsActive[]. JeśliIsActive[]zwróci false, przerwij wyszukiwanie i wywołaj zdarzenie oczyszczania (cleanup event). - Monitoruj rozmiar danych za pomocą
FitsInPlayerMap: Zapisując dane do persistentweak_map, wywołajFitsInPlayerMap(), aby upewnić się, że aktualizacja nie przekroczy limitu 256 KB, co zapobiegnie wyjątkom w czasie uruchomienia (runtime exceptions). - Konsoliduj swoje mapy: Nie twórz osobnych map dla każdej zmiennej. Zdefiniuj pojedynczą klasę zawierającą wszystkie zmienne gracza i przypisz gracza do tej klasy. Zminimalizuje to liczbę map i pozwoli zachować limit czterech persistent weak maps na wyspę.
Przeniesienie złożoności do niezawodnego backendu w chmurze
Zarządzanie cyklami życia sesji graczy, limitami baz danych i ręczną logiką oczyszczania w Verse może szybko stać się skomplikowane. Jeśli chcesz zbudować system postępów między sesjami (cross-session progression), globalnie zsynchronizowany ekwipunek czy regionalny Matchmaking, ręczne zarządzanie tymi stanami wymaga konfiguracji webhooków, skalowania zewnętrznych baz danych oraz obsługi synchronizacji między serwerami.
Dzięki horizOn te wyzwania związane z Backend są obsługiwane automatycznie. Integrując horizOn SDK ze swoim serwerem gry, możesz oddelegować zarządzanie sesjami graczy do dedykowanej bazy danych w chmurze. Gdy gracz się rozłączy, horizOn wywoła automatyczne oczyszczanie sesji, zaktualizuje globalne bazy danych i zsynchronizuje rekordy ekwipunku pomiędzy instancjami serwera, bez przekraczania limitów pamięci Verse (256 KB) i bez ryzyka runtime crashes.
Gotowy na skalowanie swojego UEFN Backend? Wypróbuj horizOn za darmo lub zapoznaj się z API docs.