Skip to content

Commit 344496c

Browse files
eripe14igoyekvLuckyyycoderabbitai[bot]Rollczi
authored
GH-61 Add balance top command (#67)
* Add top balance command * Update EconomyPermissionConstant.java * Resolve Martin suggestion * Remove test method * Update EconomyBukkitPlugin.java * Resolve Martin suggestions * Followed contributors' instructions Fixed some mistakes in code, syntax, etc. Took 15 minutes * Refactor leadeboard. * [wip] cooking. * refactor * remove useless method. * Update eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update eternaleconomy-core/src/main/java/com/eternalcode/economy/leaderboard/LeaderboardService.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * remove limit. * rename leaderboard -> leaderboardIndex * Follow rollczi feedback's * revert * Follow rollczi feedback. * remove * revert * Fix benchmark. * Improve GetLeaderboardPosition * Improve benchmark. * More realistic benchmark. * Fix * Fix scheduler issues * Fix somethings * Improve vault provider * Fix thread error Signed-off-by: Martin Sulikowski <[email protected]> * Add name change handling Signed-off-by: Martin Sulikowski <[email protected]> * Fix build Signed-off-by: Martin Sulikowski <[email protected]> --------- Signed-off-by: Martin Sulikowski <[email protected]> Co-authored-by: Igor Michalski <[email protected]> Co-authored-by: Martin Sulikowski <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Rollczi <[email protected]>
1 parent 4a2a24e commit 344496c

File tree

19 files changed

+538
-86
lines changed

19 files changed

+538
-86
lines changed

eternaleconomy-api/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
22
`economy-java`
33
`economy-repositories`
4-
// `economy-checkstyle`
4+
// `economy-checkstyle`
55
`economy-publish`
66
}
77

eternaleconomy-core/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ dependencies {
5454

5555
testImplementation(platform("org.junit:junit-bom:5.11.4"))
5656
testImplementation("org.junit.jupiter:junit-jupiter")
57+
testImplementation("com.github.ben-manes.caffeine:caffeine:3.2.0")
58+
testImplementation("com.google.guava:guava:33.0.0-jre")
59+
5760
jmh("org.openjdk.jmh:jmh-core:1.37")
5861
jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37")
5962
jmh("org.openjdk.jmh:jmh-generator-bytecode:1.37")
@@ -83,7 +86,7 @@ bukkit {
8386
}
8487

8588
tasks.runServer {
86-
minecraftVersion("1.21.1")
89+
minecraftVersion("1.21.4")
8790
}
8891

8992
tasks.shadowJar {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.eternalcode.economy;
2+
3+
import com.eternalcode.commons.scheduler.Scheduler;
4+
import com.eternalcode.commons.scheduler.Task;
5+
import java.time.Duration;
6+
import java.util.concurrent.*;
7+
import java.util.function.Supplier;
8+
9+
public class SchedulerImpl implements Scheduler {
10+
11+
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
12+
13+
@Override
14+
public Task run(Runnable runnable) {
15+
return new TaskImpl(scheduler.submit(runnable));
16+
}
17+
18+
@Override
19+
public Task runAsync(Runnable runnable) {
20+
return new TaskImpl(scheduler.submit(runnable));
21+
}
22+
23+
@Override
24+
public Task runLater(Runnable runnable, Duration duration) {
25+
ScheduledFuture<?> future = scheduler.schedule(runnable, duration.toMillis(), TimeUnit.MILLISECONDS);
26+
return new TaskImpl(future);
27+
}
28+
29+
@Override
30+
public Task runLaterAsync(Runnable runnable, Duration duration) {
31+
ScheduledFuture<?> future = scheduler.schedule(runnable, duration.toMillis(), TimeUnit.MILLISECONDS);
32+
return new TaskImpl(future);
33+
}
34+
35+
@Override
36+
public Task timer(Runnable runnable, Duration duration, Duration interval) {
37+
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(runnable, duration.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
38+
return new TaskImpl(future);
39+
}
40+
41+
@Override
42+
public Task timerAsync(Runnable runnable, Duration duration, Duration interval) {
43+
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(runnable, duration.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
44+
return new TaskImpl(future);
45+
}
46+
47+
@Override
48+
public <T> CompletableFuture<T> complete(Supplier<T> supplier) {
49+
return CompletableFuture.supplyAsync(supplier);
50+
}
51+
52+
@Override
53+
public <T> CompletableFuture<T> completeAsync(Supplier<T> supplier) {
54+
return CompletableFuture.supplyAsync(supplier);
55+
}
56+
57+
private static class TaskImpl implements Task {
58+
private final Future<?> future;
59+
60+
public TaskImpl(Future<?> future) {
61+
this.future = future;
62+
}
63+
64+
@Override
65+
public void cancel() {
66+
future.cancel(true);
67+
}
68+
69+
@Override
70+
public boolean isCanceled() {
71+
return future.isCancelled();
72+
}
73+
74+
@Override
75+
public boolean isAsync() {
76+
return false;
77+
}
78+
79+
@Override
80+
public boolean isRunning() {
81+
return !future.isDone() && !future.isCancelled();
82+
}
83+
84+
@Override
85+
public boolean isRepeating() {
86+
return false;
87+
}
88+
}
89+
}

eternaleconomy-core/src/jmh/java/com/eternalcode/economy/account/AccountBenchmark.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package com.eternalcode.economy.account;
22

3+
import com.eternalcode.economy.SchedulerImpl;
34
import com.eternalcode.economy.account.database.AccountRepositoryInMemory;
45
import java.util.ArrayList;
56
import java.util.Collections;
67
import java.util.List;
8+
import java.util.UUID;
79
import java.util.logging.Logger;
810
import org.openjdk.jmh.annotations.Benchmark;
11+
import org.openjdk.jmh.annotations.Measurement;
12+
import org.openjdk.jmh.annotations.Scope;
913
import org.openjdk.jmh.annotations.Setup;
1014
import org.openjdk.jmh.annotations.State;
11-
import org.openjdk.jmh.annotations.Scope;
12-
import org.openjdk.jmh.annotations.Measurement;
1315
import org.openjdk.jmh.annotations.Warmup;
1416

15-
import java.util.UUID;
16-
1717
@State(Scope.Thread)
1818
public class AccountBenchmark {
1919

@@ -22,10 +22,12 @@ public class AccountBenchmark {
2222

2323
private final List<String> searches = new ArrayList<>();
2424
private AccountManager accountManager;
25+
// mimo że nie jest to bezpieczne dla wielu wątków, to w przypadku JMH można to zignorować i tak potrzebujemy losowości
26+
private int index = 0;
2527

2628
@Setup
2729
public void setUp() {
28-
accountManager = new AccountManager(new AccountRepositoryInMemory());
30+
accountManager = new AccountManager(new AccountRepositoryInMemory(), new SchedulerImpl());
2931

3032
// zapełnienie TreeMapy różnymi nazwami zapewnia, że będzie ona miała optymalne wyniki
3133
// tree mapa rozdziela elementy na podstawie ich klucza, więc im bardziej zróżnicowane klucze, tym "lepsze' wyniki
@@ -34,7 +36,7 @@ public void setUp() {
3436
for (char third : ALPHABET) {
3537
String name = String.valueOf(first) + second + third;
3638

37-
accountManager.create(UUID.randomUUID(), name);
39+
accountManager.getOrCreate(UUID.randomUUID(), name);
3840
}
3941
}
4042
}
@@ -55,14 +57,10 @@ public void setUp() {
5557
LOGGER.info("Acounts size: " + accountManager.getAccounts().size() + ", Searches size: " + searches.size());
5658
}
5759

58-
// mimo że nie jest to bezpieczne dla wielu wątków, to w przypadku JMH można to zignorować i tak potrzebujemy losowości
59-
private int index = 0;
60-
6160
@Benchmark
6261
@Warmup(iterations = 5, time = 1)
6362
@Measurement(iterations = 10, time = 1)
6463
public void benchmarkGetAccountStartingWith() {
6564
accountManager.getAccountStartingWith(searches.get(index++ % searches.size()));
6665
}
67-
68-
}
66+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.eternalcode.economy.leaderboard;
2+
3+
import com.eternalcode.economy.SchedulerImpl;
4+
import com.eternalcode.economy.account.Account;
5+
import com.eternalcode.economy.account.AccountManager;
6+
import com.eternalcode.economy.account.AccountPaymentService;
7+
import com.eternalcode.economy.account.database.AccountRepositoryInMemory;
8+
import com.eternalcode.economy.config.implementation.PluginConfig;
9+
import java.math.BigDecimal;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import java.util.UUID;
13+
import java.util.concurrent.CompletableFuture;
14+
import java.util.concurrent.ThreadLocalRandom;
15+
import java.util.concurrent.TimeUnit;
16+
import org.apache.commons.math3.distribution.ParetoDistribution;
17+
import org.openjdk.jmh.annotations.Benchmark;
18+
import org.openjdk.jmh.annotations.BenchmarkMode;
19+
import org.openjdk.jmh.annotations.Fork;
20+
import org.openjdk.jmh.annotations.Level;
21+
import org.openjdk.jmh.annotations.Measurement;
22+
import org.openjdk.jmh.annotations.Mode;
23+
import org.openjdk.jmh.annotations.OutputTimeUnit;
24+
import org.openjdk.jmh.annotations.Param;
25+
import org.openjdk.jmh.annotations.Scope;
26+
import org.openjdk.jmh.annotations.Setup;
27+
import org.openjdk.jmh.annotations.State;
28+
import org.openjdk.jmh.annotations.Warmup;
29+
import org.openjdk.jmh.infra.Blackhole;
30+
31+
@State(Scope.Benchmark)
32+
@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
33+
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
34+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
35+
@BenchmarkMode(Mode.AverageTime)
36+
@Fork(value = 1, jvmArgs = {"-Xms4G", "-Xmx8G", "-XX:+UseG1GC"})
37+
public class LeaderboardServiceBenchmark {
38+
39+
private static final int PAGE_SIZE = 100;
40+
private static final int MAX_PAGES = 50;
41+
42+
@Param({"100000", "1000000", "2000000"})
43+
private int accountsCount;
44+
45+
private LeaderboardService leaderboardService;
46+
private List<Account> targetAccounts = new ArrayList<>();
47+
48+
private static final String[] FIRST_NAMES = {
49+
"Alex", "Ben", "Chris", "Dana", "Emma", "Finn", "Grace", "Hannah", "Ian", "Jake",
50+
"Kara", "Liam", "Mia", "Noah", "Olivia", "Paul", "Quinn", "Rose", "Sam", "Tina"
51+
};
52+
private static final String[] SUFFIXES = {
53+
"Gamer", "Pro", "X", "123", "Master", "Ninja", "Legend", "King", "Queen", "Star",
54+
"Wolf", "Dragon", "Shadow", "Rider", "Blaze"
55+
};
56+
57+
@Setup(Level.Trial)
58+
public void setUp() {
59+
AccountRepositoryInMemory repository = new AccountRepositoryInMemory();
60+
AccountManager accountManager = new AccountManager(repository, new SchedulerImpl());
61+
AccountPaymentService paymentService = new AccountPaymentService(accountManager, new PluginConfig());
62+
this.leaderboardService = accountManager;
63+
64+
ParetoDistribution pareto = new ParetoDistribution(1.0, 2.0);
65+
ThreadLocalRandom random = ThreadLocalRandom.current();
66+
67+
for (int i = 0; i < accountsCount; i++) {
68+
String firstName = FIRST_NAMES[random.nextInt(FIRST_NAMES.length)];
69+
String suffix = SUFFIXES[random.nextInt(SUFFIXES.length)];
70+
String name = firstName + suffix + (random.nextInt(100) < 10 ? random.nextInt(100) : "");
71+
72+
BigDecimal balance = BigDecimal.valueOf(pareto.sample()).min(BigDecimal.valueOf(100_000));
73+
Account create = accountManager.getOrCreate(UUID.randomUUID(), name);
74+
paymentService.addBalance(create, balance);
75+
targetAccounts.add(create);
76+
}
77+
}
78+
79+
@Benchmark
80+
public void benchmarkGetLeaderboardPage(Blackhole blackhole) {
81+
int randomPage = ThreadLocalRandom.current().nextInt(1, Math.min(MAX_PAGES, accountsCount / PAGE_SIZE) + 1);
82+
CompletableFuture<LeaderboardPage> future = leaderboardService.getLeaderboardPage(randomPage, PAGE_SIZE);
83+
blackhole.consume(future.join());
84+
}
85+
86+
@Benchmark
87+
public void benchmarkGetLeaderboardPosition(Blackhole blackhole) {
88+
Account target = targetAccounts.get(ThreadLocalRandom.current().nextInt(targetAccounts.size()));
89+
CompletableFuture<LeaderboardEntry> future = leaderboardService.getLeaderboardPosition(target);
90+
blackhole.consume(future.join());
91+
}
92+
}

eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyBukkitPlugin.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.eternalcode.economy.account.AccountPaymentService;
1111
import com.eternalcode.economy.account.database.AccountRepository;
1212
import com.eternalcode.economy.account.database.AccountRepositoryImpl;
13+
import com.eternalcode.economy.account.database.AccountRepositoryInMemory;
1314
import com.eternalcode.economy.bridge.BridgeManager;
1415
import com.eternalcode.economy.command.admin.AdminAddCommand;
1516
import com.eternalcode.economy.command.admin.AdminBalanceCommand;
@@ -25,6 +26,7 @@
2526
import com.eternalcode.economy.command.message.InvalidBigDecimalMessage;
2627
import com.eternalcode.economy.command.player.MoneyBalanceCommand;
2728
import com.eternalcode.economy.command.player.MoneyTransferCommand;
29+
import com.eternalcode.economy.leaderboard.LeaderboardCommand;
2830
import com.eternalcode.economy.command.validator.notsender.NotSender;
2931
import com.eternalcode.economy.command.validator.notsender.NotSenderValidator;
3032
import com.eternalcode.economy.config.ConfigService;
@@ -49,6 +51,7 @@
4951
import java.io.File;
5052
import java.math.BigDecimal;
5153
import java.time.Duration;
54+
import java.util.UUID;
5255
import net.kyori.adventure.platform.AudienceProvider;
5356
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
5457
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -95,7 +98,7 @@ public void onEnable() {
9598
this.databaseManager.connect();
9699

97100
AccountRepository accountRepository = new AccountRepositoryImpl(this.databaseManager, scheduler);
98-
AccountManager accountManager = AccountManager.create(accountRepository);
101+
AccountManager accountManager = AccountManager.create(accountRepository, scheduler);
99102

100103
DecimalFormatter decimalFormatter = new DecimalFormatterImpl(pluginConfig);
101104
AccountPaymentService accountPaymentService = new AccountPaymentService(accountManager, pluginConfig);
@@ -128,7 +131,8 @@ public void onEnable() {
128131
new AdminBalanceCommand(noticeService, decimalFormatter),
129132
new MoneyBalanceCommand(noticeService, decimalFormatter),
130133
new MoneyTransferCommand(accountPaymentService, decimalFormatter, noticeService, pluginConfig),
131-
new EconomyReloadCommand(configService, noticeService)
134+
new EconomyReloadCommand(configService, noticeService),
135+
new LeaderboardCommand(noticeService, decimalFormatter, accountManager, pluginConfig)
132136
)
133137

134138
.context(Account.class, new AccountContext(accountManager, messageConfig))

eternaleconomy-core/src/main/java/com/eternalcode/economy/EconomyPermissionConstant.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class EconomyPermissionConstant {
1212
public static final String PLAYER_BALANCE_PERMISSION = "eternaleconomy.player.balance";
1313
public static final String PLAYER_BALANCE_OTHER_PERMISSION = "eternaleconomy.player.balance.other";
1414
public static final String PLAYER_PAY_PERMISSION = "eternaleconomy.player.pay";
15+
public static final String PLAYER_BALANCE_TOP_PERMISSION = "eternaleconomy.player.balance.top";
1516
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
package com.eternalcode.economy.account;
22

33
import java.math.BigDecimal;
4+
import java.util.Objects;
45
import java.util.UUID;
6+
import java.util.function.UnaryOperator;
57

68
public record Account(UUID uuid, String name, BigDecimal balance) {
79

10+
public Account withBalance(BigDecimal balance) {
11+
return new Account(uuid, name, balance);
12+
}
13+
14+
public Account withBalance(UnaryOperator<BigDecimal> operator) {
15+
return new Account(uuid, name, operator.apply(balance));
16+
}
17+
18+
@Override
19+
public boolean equals(Object o) {
20+
if (o == null || getClass() != o.getClass()) return false;
21+
Account account = (Account) o;
22+
return Objects.equals(uuid, account.uuid) && Objects.equals(name, account.name);
23+
}
24+
25+
@Override
26+
public int hashCode() {
27+
return Objects.hash(uuid, name);
28+
}
29+
830
}

0 commit comments

Comments
 (0)