-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fb9be78
commit dbb9746
Showing
29 changed files
with
1,921 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# cloud-processors-cooldown | ||
|
||
Postprocessor that adds command cooldowns. | ||
|
||
## Installation | ||
|
||
cloud-processors-cooldown is not yet available on Maven Central. | ||
|
||
## Usage | ||
|
||
The cooldown system is managed by a `CooldownManager`, so the first step is to create an instance of that: | ||
```java | ||
CooldownManager<YourSenderType> cooldownManager = CooldownManager.of(configuration); | ||
``` | ||
The configuration is an instance of `CooldownConfiguration`. Refer to the JavaDocs for information about specific options, | ||
but an example would be: | ||
```java | ||
CooldownConfiguration configuration = CooldownConfiguration.<YourSenderType>builder() | ||
.repository(CooldownRepository.forMap(new HashMap<>())) | ||
.addActiveCooldownListener(...) | ||
.addCooldownCreationListener(...) | ||
.cooldownNotifier(notifier) | ||
.build(); | ||
``` | ||
The listeners are invoked when different events take place. The active cooldown listener in particular may be used to | ||
inform the command sender that their command execution got blocked due to an active cooldown. | ||
|
||
The repository stores active cooldowns for a command sender in the form of cooldown profiles. | ||
The cooldowns are grouped by their `CooldownGroup`, by default a unique group will be created per command. | ||
You may create a named group by using `CooldownGroup.named(name)`. Commands that use the same cooldown group | ||
will have their cooldowns shared by the command sender. | ||
|
||
You may create a repository from a map, `CloudCache` or even implement your own. If you want to persist the cooldowns | ||
across multiple temporary sessions then you may use a mapping repository to store the cooldown profiles for a persistent key, | ||
rather than the potentially temporary command sender objects: | ||
```java | ||
CooldownRepository repository = CooldownRepository.mapping( | ||
sender -> sender.uuid(), | ||
CooldownRepository.forMap(new HashMap<UUID, CooldownProfile>()) | ||
); | ||
``` | ||
|
||
You may also customize how the cooldown profiles are created by passing a `CooldownProfileFactory` to the `CooldownConfiguration`. | ||
|
||
If you want to have the cooldowns automatically removed from the repository to prevent unused profiles from taking up memory you | ||
may register a `ScheduledCleanupCreationListener`: | ||
```java | ||
CooldownRepository repository = CooldownRepository.forMap(new HashMap<>()); | ||
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); | ||
CooldownConfiguration configuration = CooldownConfiguration.<YourSenderType>builder() | ||
// ... | ||
.repository(repository) | ||
.addCreationListener(new ScheduledCleanupCreationListener(executorService, repository)) | ||
.build(); | ||
``` | ||
|
||
You then need to register the postprocessor: | ||
```java | ||
commandManager.registerCommandPostProcessor(cooldownManager.createPostprocessor()); | ||
``` | ||
|
||
### Builders | ||
|
||
The cooldowns are configured using a `Cooldown` instance: | ||
```java | ||
Cooldown cooldown = Cooldown.of(DurationFunction.constant(Duration.ofMinutes(5L))); | ||
Cooldown cooldown = Cooldown.of(DurationFunction.constant(Duration.ofMinutes(5L)), CooldownGroup.named("group-name")); | ||
``` | ||
which can then be applied to the command by either manually setting the meta value: | ||
```java | ||
commandBuilder.meta(CooldownManager.META_COOLDOWN_DURATION, cooldown); | ||
``` | ||
or by applying the cooldown to the builder: | ||
```java | ||
commandBuilder.apply(cooldown); | ||
``` | ||
|
||
### Annotations | ||
|
||
Annotated commands may use the `@Cooldown` annotation: | ||
```java | ||
@Cooldown(duration = 5, timeUnit = ChronoUnit.MINUTES, group = "some-group") | ||
public void yourCommand() { | ||
// ... | ||
} | ||
``` | ||
You need to install the builder modifier for this to work: | ||
```java | ||
CooldownBuilderModifier.install(annotationParser); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
plugins { | ||
id("cloud-processors.base-conventions") | ||
id("cloud-processors.publishing-conventions") | ||
} | ||
|
||
dependencies { | ||
api(projects.cloudProcessorsCommon) | ||
|
||
compileOnly(libs.cloud.annotations) | ||
} | ||
|
||
// TODO(City): Disable this | ||
// we're getting errors on generated files due to -Werror :( | ||
tasks.withType<JavaCompile> { | ||
options.compilerArgs.remove("-Werror") | ||
} |
102 changes: 102 additions & 0 deletions
102
cloud-processors-cooldown/src/main/java/org/incendo/cloud/processors/cooldown/Cooldown.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// | ||
// MIT License | ||
// | ||
// Copyright (c) 2024 Incendo | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
// | ||
package org.incendo.cloud.processors.cooldown; | ||
|
||
import cloud.commandframework.Command; | ||
import org.apiguardian.api.API; | ||
import org.checkerframework.checker.nullness.qual.NonNull; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
import org.immutables.value.Value; | ||
import org.incendo.cloud.processors.immutables.StagedImmutableBuilder; | ||
|
||
/** | ||
* An instance of a cooldown for a {@link CooldownGroup} belonging to a command sender. | ||
* | ||
* @param <C> command sender type | ||
* @since 1.0.0 | ||
*/ | ||
@StagedImmutableBuilder | ||
@Value.Immutable | ||
@API(status = API.Status.STABLE, since = "1.0.0") | ||
public interface Cooldown<C> extends Command.Builder.Applicable<C> { | ||
|
||
/** | ||
* Returns a new cooldown with the given {@code duration} | ||
* using {@link CooldownConfiguration#fallbackGroup()} as the group. | ||
* | ||
* @param <C> command sender type | ||
* @param duration the duration | ||
* @return the cooldown | ||
*/ | ||
static <C> @NonNull Cooldown<C> of(final @NonNull DurationFunction<C> duration) { | ||
return Cooldown.<C>builder().duration(duration).build(); | ||
} | ||
|
||
/** | ||
* Returns a new cooldown with the given {@code duration} and {@code group}. | ||
* | ||
* @param <C> command sender type | ||
* @param duration the duration | ||
* @param group cooldown group | ||
* @return the cooldown | ||
*/ | ||
static <C> @NonNull Cooldown<C> of( | ||
final @NonNull DurationFunction<C> duration, | ||
final @NonNull CooldownGroup group | ||
) { | ||
return Cooldown.<C>builder().duration(duration).group(group).build(); | ||
} | ||
|
||
/** | ||
* Returns a new cooldown builder. | ||
* | ||
* @param <C> command sender type | ||
* @return the builder | ||
*/ | ||
static <C> ImmutableCooldown.@NonNull DurationBuildStage<C> builder() { | ||
return ImmutableCooldown.builder(); | ||
} | ||
|
||
/** | ||
* Returns the cooldown duration. | ||
* | ||
* @return the duration | ||
*/ | ||
@NonNull DurationFunction<C> duration(); | ||
|
||
/** | ||
* Returns the group that this instance belongs to. | ||
* If set to {@code null} then {@link CooldownConfiguration#fallbackGroup()} will be used. | ||
* | ||
* @return the cooldown group | ||
*/ | ||
default @Nullable CooldownGroup group() { | ||
return null; | ||
} | ||
|
||
@Override | ||
default Command.@NonNull Builder<C> applyToCommandBuilder(Command.@NonNull Builder<C> builder) { | ||
return builder.meta(CooldownManager.META_COOLDOWN_DURATION, this); | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
...s-cooldown/src/main/java/org/incendo/cloud/processors/cooldown/CooldownConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// | ||
// MIT License | ||
// | ||
// Copyright (c) 2024 Incendo | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
// | ||
package org.incendo.cloud.processors.cooldown; | ||
|
||
import cloud.commandframework.Command; | ||
import cloud.commandframework.context.CommandContext; | ||
import java.time.Clock; | ||
import java.util.List; | ||
import java.util.function.Function; | ||
import java.util.function.Predicate; | ||
import org.apiguardian.api.API; | ||
import org.checkerframework.checker.nullness.qual.NonNull; | ||
import org.immutables.value.Value; | ||
import org.incendo.cloud.processors.cooldown.listener.CooldownActiveListener; | ||
import org.incendo.cloud.processors.cooldown.listener.CooldownCreationListener; | ||
import org.incendo.cloud.processors.cooldown.profile.CooldownProfileFactory; | ||
import org.incendo.cloud.processors.cooldown.profile.StandardCooldownProfileFactory; | ||
import org.incendo.cloud.processors.immutables.StagedImmutableBuilder; | ||
|
||
/** | ||
* Configuration for a {@link CooldownManager}. | ||
* | ||
* @param <C> command sender type | ||
* @since 1.0.0 | ||
*/ | ||
@Value.Immutable | ||
@StagedImmutableBuilder | ||
@API(status = API.Status.STABLE, since = "1.0.0") | ||
public interface CooldownConfiguration<C> { | ||
|
||
/** | ||
* Returns a new configuration builder. | ||
* | ||
* @param <C> command sender type | ||
* @return the builder | ||
*/ | ||
static <C> ImmutableCooldownConfiguration.@NonNull RepositoryBuildStage<C> builder() { | ||
return ImmutableCooldownConfiguration.builder(); | ||
} | ||
|
||
/** | ||
* Returns the repository used to store cooldowns. | ||
* | ||
* @return the repository | ||
*/ | ||
@NonNull CooldownRepository<C> repository(); | ||
|
||
/** | ||
* Returns the active cooldown listeners. | ||
* | ||
* <p>The active cooldown listeners are invoked when a cooldown is preventing a sender from executing a command.</p> | ||
* | ||
* @return active cooldown listeners | ||
*/ | ||
@NonNull List<CooldownActiveListener<C>> activeCooldownListeners(); | ||
|
||
/** | ||
* Returns the creation listeners. | ||
* | ||
* <p>The creation listeners are invoked when a new cooldown is created.</p> | ||
* | ||
* @return creation listeners | ||
*/ | ||
@NonNull List<CooldownCreationListener<C>> creationListeners(); | ||
|
||
/** | ||
* Returns a predicate that determines whether the {@link CommandContext} | ||
* should bypass the cooldown requirement. | ||
* | ||
* <p>The default implementation always returns {@code false}</p> | ||
* | ||
* @return predicate that determines whether cooldowns should be skipped | ||
*/ | ||
default @NonNull Predicate<@NonNull CommandContext<C>> bypassCooldown() { | ||
return context -> false; | ||
} | ||
|
||
/** | ||
* Returns the clock used to calculate the current time. | ||
* | ||
* @return the clock | ||
*/ | ||
default @NonNull Clock clock() { | ||
return Clock.systemUTC(); | ||
} | ||
|
||
/** | ||
* Returns the factory that produces cooldown profiles. | ||
* | ||
* @return cooldown profile factory | ||
*/ | ||
default @NonNull CooldownProfileFactory profileFactory() { | ||
return new StandardCooldownProfileFactory(this); | ||
} | ||
|
||
/** | ||
* Returns the function that determines the fallback {@link CooldownGroup} in case no group has been provided | ||
* to the {@link Cooldown}. | ||
* | ||
* @return the fallback group function | ||
*/ | ||
default @NonNull Function<@NonNull Command<C>, @NonNull CooldownGroup> fallbackGroup() { | ||
return CooldownGroup::command; | ||
} | ||
} |
Oops, something went wrong.