-
-
Couldn't load subscription status.
- Fork 21
GH-930 Msg placeholders & Async placeholder for future #1203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
* Register new placeholders for `msg_toggle` and `socialspy_status` in `MsgPlaceholderSetup`. * Extend `MsgMessages` with a `Placeholders` interface and implement language-specific placeholder formats in `ENMsgMessages` and `PLMsgMessages`.
* Add caching logic for `msg_toggle` state in `MsgPlaceholderSetup`. * Introduce new formatted placeholders for `msg_toggle` and `socialspy_status`. * Extend `MsgMessages.Placeholders` to support a loading state with translations.
…erAPI` * Adjust cache duration in `MsgPlaceholderSetup` from 200ms to 5s. * Rename `msg_toggle` placeholders to `msg_status` for consistency. * Extend `ENPlaceholders` and `PLPlaceholders` with `OkaeriConfig`. * Add `PlaceholderAPI` dependency in `runServer` task configuration.
* Replace existing cache in `MsgPlaceholderSetup` with `AsyncPlaceholderCached`. * Introduce `AsyncPlaceholderCacheRegistry` for managing placeholder caches. * Add `AsyncPlaceholderCacheController` to handle cache invalidation on player quit. * Simplify placeholder logic and remove redundant caching methods.
…derCacheRegistry` * Replace individual `stateCache` with dynamically registered cache in the `AsyncPlaceholderCacheRegistry`. * Simplify placeholder setup logic and adjust `MsgToggleService` to utilize the registry for cache management.
…lidation * Add `Subscribe` annotations to invalidate cache on `EternalReloadEvent` and `EternalShutdownEvent`.
* Rename `onEternalDisable` to `onDisable` and `onReload`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a new asynchronous caching mechanism for placeholders and integrates it with the messaging feature. The changes include a new AsyncPlaceholderCacheRegistry and AsyncPlaceholderCached for managing async data loading, new placeholders for message and social spy status, and refactoring MsgToggleServiceImpl to use the new caching system. The overall implementation is good, but I've found a few areas for improvement. There's a race condition in AsyncPlaceholderCached that could lead to multiple concurrent loads for the same data, some code duplication in MsgPlaceholderSetup when defining similar placeholders, and a minor inconsistency in a comment in one of the message files. I've left specific comments with suggestions on how to address these points.
...alcore-core/src/main/java/com/eternalcode/core/placeholder/cache/AsyncPlaceholderCached.java
Show resolved
Hide resolved
eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/MsgPlaceholderSetup.java
Show resolved
Hide resolved
eternalcore-core/src/main/java/com/eternalcode/core/feature/msg/messages/ENMsgMessages.java
Show resolved
Hide resolved
...nalcore-core/src/main/java/com/eternalcode/core/feature/msg/toggle/MsgToggleServiceImpl.java
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| @Subscribe(EternalInitializeEvent.class) | ||
| void setUpPlaceholders(PlaceholderRegistry placeholderRegistry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proponowalbym poprawic nazewnictwo metody obecnie jest to ogolnikowe a nie jasne ze chodzi o rejestracje placeholderow, czyli jezeli jest kompozycja na registry dajemy setUpPlaceholders -> registerMessagePlaceholders, lub samo registryPlaceholders ale jezeli wciagamy "msg" to Message
| player -> String.valueOf(this.msgService.isSpy(player.getUniqueId())) | ||
| )); | ||
|
|
||
| placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Takie wyrazenia w scope mozna byloby wyizolowac do prywatnej metody przykladowo
private String socialSpyStatusFormatted(UUID uuid, Translation translation) {
return msgService.isSpy(uuid)
? translation.msg().placeholders().socialSpyEnabled()
: translation.msg().placeholders().socialSpyDisabled();
}| void setUpPlaceholders(PlaceholderRegistry placeholderRegistry) { | ||
| Translation translation = this.translationManager.getMessages(); | ||
|
|
||
| AsyncPlaceholderCached<MsgState> stateCache = this.cacheRegistry.register( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Narazie wystarczy ale w przyszlosci jezeli bedziesz planowac w roadmapie dalej zasilac w pelni asynchroniczne placeholdery to wypadaloby zwracac Cfa i opakowywac go w calosci
|
|
||
| placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( | ||
| "socialspy_status", | ||
| player -> String.valueOf(this.msgService.isSpy(player.getUniqueId())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obecny auto boxing zamienic na Boolean#toString intuicyjniej bardziej i wyizolowac do prywatnej metody cos w stylu:
private String getSocialSpyStatus(UUID uuid) {
return Boolean.toString(msgService.isSpy(uuid));
}| @Controller | ||
| public class MsgPlaceholderSetup { | ||
|
|
||
| public static final String MSG_STATE_CACHE_KEY = "msg_state"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tutaj mozna byloby rozwazyc wrzucenie dodatkowych stalych globalnych na sama mozliwosc kontrolowania centralnia czas cache zamiast overidowac w metodzie rejeestracji
| .build(); | ||
| } | ||
|
|
||
| public T getCached(UUID uuid) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return null - jak value nie jest jeszcze dostepna to wymaga obslugi null majac ze soba ryzuko rzucenia NPE.
Obecnie nie zwraca pelnej informacji o tym ze loading odbywa sie cyklu async.
- Proponowalbym zwracac CFA dlaczego lepiej? - bo zwracamy CFA zamiast null od razu wiemy ze score jest cyklem async,
- -Mozna dorzucic null check na UUID uzyskujemy safe API
- Izolacja logiki ladowania do loadAsync - czystsza seperacja wlasciwej odpowiedzialnosci.
- Obslugujemy async ladowanie danych z pola loader i aktualizujemy cache,
- Zapobiegamy wielokrotnemu ruszaniu loadera dla tego samego UUID
- Wazne zeby logika bla non-blocking przy Paper API nie blokujac tickow serwera
- Ogolnie to najlepiej opakowac takie logiki w jakis error handling dla spokoju i wytrwalosci
Finalnie by to wjechalo tak:
public CompletableFuture<T> getCached(UUID uuid) {
// ewentualny null check dla UUID
T cached = cache.getIfPresent(uuid);
if (cached != null) {
return CompletableFuture.completedFuture(cached);
}
return loading.computeIfAbsent(uuid, this::loadAsync);
}
private CompletableFuture<T> loadAsync(UUID uuid) {
return loader.apply(uuid).whenComplete((value, throwable) -> {
if (value != null) {
cache.put(uuid, value);
}
loading.remove(uuid);
});
}| public AsyncPlaceholderCacheRegistry() { | ||
| } | ||
|
|
||
| public <T> AsyncPlaceholderCached<T> register(String key, Function<UUID, CompletableFuture<T>> loader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moze byc taka sytuacja ze key juz istnieje w mapie ale zostanie nadpisany bez ostrzezenia to mamy niestety utrate cache,
Minusem troche moze byc to ze nie ma konfiguracji loadera z timeoutem,
Null-check dla key i loader i ewentualne rzucenie wyjatku kiedy klucz juz istnieje na runtime, przekazywanie timeoutu loadera do PlaceholderCached to samo w metodach nizej.
Wlasciwa walidacja do logiki tutaj bylaby bardzo potrzebna jedna metoda zalatwiasz cala walidacje przykladowo:
private <T> void validateRegistrationParameters(String key, Function<UUID, CompletableFuture<T>> loader, Duration expireAfterWrite) {
if (key == null || key.trim().isEmpty()) {
throw new IllegalArgumentException("Cache key cannot be null or empty");
}
if (loader == null) {
throw new IllegalArgumentException("Loader function cannot be null");
}
if (expireAfterWrite == null) {
throw new IllegalArgumentException("Expire duration cannot be null");
}
if (expireAfterWrite.isNegative() || expireAfterWrite.isZero()) {
throw new IllegalArgumentException("Expire duration must be positive");
}
}
Wszystko tutaj mozna rozbudowac i rozszerzyc na jakas konfiguracje jezeli potrzeba w obecnym stanie jezeli nie - zostawiamy i tyle
| import java.util.function.Consumer; | ||
|
|
||
| @Service | ||
| class MsgToggleServiceImpl implements MsgToggleService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rozbilbym to na kilka innych komponentow z ich odpowiedzialnoscia z kosztem korzysci,
MessageStateCache-> operacja na cache, izolacja logiki cache,MessageStateToggleCalculator-> logika biznesowa od samego toggle w zamian za kalkulacje i invertowanie,MessageStateUpdateCoordinator-> koordynacja samym update cos ala transaction-like,MessageToggleServiceImpl-> Orchestracja uzyskujemy cienka warstwe ktora koordynuje tylko komponenty
Wszystko wchodzi do glownego MsgToggleService. Wyzej zaznaczylem Message ale to wiadomo o co raczej chodzi
@Override
public CompletableFuture<Void> setState(UUID playerId, MsgState state) {
return updateCoordinator.updateState(playerId, state);
}
// i tak dalej:
MsgState newState = toggleCalculator.calculateToggledState(currentState);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jezeli cos niezrozumialego smialo pytac lub dawac wlasne propozycje zmian ;).
Wszystko mozna podkrecic i jezeli jest jakis problem nie sugerowac sie moimi zmianami bo zawsze jest jakies ala w trakcie review
|
|
||
| placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( | ||
| "socialspy_status", | ||
| player -> String.valueOf(this.msgService.isSpy(player.getUniqueId())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
microoptimizations: use Boolean#toString instead of String.valueOf
| player -> String.valueOf(this.msgService.isSpy(player.getUniqueId())) | |
| player -> Boolean.toString(this.msgService.isSpy(player.getUniqueId())) |
| @Getter | ||
| @Accessors(fluent = true) | ||
| public static class PLPlaceholders extends OkaeriConfig implements MsgMessages.Placeholders { | ||
| private String loading = "<yellow>Ładowanie..."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private String loading = "<yellow>Ładowanie..."; | |
| private String loading = "<yellow>Wczytywanie..."; |
Wczytywanie (w kontekście danych) tutaj brzmi lepiej, ładowanie bardziej się kojarzy z jakimiś intensywnymi zadaniami
|
|
||
| @Getter | ||
| @Accessors(fluent = true) | ||
| public static class ENPlaceholders extends OkaeriConfig implements MsgMessages.Placeholders { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section is too general; you should move it to the feature-related section. Creating such a section makes it similar to PlayerSection/ItemSection in the past. Currently, it only has a few messages, but over time, it will become very messy. Alternatively, I suggest you move it to the placeholders.yml file, but remember to also divide it into feature-related sections.
| AsyncPlaceholderCached<MsgState> stateCache = this.cacheRegistry.register( | ||
| MSG_STATE_CACHE_KEY, | ||
| this.msgToggleRepository::getPrivateChatState, | ||
| Duration.ofMinutes(10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to private static variable, maybe?
| public Notice socialSpyEnable = Notice.chat("<green>► <white>SocialSpy has been {STATE}<white>!"); | ||
| public Notice socialSpyDisable = Notice.chat("<red>► <white>SocialSpy has been {STATE}<white>!"); | ||
|
|
||
| @Comment("# Formatowanie placeholderów") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gemini is right 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 komentarz na razie bo może być dużo do przerobienia
|
|
||
| private final MsgToggleRepository msgToggleRepository; | ||
| private final ConcurrentHashMap<UUID, MsgState> cachedToggleStates; | ||
| private final AsyncPlaceholderCacheRegistry cacheRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
słabe rozwiązanie, najlepiej jakby tego cache nie było w repo + nie było tutaj nic placeholderach, bo to tworzy dziwne zależności. Raczej placeholdery widzą API serisów i tyle. Serwisy nie powinny widzieć placeholders
Tested with love for OpenSource 💕
https://medal.tv/pl/games/minecraft/clips/lhPGLpi6DGCYMFpSd?invite=cr-MSxmazgsNDI2MDIzOTA4&