[AB-25] refactoring experience collection to work instantly instead of delayed job

adding level up notification for experience
This commit is contained in:
Sheldan
2022-11-20 15:14:43 +01:00
parent d315113395
commit 5c7b018b2a
41 changed files with 547 additions and 1736 deletions

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.experience.config;
import dev.sheldan.abstracto.core.service.ExecutorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
@Configuration
public class ExperienceExecutorConfig {
@Autowired
private ExecutorService executorService;
@Bean(name = "experienceUpdateExecutor")
public TaskExecutor experienceUpdateExecutor() {
return executorService.setupExecutorFor("experienceUpdateExecutor");
}
}

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.experience.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import static dev.sheldan.abstracto.experience.config.ExperienceFeatureMode.LEVEL_UP_NOTIFICATION;
/**
* {@link FeatureConfig} instance containing the required configuration concerning system config and post targets for
* the {@link ExperienceFeatureDefinition} feature.
@@ -26,6 +29,7 @@ public class ExperienceFeatureConfig implements FeatureConfig {
* The multiplier which is applied to each calculated gained experience
*/
public static final String EXP_MULTIPLIER_KEY = "expMultiplier";
public static final String EXP_COOLDOWN_SECONDS_KEY = "expCooldownSeconds";
@Override
public FeatureDefinition getFeature() {
@@ -37,6 +41,11 @@ public class ExperienceFeatureConfig implements FeatureConfig {
*/
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(EXP_MULTIPLIER_KEY, MIN_EXP_KEY, MAX_EXP_KEY);
return Arrays.asList(EXP_MULTIPLIER_KEY, MIN_EXP_KEY, MAX_EXP_KEY, EXP_COOLDOWN_SECONDS_KEY);
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(LEVEL_UP_NOTIFICATION);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.experience.config;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum ExperienceFeatureMode implements FeatureMode {
LEVEL_UP_NOTIFICATION("levelUpNotification");
private final String key;
ExperienceFeatureMode(String key) {
this.key = key;
}
}

View File

@@ -15,10 +15,6 @@ import java.util.concurrent.CompletableFuture;
@Setter
@Builder
public class ExperienceGainResult {
/**
* The calculation result contained in a {@link CompletableFuture future}. The future is necessary, because the calculation both calculates the new role
* and removes/adds {@link net.dv8tion.jda.api.entities.Role role} to the {@link net.dv8tion.jda.api.entities.Member member}
*/
private CompletableFuture<RoleCalculationResult> calculationResult;
/**
* The ID of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} for which this is the result

View File

@@ -4,20 +4,10 @@ import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
/**
* The result of calculating the appropriate {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole role} for a {@link dev.sheldan.abstracto.experience.model.database.AUserExperience user}
* in a server.
*/
@Getter
@Setter
@Builder
public class RoleCalculationResult {
/**
* The ID of the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole role} which was given to the user. Can be null, in case no role is given.
*/
private Long experienceRoleId;
/**
* The ID of a {@link dev.sheldan.abstracto.core.models.database.AUserInAServer user} for who the role was calculated for.
*/
private Long userInServerId;
private Long oldRoleId;
private Long newRoleId;
}

View File

@@ -1,27 +0,0 @@
package dev.sheldan.abstracto.experience.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* Container object to store the experience in runtime and group it together. This basically is just a list of users who were tracked by experience.
* The actual calculation of the appropriate experience amount is done later.
*/
@Getter
@Setter
@Builder
public class ServerExperience {
/**
* The ID of the {@link dev.sheldan.abstracto.core.models.database.AServer} for which this experience were collected
*/
private Long serverId;
/**
* A list of IDs of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} which should be given experience
*/
@Builder.Default
private List<Long> userInServerIds = new ArrayList<>();
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.experience.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.RoleDisplay;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class LevelUpNotificationModel {
private MemberDisplay memberDisplay;
private Integer oldLevel;
private Integer newLevel;
private RoleDisplay oldRole;
private RoleDisplay newRole;
private Long oldExperience;
private Long newExperience;
}

View File

@@ -1,21 +1,17 @@
package dev.sheldan.abstracto.experience.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.experience.model.LeaderBoard;
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
import dev.sheldan.abstracto.experience.model.RoleCalculationResult;
import dev.sheldan.abstracto.experience.model.ServerExperience;
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* Service providing the required mechanisms to provide experience tracking.
@@ -24,12 +20,7 @@ import java.util.function.Function;
*/
public interface AUserExperienceService {
String EXPERIENCE_GAIN_CHANNEL_GROUP_KEY = "experienceGain";
/**
* Adds the given {@link AUserInAServer userInAServer} to the list of user who gained experience in the current minute.
* Does not add the user to the list of users, if it is already in there.
* @param userInAServer The {@link AUserInAServer userInAServer} to be added to the list of users gaining experience
*/
void addExperience(AUserInAServer userInAServer);
void addExperience(Member member, Message message);
/**
* Calculates the appropriate level for the given experience amount according to the given {@link AExperienceLevel levels}
@@ -51,58 +42,9 @@ public interface AUserExperienceService {
*/
boolean updateUserLevel(AUserExperience userExperience, List<AExperienceLevel> levels, Long experienceCount);
/**
* Iterates through the given list of {@link AServer servers} and increases the experience of the users contained in the
* {@link ServerExperience serverExperience} object, also increments the level and changes the role if necessary.
* This uses the respective configurable max/minExp and multiplier for each {@link AServer server} and increases the message count
* of each user by 1.
* @param serverExp The list of {@link AServer servers} containing the users which get experience
* @return A {@link CompletableFuture future} completing when the experience gain was calculated and roles were assigned
*/
CompletableFuture<Void> handleExperienceGain(List<ServerExperience> serverExp);
CompletableFuture<Void> syncUserRolesWithFeedback(AServer server, MessageChannel messageChannel);
/**
* Calculates the currently appropriate {@link AExperienceRole} for the given user and updates the role on the
* {@link net.dv8tion.jda.api.entities.Member} and ond the {@link AUserExperience}. Effectively synchronizes the
* state in the server and the database.
* @param userExperience The {@link AUserExperience userExperience} object to recalculate the {@link AExperienceRole experienceRole} for
* @param roles The list of {@link AExperienceRole roles} used as a role configuration
* @param currentLevel The current level of the user
* @return A {@link CompletableFuture future} containing the {@link RoleCalculationResult result} of the role calculation,
* completing after the role of the {@link net.dv8tion.jda.api.entities.Member} has been updated, if any
*/
CompletableFuture<RoleCalculationResult> updateUserRole(AUserExperience userExperience, List<AExperienceRole> roles, Integer currentLevel);
/**
* Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role})
* of all the users provided in the {@link AServer} object in the {@link AUserExperience}
* and on the {@link net.dv8tion.jda.api.entities.Member} according
* to how much experience the user has. Runs completely in the background.
* @param server The {@link AServer} to update the users for
* @return The list of {@link CompletableFuture futures} for each update of the users in the {@link AServer server}
*/
List<CompletableFuture<RoleCalculationResult>> syncUserRoles(AServer server);
/**
* Synchronizes the state ({@link AExperienceRole}, {@link net.dv8tion.jda.api.entities.Role})
* of all the users provided in the {@link AServer} object in the {@link AUserExperience}
* and on the {@link net.dv8tion.jda.api.entities.Member} according
* to how much experience the user has. This method provides feedback back to the user in the provided {@link AChannel channel}
* while the process is going own.
* @param server The {@link AServer} to update users for
* @param channelId The ID of a {@link AChannel channel} in which the {@link dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel statusUpdate}
* should be posted to
* @return A {@link CompletableFuture future} which completes after all the role changes have been completed
*/
CompletableFuture<Void> syncUserRolesWithFeedback(AServer server, Long channelId);
/**
* Recalculates the role of a single user in a server and synchronize the {@link net.dv8tion.jda.api.entities.Role}
* in the {@link net.dv8tion.jda.api.entities.Guild}
* @param userExperience The {@link AUserExperience} to synchronize the role for
* @return A {@link CompletableFuture future} which completes after the roles have been synced for the given {@link AUserInAServer user}
*/
CompletableFuture<RoleCalculationResult> syncForSingleUser(AUserExperience userExperience);
CompletableFuture<Void> syncForSingleUser(AUserExperience userExperience, Member member);
/**
* Loads the desired page of the ordered complete leaderboard from the {@link AServer} and returns the information as a {@link LeaderBoard}
@@ -122,19 +64,6 @@ public interface AUserExperienceService {
*/
LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer);
/**
* Provides a method to execute an action on a list of {@link AUserExperience} and provide feedback in the given {@link AChannel}
* in the form of {@link dev.sheldan.abstracto.experience.model.template.UserSyncStatusModel} to be rendered with a certain
* template
* @param experiences The list of {@link AUserExperience} to be working on
* @param channel The {@link AChannel} used to provide feedback to the user
* @param toExecute The {@link Function} which should be executed on each element of the passed list,
* this function needs to take a {@link AUserExperience userExperience} as parameter and returns a {@link CompletableFuture}
* with a {@link RoleCalculationResult} for each of them. These futures are then returned.
* @return A {@link CompletableFutureList completeFutureList} which represents the individual {@link RoleCalculationResult results} and a primary future, which is completed after all of the individual ones are
*/
CompletableFutureList<RoleCalculationResult> executeActionOnUserExperiencesWithFeedBack(List<AUserExperience> experiences, AChannel channel, Function<AUserExperience, CompletableFuture<RoleCalculationResult>> toExecute);
/**
* Disables the experience gain for a user directly. This sets the `experienceGainDisabled` on the respective {@link AUserExperience} object to true
* @param userInAServer The {@link AUserInAServer} to disable experience gain for
@@ -147,12 +76,6 @@ public interface AUserExperienceService {
*/
void enableExperienceForUser(AUserInAServer userInAServer);
/**
* Updates the actually stored experience roles in the database
* @param results The list of {@link RoleCalculationResult} which should be updated in the database
*/
void syncRolesInStorage(List<RoleCalculationResult> results);
boolean experienceGainEnabledInChannel(MessageChannel messageChannel);
AUserExperience createUserExperienceForUser(AUserInAServer aUserInAServer, Long experience, Long messageCount);

View File

@@ -7,6 +7,7 @@ import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.template.LevelRole;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -15,34 +16,12 @@ import java.util.concurrent.CompletableFuture;
* Service providing several methods surrounding {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole experienceRole}.
*/
public interface ExperienceRoleService {
/**
* Creates an {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole experienceRole} according to the given
* parameters. This actually updates the {@link net.dv8tion.jda.api.entities.Member members}
* which currently possessed the given role before and provides a display to see how far the progress is
* @param role The {@link ARole role} to set the level to
* @param level The new level the {@link ARole role} should be awarded at
* @param channelId The ID of the {@link dev.sheldan.abstracto.core.models.database.AChannel} in which the status updates
* should be sent to
* @return A {@link CompletableFuture future} which completes, after all the updates on the {@link net.dv8tion.jda.api.entities.Member}
* have been completed
*/
CompletableFuture<Void> setRoleToLevel(Role role, Integer level, Long channelId);
/**
* Removes the role from the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole} configuration,
* this will also update all the {@link net.dv8tion.jda.api.entities.Member} which previously had this role and re-calculates
* a new {@link AExperienceRole experienceRole} for them while also updating them in the guild
* @param role The {@link ARole} to remove from the {@link dev.sheldan.abstracto.experience.model.database.AExperienceRole}
* configuration
* @param channelId The ID of the {@link dev.sheldan.abstracto.core.models.database.AChannel} in which the status updates
* should be sent to
* @return A {@link CompletableFuture future} which completes, after all the updates on the {@link net.dv8tion.jda.api.entities.Member}
* have been completed
*/
CompletableFuture<Void> unsetRoles(ARole role, Long channelId);
CompletableFuture<Void> setRoleToLevel(Role role, Integer level, GuildMessageChannel messageChannel);
CompletableFuture<Void> unsetRoles(ARole role, GuildMessageChannel messageChannel);
List<AExperienceRole> getExperienceRolesAtLevel(Integer level, AServer server);
CompletableFuture<Void> unsetRoles(List<ARole> roles, Long channelId);
CompletableFuture<Void> unsetRoles(List<ARole> roles, Long channelId, AExperienceRole toAdd);
CompletableFuture<Void> unsetRoles(List<ARole> roles, GuildMessageChannel messageChannel);
/**
* Calculates the appropriate {@link AExperienceRole experienceRole} based on the provided list of {@link AExperienceRole experienceRole}

View File

@@ -37,6 +37,7 @@ public interface ExperienceRoleManagementService {
* @param role The {@link AExperienceRole experienceRole} to delete.
*/
void unsetRole(AExperienceRole role);
void unsetRoles(List<AExperienceRole> role);
/**
* Retrieves the {@link AExperienceRole experienceRole} which uses the given {@link ARole role} in the {@link AServer server}
@@ -44,6 +45,7 @@ public interface ExperienceRoleManagementService {
* @return the {@link AExperienceRole experienceRole} which uses the given {@link ARole role}
*/
AExperienceRole getRoleInServer(ARole role);
List<AExperienceRole> getRolesInServer(List<ARole> role);
/**
* Retrieves a possible {@link AExperienceRole role}, if it exists, for the given {@link ARole}. Returns an empty Optional if it does not exist

View File

@@ -3,6 +3,7 @@ package dev.sheldan.abstracto.experience.service.management;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
@@ -19,6 +20,7 @@ public interface UserExperienceManagementService {
* @return The {@link AUserExperience userExperience} object representing the {@link AUserInAServer userInAServer}
*/
AUserExperience findUserInServer(AUserInAServer aUserInAServer);
void removeExperienceRoleFromUsers(AExperienceRole experienceRole);
/**
* Retrieves a possible {@link AUserExperience userExperience} for the given ID of the {@link AUserInAServer}.